34330 lines
1.1 MiB
34330 lines
1.1 MiB
require({cache:{
|
|
'url:dijit/templates/CheckedMenuItem.html':"<tr class=\"dijitReset dijitMenuItem\" data-dojo-attach-point=\"focusNode\" role=\"menuitemcheckbox\" tabIndex=\"-1\">\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(){
|
|
require({cache:{
|
|
'url:dijit/form/templates/TextBox.html':"<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" role=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" data-dojo-attach-point='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"}});
|
|
define("dijit/form/TextBox", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-construct", // domConstruct.create
|
|
"dojo/dom-style", // domStyle.getComputedStyle
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/sniff", // has("ie") has("mozilla")
|
|
"./_FormValueWidget",
|
|
"./_TextBoxMixin",
|
|
"dojo/text!./templates/TextBox.html",
|
|
"../main" // to export dijit._setSelectionRange, remove in 2.0
|
|
], function(declare, domConstruct, domStyle, kernel, lang, has,
|
|
_FormValueWidget, _TextBoxMixin, template, dijit){
|
|
|
|
// module:
|
|
// dijit/form/TextBox
|
|
|
|
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);
|
|
},
|
|
|
|
postCreate: function(){
|
|
this.inherited(arguments);
|
|
|
|
if(has("ie") < 9){
|
|
// IE INPUT tag fontFamily has to be set directly using STYLE
|
|
// the defer gives IE a chance to render the TextBox and to deal with font inheritance
|
|
this.defer(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.*/}
|
|
});
|
|
}
|
|
},
|
|
|
|
_onInput: function(e){
|
|
this.inherited(arguments);
|
|
if(this.intermediateChanges){ // _TextBoxMixin uses onInput
|
|
// allow the key to post to the widget input box
|
|
this.defer(function(){ this._handleOnChange(this.get('value'), false); });
|
|
}
|
|
},
|
|
|
|
_setPlaceHolderAttr: function(v){
|
|
this._set("placeHolder", v);
|
|
if(!this._phspan){
|
|
this._attachPoints.push('_phspan');
|
|
// dijitInputField class gives placeHolder same padding as the input field
|
|
// parent node already has dijitInputField class but it doesn't affect this <span>
|
|
// since it's position: absolute.
|
|
this._phspan = domConstruct.create('span',{ onmousedown:function(e){ e.preventDefault(); }, className:'dijitPlaceHolder dijitInputField'},this.textbox,'after');
|
|
}
|
|
this._phspan.innerHTML="";
|
|
this._phspan.appendChild(this._phspan.ownerDocument.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 get('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();
|
|
|
|
if(has("mozilla")){
|
|
if(this.selectOnClick){
|
|
// clear selection so that the next mouse click doesn't reselect
|
|
this.textbox.selectionStart = this.textbox.selectionEnd = undefined;
|
|
}
|
|
}
|
|
},
|
|
|
|
_onFocus: function(/*String*/ by){
|
|
if(this.disabled || this.readOnly){ return; }
|
|
this.inherited(arguments);
|
|
this._updatePlaceHolder();
|
|
}
|
|
});
|
|
|
|
if(has("ie")){
|
|
TextBox.prototype._isTextSelected = function(){
|
|
var range = this.ownerDocument.selection.createRange();
|
|
var parent = range.parentElement();
|
|
return parent == this.textbox && range.text.length > 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
return TextBox;
|
|
});
|
|
|
|
},
|
|
'dijit/_base/scroll':function(){
|
|
define("dijit/_base/scroll", [
|
|
"dojo/window", // windowUtils.scrollIntoView
|
|
"../main" // export symbol to dijit
|
|
], function(windowUtils, dijit){
|
|
// module:
|
|
// dijit/_base/scroll
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// Back compatibility module, new code should use windowUtils directly instead of using this module.
|
|
};
|
|
=====*/
|
|
|
|
dijit.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
|
|
// summary:
|
|
// Scroll the passed node into view, if it is not already.
|
|
// Deprecated, use `windowUtils.scrollIntoView` instead.
|
|
|
|
windowUtils.scrollIntoView(node, pos);
|
|
};
|
|
});
|
|
|
|
},
|
|
'dijit/_TemplatedMixin':function(){
|
|
define("dijit/_TemplatedMixin", [
|
|
"dojo/_base/lang", // lang.getObject
|
|
"dojo/touch",
|
|
"./_WidgetBase",
|
|
"dojo/string", // string.substitute string.trim
|
|
"dojo/cache", // dojo.cache
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-construct", // domConstruct.destroy, domConstruct.toDom
|
|
"dojo/sniff", // has("ie")
|
|
"dojo/_base/unload" // unload.addOnWindowUnload
|
|
], function(lang, touch, _WidgetBase, string, cache, array, declare, domConstruct, has, unload) {
|
|
|
|
// module:
|
|
// dijit/_TemplatedMixin
|
|
|
|
var _TemplatedMixin = declare("dijit._TemplatedMixin", null, {
|
|
// summary:
|
|
// Mixin for widgets that are instantiated from a template
|
|
|
|
// templateString: [protected] String
|
|
// A string that represents the widget template.
|
|
// Use in conjunction with dojo.cache() to load from a file.
|
|
templateString: null,
|
|
|
|
// templatePath: [protected deprecated] String
|
|
// Path to template (HTML file) for this widget relative to dojo.baseUrl.
|
|
// Deprecated: use templateString with require([... "dojo/text!..."], ...) instead
|
|
templatePath: null,
|
|
|
|
// skipNodeCache: [protected] Boolean
|
|
// If using a cached widget template nodes poses issues for a
|
|
// particular widget class, it can set this property to ensure
|
|
// that its template is always re-built from a string
|
|
_skipNodeCache: false,
|
|
|
|
// _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,
|
|
|
|
/*=====
|
|
// _attachPoints: [private] String[]
|
|
// List of widget attribute names associated with data-dojo-attach-point=... in the
|
|
// template, ex: ["containerNode", "labelNode"]
|
|
_attachPoints: [],
|
|
|
|
// _attachEvents: [private] Handle[]
|
|
// List of connections associated with data-dojo-attach-event=... in the
|
|
// template
|
|
_attachEvents: [],
|
|
=====*/
|
|
|
|
constructor: function(/*===== params, srcNodeRef =====*/){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree.
|
|
|
|
this._attachPoints = [];
|
|
this._attachEvents = [];
|
|
},
|
|
|
|
_stringRepl: function(tmpl){
|
|
// summary:
|
|
// Does substitution of ${foo} type properties in template string
|
|
// tags:
|
|
// private
|
|
var className = this.declaredClass, _this = this;
|
|
// Cache contains a string because we need to do property replacement
|
|
// do the property replacement
|
|
return string.substitute(tmpl, this, function(value, key){
|
|
if(key.charAt(0) == '!'){ value = lang.getObject(key.substr(1), false, _this); }
|
|
if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide
|
|
if(value == null){ return ""; }
|
|
|
|
// Substitution keys beginning with ! will skip the transform step,
|
|
// in case a user wishes to insert unescaped markup, e.g. ${!foo}
|
|
return key.charAt(0) == "!" ? value :
|
|
// Safer substitution, see heading "Attribute values" in
|
|
// http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
|
|
value.toString().replace(/"/g,"""); //TODO: add &? use encodeXML method?
|
|
}, this);
|
|
},
|
|
|
|
buildRendering: function(){
|
|
// summary:
|
|
// Construct the UI for this widget from a template, setting this.domNode.
|
|
// tags:
|
|
// protected
|
|
|
|
if(!this.templateString){
|
|
this.templateString = cache(this.templatePath, {sanitize: true});
|
|
}
|
|
|
|
// Lookup cached version of template, and download to cache if it
|
|
// isn't there already. Returns either a DomNode or a string, depending on
|
|
// whether or not the template contains ${foo} replacement parameters.
|
|
var cached = _TemplatedMixin.getCachedTemplate(this.templateString, this._skipNodeCache, this.ownerDocument);
|
|
|
|
var node;
|
|
if(lang.isString(cached)){
|
|
node = domConstruct.toDom(this._stringRepl(cached), this.ownerDocument);
|
|
if(node.nodeType != 1){
|
|
// Flag common problems such as templates with multiple top level nodes (nodeType == 11)
|
|
throw new Error("Invalid template: " + cached);
|
|
}
|
|
}else{
|
|
// if it's a node, all we have to do is clone it
|
|
node = cached.cloneNode(true);
|
|
}
|
|
|
|
this.domNode = node;
|
|
|
|
// Call down to _Widget.buildRendering() to get base classes assigned
|
|
// TODO: change the baseClass assignment to _setBaseClassAttr
|
|
this.inherited(arguments);
|
|
|
|
// recurse through the node, looking for, and attaching to, our
|
|
// attachment points and events, which should be defined on the template node.
|
|
this._attachTemplateNodes(node, function(n,p){ return n.getAttribute(p); });
|
|
|
|
this._beforeFillContent(); // hook for _WidgetsInTemplateMixin
|
|
|
|
this._fillContent(this.srcNodeRef);
|
|
},
|
|
|
|
_beforeFillContent: function(){
|
|
},
|
|
|
|
_fillContent: function(/*DomNode*/ source){
|
|
// summary:
|
|
// Relocate source contents to templated container node.
|
|
// this.containerNode must be able to receive children, or exceptions will be thrown.
|
|
// tags:
|
|
// protected
|
|
var dest = this.containerNode;
|
|
if(source && dest){
|
|
while(source.hasChildNodes()){
|
|
dest.appendChild(source.firstChild);
|
|
}
|
|
}
|
|
},
|
|
|
|
_attachTemplateNodes: function(rootNode, getAttrFunc){
|
|
// summary:
|
|
// Iterate through the template and attach functions and nodes accordingly.
|
|
// Alternately, if rootNode is an array of widgets, then will process data-dojo-attach-point
|
|
// etc. for those widgets.
|
|
// description:
|
|
// Map widget properties and functions to the handlers specified in
|
|
// the dom node and it's descendants. This function iterates over all
|
|
// nodes and looks for these properties:
|
|
//
|
|
// - dojoAttachPoint/data-dojo-attach-point
|
|
// - dojoAttachEvent/data-dojo-attach-event
|
|
// rootNode: DomNode|Widget[]
|
|
// the node to search for properties. All children will be searched.
|
|
// getAttrFunc: Function
|
|
// a function which will be used to obtain property for a given
|
|
// DomNode/Widget
|
|
// tags:
|
|
// private
|
|
|
|
var nodes = lang.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*"));
|
|
var x = lang.isArray(rootNode) ? 0 : -1;
|
|
for(; x < 0 || nodes[x]; x++){ // don't access nodes.length on IE, see #14346
|
|
var baseNode = (x == -1) ? rootNode : nodes[x];
|
|
if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){
|
|
continue;
|
|
}
|
|
// Process data-dojo-attach-point
|
|
var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point");
|
|
if(attachPoint){
|
|
var point, points = attachPoint.split(/\s*,\s*/);
|
|
while((point = points.shift())){
|
|
if(lang.isArray(this[point])){
|
|
this[point].push(baseNode);
|
|
}else{
|
|
this[point]=baseNode;
|
|
}
|
|
this._attachPoints.push(point);
|
|
}
|
|
}
|
|
|
|
// Process data-dojo-attach-event
|
|
var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event");
|
|
if(attachEvent){
|
|
// NOTE: we want to support attributes that have the form
|
|
// "domEvent: nativeEvent; ..."
|
|
var event, events = attachEvent.split(/\s*,\s*/);
|
|
var trim = lang.trim;
|
|
while((event = events.shift())){
|
|
if(event){
|
|
var thisFunc = null;
|
|
if(event.indexOf(":") != -1){
|
|
// oh, if only JS had tuple assignment
|
|
var funcNameArr = event.split(":");
|
|
event = trim(funcNameArr[0]);
|
|
thisFunc = trim(funcNameArr[1]);
|
|
}else{
|
|
event = trim(event);
|
|
}
|
|
if(!thisFunc){
|
|
thisFunc = event;
|
|
}
|
|
// Map "press", "move" and "release" to keys.touch, keys.move, keys.release
|
|
this._attachEvents.push(this.connect(baseNode, touch[event] || event, thisFunc));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
destroyRendering: function(){
|
|
// Delete all attach points to prevent IE6 memory leaks.
|
|
array.forEach(this._attachPoints, function(point){
|
|
delete this[point];
|
|
}, this);
|
|
this._attachPoints = [];
|
|
|
|
// And same for event handlers
|
|
array.forEach(this._attachEvents, this.disconnect, this);
|
|
this._attachEvents = [];
|
|
|
|
this.inherited(arguments);
|
|
}
|
|
});
|
|
|
|
// key is templateString; object is either string or DOM tree
|
|
_TemplatedMixin._templateCache = {};
|
|
|
|
_TemplatedMixin.getCachedTemplate = function(templateString, alwaysUseString, doc){
|
|
// summary:
|
|
// Static method to get a template based on the templatePath or
|
|
// templateString key
|
|
// templateString: String
|
|
// The template
|
|
// alwaysUseString: Boolean
|
|
// Don't cache the DOM tree for this template, even if it doesn't have any variables
|
|
// doc: Document?
|
|
// The target document. Defaults to document global if unspecified.
|
|
// returns: Mixed
|
|
// Either string (if there are ${} variables that need to be replaced) or just
|
|
// a DOM tree (if the node can be cloned directly)
|
|
|
|
// is it already cached?
|
|
var tmplts = _TemplatedMixin._templateCache;
|
|
var key = templateString;
|
|
var cached = tmplts[key];
|
|
if(cached){
|
|
try{
|
|
// if the cached value is an innerHTML string (no ownerDocument) or a DOM tree created within the
|
|
// current document, then use the current cached value
|
|
if(!cached.ownerDocument || cached.ownerDocument == (doc || document)){
|
|
// string or node of the same document
|
|
return cached;
|
|
}
|
|
}catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
|
|
domConstruct.destroy(cached);
|
|
}
|
|
|
|
templateString = string.trim(templateString);
|
|
|
|
if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){
|
|
// there are variables in the template so all we can do is cache the string
|
|
return (tmplts[key] = templateString); //String
|
|
}else{
|
|
// there are no variables in the template so we can cache the DOM tree
|
|
var node = domConstruct.toDom(templateString, doc);
|
|
if(node.nodeType != 1){
|
|
throw new Error("Invalid template: " + templateString);
|
|
}
|
|
return (tmplts[key] = node); //Node
|
|
}
|
|
};
|
|
|
|
if(has("ie")){
|
|
unload.addOnWindowUnload(function(){
|
|
var cache = _TemplatedMixin._templateCache;
|
|
for(var key in cache){
|
|
var value = cache[key];
|
|
if(typeof value == "object"){ // value is either a string or a DOM node template
|
|
domConstruct.destroy(value);
|
|
}
|
|
delete cache[key];
|
|
}
|
|
});
|
|
}
|
|
|
|
// These arguments can be specified for widgets which are used in templates.
|
|
// Since any widget can be specified as sub widgets in template, mix it
|
|
// into the base widget class. (This is a hack, but it's effective.).
|
|
// Remove for 2.0. Also, hide from API doc parser.
|
|
lang.extend(_WidgetBase, /*===== {} || =====*/ {
|
|
dojoAttachEvent: "",
|
|
dojoAttachPoint: ""
|
|
});
|
|
|
|
return _TemplatedMixin;
|
|
});
|
|
|
|
},
|
|
'dijit/_CssStateMixin':function(){
|
|
define("dijit/_CssStateMixin", [
|
|
"dojo/_base/array", // array.forEach array.map
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.isDescendant()
|
|
"dojo/dom-class", // domClass.toggle
|
|
"dojo/has",
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/on",
|
|
"dojo/ready",
|
|
"dojo/_base/window", // win.body
|
|
"./registry"
|
|
], function(array, declare, dom, domClass, has, lang, on, ready, win, registry){
|
|
|
|
// module:
|
|
// dijit/_CssStateMixin
|
|
|
|
var CssStateMixin = declare("dijit._CssStateMixin", [], {
|
|
// summary:
|
|
// Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus
|
|
// state changes, and also higher-level state changes such becoming disabled or selected.
|
|
//
|
|
// description:
|
|
// By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically
|
|
// maintain CSS classes on the widget root node (this.domNode) depending on hover,
|
|
// active, focus, etc. state. Ex: with a baseClass of dijitButton, it will apply the classes
|
|
// dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it.
|
|
//
|
|
// It also sets CSS like dijitButtonDisabled based on widget semantic state.
|
|
//
|
|
// By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons
|
|
// within the widget).
|
|
|
|
// cssStateNodes: [protected] Object
|
|
// List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus
|
|
//
|
|
// Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names
|
|
// (like "dijitUpArrowButton"). Example:
|
|
// | {
|
|
// | "upArrowButton": "dijitUpArrowButton",
|
|
// | "downArrowButton": "dijitDownArrowButton"
|
|
// | }
|
|
// The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it
|
|
// is hovered, etc.
|
|
cssStateNodes: {},
|
|
|
|
// hovering: [readonly] Boolean
|
|
// True if cursor is over this widget
|
|
hovering: false,
|
|
|
|
// active: [readonly] Boolean
|
|
// True if mouse was pressed while over this widget, and hasn't been released yet
|
|
active: false,
|
|
|
|
_applyAttributes: function(){
|
|
// This code would typically be in postCreate(), but putting in _applyAttributes() for
|
|
// performance: so the class changes happen before DOM is inserted into the document.
|
|
// Change back to postCreate() in 2.0. See #11635.
|
|
|
|
this.inherited(arguments);
|
|
|
|
// Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node
|
|
array.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active", "_opened"], function(attr){
|
|
this.watch(attr, lang.hitch(this, "_setStateClass"));
|
|
}, this);
|
|
|
|
// Track hover and active mouse events on widget root node, plus possibly on subnodes
|
|
for(var ap in this.cssStateNodes){
|
|
this._trackMouseState(this[ap], this.cssStateNodes[ap]);
|
|
}
|
|
this._trackMouseState(this.domNode, this.baseClass);
|
|
|
|
// Set state initially; there's probably no hover/active/focus state but widget might be
|
|
// disabled/readonly/checked/selected so we want to set CSS classes for those conditions.
|
|
this._setStateClass();
|
|
},
|
|
|
|
_cssMouseEvent: function(/*Event*/ event){
|
|
// summary:
|
|
// Handler for CSS event on this.domNode. Sets hovering and active properties depending on mouse state,
|
|
// which triggers _setStateClass() to set appropriate CSS classes for this.domNode.
|
|
|
|
if(!this.disabled){
|
|
switch(event.type){
|
|
case "mouseover":
|
|
this._set("hovering", true);
|
|
this._set("active", this._mouseDown);
|
|
break;
|
|
case "mouseout":
|
|
this._set("hovering", false);
|
|
this._set("active", false);
|
|
break;
|
|
case "mousedown":
|
|
case "touchstart":
|
|
this._set("active", true);
|
|
break;
|
|
case "mouseup":
|
|
case "touchend":
|
|
this._set("active", false);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
_setStateClass: function(){
|
|
// summary:
|
|
// Update the visual state of the widget by setting the css classes on this.domNode
|
|
// (or this.stateNode if defined) by combining this.baseClass with
|
|
// various suffixes that represent the current widget state(s).
|
|
//
|
|
// description:
|
|
// In the case where a widget has multiple
|
|
// states, it sets the class based on all possible
|
|
// combinations. For example, an invalid form widget that is being hovered
|
|
// will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover".
|
|
//
|
|
// The widget may have one or more of the following states, determined
|
|
// by this.state, this.checked, this.valid, and this.selected:
|
|
//
|
|
// - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid
|
|
// - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet
|
|
// - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true
|
|
// - Selected - ex: currently selected tab will have this.selected==true
|
|
//
|
|
// In addition, it may have one or more of the following states,
|
|
// based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused):
|
|
//
|
|
// - Disabled - if the widget is disabled
|
|
// - Active - if the mouse (or space/enter key?) is being pressed down
|
|
// - Focused - if the widget has focus
|
|
// - Hover - if the mouse is over the widget
|
|
|
|
// Compute new set of classes
|
|
var newStateClasses = this.baseClass.split(" ");
|
|
|
|
function multiply(modifier){
|
|
newStateClasses = newStateClasses.concat(array.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier);
|
|
}
|
|
|
|
if(!this.isLeftToRight()){
|
|
// For RTL mode we need to set an addition class like dijitTextBoxRtl.
|
|
multiply("Rtl");
|
|
}
|
|
|
|
var checkedState = this.checked == "mixed" ? "Mixed" : (this.checked ? "Checked" : "");
|
|
if(this.checked){
|
|
multiply(checkedState);
|
|
}
|
|
if(this.state){
|
|
multiply(this.state);
|
|
}
|
|
if(this.selected){
|
|
multiply("Selected");
|
|
}
|
|
if(this._opened){
|
|
multiply("Opened");
|
|
}
|
|
|
|
if(this.disabled){
|
|
multiply("Disabled");
|
|
}else if(this.readOnly){
|
|
multiply("ReadOnly");
|
|
}else{
|
|
if(this.active){
|
|
multiply("Active");
|
|
}else if(this.hovering){
|
|
multiply("Hover");
|
|
}
|
|
}
|
|
|
|
if(this.focused){
|
|
multiply("Focused");
|
|
}
|
|
|
|
// Remove old state classes and add new ones.
|
|
// For performance concerns we only write into domNode.className once.
|
|
var tn = this.stateNode || this.domNode,
|
|
classHash = {}; // set of all classes (state and otherwise) for node
|
|
|
|
array.forEach(tn.className.split(" "), function(c){ classHash[c] = true; });
|
|
|
|
if("_stateClasses" in this){
|
|
array.forEach(this._stateClasses, function(c){ delete classHash[c]; });
|
|
}
|
|
|
|
array.forEach(newStateClasses, function(c){ classHash[c] = true; });
|
|
|
|
var newClasses = [];
|
|
for(var c in classHash){
|
|
newClasses.push(c);
|
|
}
|
|
tn.className = newClasses.join(" ");
|
|
|
|
this._stateClasses = newStateClasses;
|
|
},
|
|
|
|
_subnodeCssMouseEvent: function(node, clazz, evt){
|
|
// summary:
|
|
// Handler for hover/active mouse event on widget's subnode
|
|
if(this.disabled || this.readOnly){
|
|
return;
|
|
}
|
|
function hover(isHovering){
|
|
domClass.toggle(node, clazz+"Hover", isHovering);
|
|
}
|
|
function active(isActive){
|
|
domClass.toggle(node, clazz+"Active", isActive);
|
|
}
|
|
function focused(isFocused){
|
|
domClass.toggle(node, clazz+"Focused", isFocused);
|
|
}
|
|
switch(evt.type){
|
|
case "mouseover":
|
|
hover(true);
|
|
break;
|
|
case "mouseout":
|
|
hover(false);
|
|
active(false);
|
|
break;
|
|
case "mousedown":
|
|
case "touchstart":
|
|
active(true);
|
|
break;
|
|
case "mouseup":
|
|
case "touchend":
|
|
active(false);
|
|
break;
|
|
case "focus":
|
|
case "focusin":
|
|
focused(true);
|
|
break;
|
|
case "blur":
|
|
case "focusout":
|
|
focused(false);
|
|
break;
|
|
}
|
|
},
|
|
|
|
_trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){
|
|
// summary:
|
|
// Track mouse/focus events on specified node and set CSS class on that node to indicate
|
|
// current state. Usually not called directly, but via cssStateNodes attribute.
|
|
// description:
|
|
// Given class=foo, will set the following CSS class on the node
|
|
//
|
|
// - fooActive: if the user is currently pressing down the mouse button while over the node
|
|
// - fooHover: if the user is hovering the mouse over the node, but not pressing down a button
|
|
// - fooFocus: if the node is focused
|
|
//
|
|
// Note that it won't set any classes if the widget is disabled.
|
|
// node: DomNode
|
|
// Should be a sub-node of the widget, not the top node (this.domNode), since the top node
|
|
// is handled specially and automatically just by mixing in this class.
|
|
// clazz: String
|
|
// CSS class name (ex: dijitSliderUpArrow)
|
|
|
|
// Flag for listener code below to call this._cssMouseEvent() or this._subnodeCssMouseEvent()
|
|
// when node is hovered/active
|
|
node._cssState = clazz;
|
|
}
|
|
});
|
|
|
|
ready(function(){
|
|
// Document level listener to catch hover etc. events on widget root nodes and subnodes.
|
|
// Note that when the mouse is moved quickly, a single onmouseenter event could signal that multiple widgets
|
|
// have been hovered or unhovered (try test_Accordion.html)
|
|
function handler(evt){
|
|
// Poor man's event propagation. Don't propagate event to ancestors of evt.relatedTarget,
|
|
// to avoid processing mouseout events moving from a widget's domNode to a descendant node;
|
|
// such events shouldn't be interpreted as a mouseleave on the widget.
|
|
if(!dom.isDescendant(evt.relatedTarget, evt.target)){
|
|
for(var node = evt.target; node && node != evt.relatedTarget; node = node.parentNode){
|
|
// Process any nodes with _cssState property. They are generally widget root nodes,
|
|
// but could also be sub-nodes within a widget
|
|
if(node._cssState){
|
|
var widget = registry.getEnclosingWidget(node);
|
|
if(widget){
|
|
if(node == widget.domNode){
|
|
// event on the widget's root node
|
|
widget._cssMouseEvent(evt);
|
|
}else{
|
|
// event on widget's sub-node
|
|
widget._subnodeCssMouseEvent(node, node._cssState, evt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function ieHandler(evt){
|
|
evt.target = evt.srcElement;
|
|
handler(evt);
|
|
}
|
|
|
|
// Use addEventListener() (and attachEvent() on IE) to catch the relevant events even if other handlers
|
|
// (on individual nodes) call evt.stopPropagation() or event.stopEvent().
|
|
// Currently typematic.js is doing that, not sure why.
|
|
// Don't monitor mouseover/mouseout on mobile because iOS generates "phantom" mouseover/mouseout events when
|
|
// drag-scrolling, at the point in the viewport where the drag originated. Test the Tree in api viewer.
|
|
var body = win.body(),
|
|
types = (has("touch") ? [] : ["mouseover", "mouseout"]).concat(["mousedown", "touchstart", "mouseup", "touchend"]);
|
|
array.forEach(types, function(type){
|
|
if(body.addEventListener){
|
|
body.addEventListener(type, handler, true); // W3C
|
|
}else{
|
|
body.attachEvent("on"+type, ieHandler); // IE
|
|
}
|
|
});
|
|
|
|
// Track focus events on widget sub-nodes that have been registered via _trackMouseState().
|
|
// However, don't track focus events on the widget root nodes, because focus is tracked via the
|
|
// focus manager (and it's not really tracking focus, but rather tracking that focus is on one of the widget's
|
|
// nodes or a subwidget's node or a popup node, etc.)
|
|
// Remove for 2.0 (if focus CSS needed, just use :focus pseudo-selector).
|
|
on(body, "focusin, focusout", function(evt){
|
|
var node = evt.target;
|
|
if(node._cssState && !node.getAttribute("widgetId")){
|
|
var widget = registry.getEnclosingWidget(node);
|
|
widget._subnodeCssMouseEvent(node, node._cssState, evt);
|
|
}
|
|
});
|
|
});
|
|
|
|
return CssStateMixin;
|
|
});
|
|
|
|
},
|
|
'dijit/layout/ScrollingTabController':function(){
|
|
require({cache:{
|
|
'url:dijit/layout/templates/ScrollingTabController.html':"<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div data-dojo-type=\"dijit.layout._ScrollingTabControllerMenuButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\"\n\t\t\tdata-dojo-props=\"containerId: '${containerId}', iconClass: 'dijitTabStripMenuIcon',\n\t\t\t\t\tdropDownPosition: ['below-alt', 'above-alt']\"\n\t\t\tdata-dojo-attach-point=\"_menuBtn\" showLabel=\"false\" title=\"\">▼</div>\n\t<div data-dojo-type=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\"\n\t\t\tdata-dojo-props=\"iconClass:'dijitTabStripSlideLeftIcon', showLabel:false, title:''\"\n\t\t\tdata-dojo-attach-point=\"_leftBtn\" data-dojo-attach-event=\"onClick: doSlideLeft\">◀</div>\n\t<div data-dojo-type=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\"\n\t\t\tdata-dojo-props=\"iconClass:'dijitTabStripSlideRightIcon', showLabel:false, title:''\"\n\t\t\tdata-dojo-attach-point=\"_rightBtn\" data-dojo-attach-event=\"onClick: doSlideRight\">▶</div>\n\t<div class='dijitTabListWrapper' data-dojo-attach-point='tablistWrapper'>\n\t\t<div role='tablist' data-dojo-attach-event='onkeypress:onkeypress'\n\t\t\t\tdata-dojo-attach-point='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>",
|
|
'url:dijit/layout/templates/_ScrollingTabControllerButton.html':"<div data-dojo-attach-event=\"onclick:_onClick\" class=\"dijitTabInnerDiv dijitTabContent dijitButtonContents\" data-dojo-attach-point=\"focusNode\">\n\t<img role=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" data-dojo-attach-point=\"iconNode\"/>\n\t<span data-dojo-attach-point=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n</div>"}});
|
|
define("dijit/layout/ScrollingTabController", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.add domClass.contains
|
|
"dojo/dom-geometry", // domGeometry.contentBox
|
|
"dojo/dom-style", // domStyle.style
|
|
"dojo/_base/fx", // Animation
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/on",
|
|
"dojo/query", // query
|
|
"dojo/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, on, query, has,
|
|
registry, tabControllerTemplate, buttonTemplate, TabController, layoutUtils, _WidgetsInTemplateMixin,
|
|
Menu, MenuItem, Button, _HasDropDown){
|
|
|
|
// module:
|
|
// dijit/layout/ScrollingTabController
|
|
|
|
|
|
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;
|
|
|
|
// changes to the tab button label or iconClass will have changed the width of the
|
|
// buttons, so do a resize
|
|
this.own(on(this.containerNode, "attrmodified-label, attrmodified-iconclass", lang.hitch(this, function(evt){
|
|
if(this._dim){
|
|
this.resize(this._dim);
|
|
}
|
|
})));
|
|
},
|
|
|
|
onAddChild: function(page, insertIndex){
|
|
this.inherited(arguments);
|
|
|
|
// 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 + rightTab.offsetWidth - 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/_WidgetBase*/ 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 + children[children.length-1].domNode.offsetWidth) - 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/_base/fx/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; }
|
|
});
|
|
|
|
// 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",
|
|
ownerDocument: this.ownerDocument,
|
|
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,
|
|
disabled: page.disabled,
|
|
ownerDocument: this.ownerDocument,
|
|
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;
|
|
});
|
|
|
|
},
|
|
'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\" role=\"presentation\"\n\t\t/></td></tr></tbody\n></table>\n",
|
|
'dijit/DialogUnderlay':function(){
|
|
define("dijit/DialogUnderlay", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/window", // winUtils.getBox
|
|
"./_Widget",
|
|
"./_TemplatedMixin",
|
|
"./BackgroundIframe"
|
|
], function(declare, domAttr, winUtils, _Widget, _TemplatedMixin, BackgroundIframe){
|
|
|
|
// module:
|
|
// dijit/DialogUnderlay
|
|
|
|
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
|
|
this.ownerDocumentBody.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(this.ownerDocument);
|
|
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";
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/place':function(){
|
|
define("dijit/place", [
|
|
"dojo/_base/array", // array.forEach array.map array.some
|
|
"dojo/dom-geometry", // domGeometry.position
|
|
"dojo/dom-style", // domStyle.getComputedStyle
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/_base/window", // win.body
|
|
"dojo/window", // winUtils.getBox
|
|
"./main" // dijit (defining dijit.place to match API doc)
|
|
], function(array, domGeometry, domStyle, kernel, win, winUtils, dijit){
|
|
|
|
// module:
|
|
// dijit/place
|
|
|
|
|
|
function _place(/*DomNode*/ node, choices, layoutNode, aroundNodeCoords){
|
|
// summary:
|
|
// Given a list of spots to put node, put it at the first spot where it fits,
|
|
// of if it doesn't fit anywhere then the place with the least overflow
|
|
// choices: Array
|
|
// Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} }
|
|
// Above example says to put the top-left corner of the node at (10,20)
|
|
// layoutNode: Function(node, aroundNodeCorner, nodeCorner, size)
|
|
// for things like tooltip, they are displayed differently (and have different dimensions)
|
|
// based on their orientation relative to the parent. This adjusts the popup based on orientation.
|
|
// It also passes in the available size for the popup, which is useful for tooltips to
|
|
// tell them that their width is limited to a certain amount. layoutNode() may return a value expressing
|
|
// how much the popup had to be modified to fit into the available space. This is used to determine
|
|
// what the best placement is.
|
|
// aroundNodeCoords: Object
|
|
// Size of aroundNode, ex: {w: 200, h: 50}
|
|
|
|
// get {x: 10, y: 10, w: 100, h:100} type obj representing position of
|
|
// viewport over document
|
|
var view = winUtils.getBox(node.ownerDocument);
|
|
|
|
// This won't work if the node is inside a <div style="position: relative">,
|
|
// so reattach it to win.doc.body. (Otherwise, the positioning will be wrong
|
|
// and also it might get cutoff)
|
|
if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){
|
|
win.body(node.ownerDocument).appendChild(node);
|
|
}
|
|
|
|
var best = null;
|
|
array.some(choices, function(choice){
|
|
var corner = choice.corner;
|
|
var pos = choice.pos;
|
|
var overflow = 0;
|
|
|
|
// calculate amount of space available given specified position of node
|
|
var spaceAvailable = {
|
|
w: {
|
|
'L': view.l + view.w - pos.x,
|
|
'R': pos.x - view.l,
|
|
'M': view.w
|
|
}[corner.charAt(1)],
|
|
h: {
|
|
'T': view.t + view.h - pos.y,
|
|
'B': pos.y - view.t,
|
|
'M': view.h
|
|
}[corner.charAt(0)]
|
|
};
|
|
|
|
// Clear left/right position settings set earlier so they don't interfere with calculations,
|
|
// specifically when layoutNode() (a.k.a. Tooltip.orient()) measures natural width of Tooltip
|
|
var s = node.style;
|
|
s.left = s.right = "auto";
|
|
|
|
// configure node to be displayed in given position relative to button
|
|
// (need to do this in order to get an accurate size for the node, because
|
|
// a tooltip's size changes based on position, due to triangle)
|
|
if(layoutNode){
|
|
var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords);
|
|
overflow = typeof res == "undefined" ? 0 : res;
|
|
}
|
|
|
|
// get node's size
|
|
var style = node.style;
|
|
var oldDisplay = style.display;
|
|
var oldVis = style.visibility;
|
|
if(style.display == "none"){
|
|
style.visibility = "hidden";
|
|
style.display = "";
|
|
}
|
|
var bb = domGeometry.position(node);
|
|
style.display = oldDisplay;
|
|
style.visibility = oldVis;
|
|
|
|
// coordinates and size of node with specified corner placed at pos,
|
|
// and clipped by viewport
|
|
var
|
|
startXpos = {
|
|
'L': pos.x,
|
|
'R': pos.x - bb.w,
|
|
'M': Math.max(view.l, Math.min(view.l + view.w, pos.x + (bb.w >> 1)) - bb.w) // M orientation is more flexible
|
|
}[corner.charAt(1)],
|
|
startYpos = {
|
|
'T': pos.y,
|
|
'B': pos.y - bb.h,
|
|
'M': Math.max(view.t, Math.min(view.t + view.h, pos.y + (bb.h >> 1)) - bb.h)
|
|
}[corner.charAt(0)],
|
|
startX = Math.max(view.l, startXpos),
|
|
startY = Math.max(view.t, startYpos),
|
|
endX = Math.min(view.l + view.w, startXpos + bb.w),
|
|
endY = Math.min(view.t + view.h, startYpos + bb.h),
|
|
width = endX - startX,
|
|
height = endY - startY;
|
|
|
|
overflow += (bb.w - width) + (bb.h - height);
|
|
|
|
if(best == null || overflow < best.overflow){
|
|
best = {
|
|
corner: corner,
|
|
aroundCorner: choice.aroundCorner,
|
|
x: startX,
|
|
y: startY,
|
|
w: width,
|
|
h: height,
|
|
overflow: overflow,
|
|
spaceAvailable: spaceAvailable
|
|
};
|
|
}
|
|
|
|
return !overflow;
|
|
});
|
|
|
|
// In case the best position is not the last one we checked, need to call
|
|
// layoutNode() again.
|
|
if(best.overflow && layoutNode){
|
|
layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords);
|
|
}
|
|
|
|
// And then position the node. Do this last, after the layoutNode() above
|
|
// has sized the node, due to browser quirks when the viewport is scrolled
|
|
// (specifically that a Tooltip will shrink to fit as though the window was
|
|
// scrolled to the left).
|
|
//
|
|
// In RTL mode, set style.right rather than style.left so in the common case,
|
|
// window resizes move the popup along with the aroundNode.
|
|
var l = domGeometry.isBodyLtr(node.ownerDocument),
|
|
s = node.style;
|
|
s.top = best.y + "px";
|
|
s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px";
|
|
s[l ? "right" : "left"] = "auto"; // needed for FF or else tooltip goes to far left
|
|
|
|
return best;
|
|
}
|
|
|
|
var place = {
|
|
// summary:
|
|
// Code to place a DOMNode relative to another DOMNode.
|
|
// Load using require(["dijit/place"], function(place){ ... }).
|
|
|
|
at: function(node, pos, corners, padding){
|
|
// summary:
|
|
// Positions one of the node's corners at specified position
|
|
// such that node is fully visible in viewport.
|
|
// description:
|
|
// NOTE: node is assumed to be absolutely or relatively positioned.
|
|
// node: DOMNode
|
|
// The node to position
|
|
// pos: dijit/place.__Position
|
|
// Object like {x: 10, y: 20}
|
|
// corners: String[]
|
|
// Array of Strings representing order to try corners in, like ["TR", "BL"].
|
|
// Possible values are:
|
|
//
|
|
// - "BL" - bottom left
|
|
// - "BR" - bottom right
|
|
// - "TL" - top left
|
|
// - "TR" - top right
|
|
// padding: dijit/place.__Position?
|
|
// optional param to set padding, to put some buffer around the element you want to position.
|
|
// example:
|
|
// Try to place node's top right corner at (10,20).
|
|
// If that makes node go (partially) off screen, then try placing
|
|
// bottom left corner at (10,20).
|
|
// | place(node, {x: 10, y: 20}, ["TR", "BL"])
|
|
var choices = array.map(corners, function(corner){
|
|
var c = { corner: corner, pos: {x:pos.x,y:pos.y} };
|
|
if(padding){
|
|
c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x;
|
|
c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y;
|
|
}
|
|
return c;
|
|
});
|
|
|
|
return _place(node, choices);
|
|
},
|
|
|
|
around: function(
|
|
/*DomNode*/ node,
|
|
/*DomNode|dijit/place.__Rectangle*/ anchor,
|
|
/*String[]*/ positions,
|
|
/*Boolean*/ leftToRight,
|
|
/*Function?*/ layoutNode){
|
|
|
|
// summary:
|
|
// Position node adjacent or kitty-corner to anchor
|
|
// such that it's fully visible in viewport.
|
|
// description:
|
|
// Place node such that corner of node touches a corner of
|
|
// aroundNode, and that node is fully visible.
|
|
// anchor:
|
|
// Either a DOMNode or a rectangle (object with x, y, width, height).
|
|
// positions:
|
|
// Ordered list of positions to try matching up.
|
|
//
|
|
// - before: places drop down to the left of the anchor node/widget, or to the right in the case
|
|
// of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down
|
|
// with the top of the anchor, or the bottom of the drop down with bottom of the anchor.
|
|
// - after: places drop down to the right of the anchor node/widget, or to the left in the case
|
|
// of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down
|
|
// with the top of the anchor, or the bottom of the drop down with bottom of the anchor.
|
|
// - before-centered: centers drop down to the left of the anchor node/widget, or to the right
|
|
// in the case of RTL scripts like Hebrew and Arabic
|
|
// - after-centered: centers drop down to the right of the anchor node/widget, or to the left
|
|
// in the case of RTL scripts like Hebrew and Arabic
|
|
// - above-centered: drop down is centered above anchor node
|
|
// - above: drop down goes above anchor node, left sides aligned
|
|
// - above-alt: drop down goes above anchor node, right sides aligned
|
|
// - below-centered: drop down is centered above anchor node
|
|
// - below: drop down goes below anchor node
|
|
// - below-alt: drop down goes below anchor node, right sides aligned
|
|
// layoutNode: Function(node, aroundNodeCorner, nodeCorner)
|
|
// For things like tooltip, they are displayed differently (and have different dimensions)
|
|
// based on their orientation relative to the parent. This adjusts the popup based on orientation.
|
|
// leftToRight:
|
|
// True if widget is LTR, false if widget is RTL. Affects the behavior of "above" and "below"
|
|
// positions slightly.
|
|
// example:
|
|
// | placeAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'});
|
|
// This will try to position node such that node's top-left corner is at the same position
|
|
// as the bottom left corner of the aroundNode (ie, put node below
|
|
// aroundNode, with left edges aligned). If that fails it will try to put
|
|
// the bottom-right corner of node where the top right corner of aroundNode is
|
|
// (ie, put node above aroundNode, with right edges aligned)
|
|
//
|
|
|
|
// if around is a DOMNode (or DOMNode id), convert to coordinates
|
|
var aroundNodePos = (typeof anchor == "string" || "offsetWidth" in anchor)
|
|
? domGeometry.position(anchor, true)
|
|
: anchor;
|
|
|
|
// Compute position and size of visible part of anchor (it may be partially hidden by ancestor nodes w/scrollbars)
|
|
if(anchor.parentNode){
|
|
// ignore nodes between position:relative and position:absolute
|
|
var sawPosAbsolute = domStyle.getComputedStyle(anchor).position == "absolute";
|
|
var parent = anchor.parentNode;
|
|
while(parent && parent.nodeType == 1 && parent.nodeName != "BODY"){ //ignoring the body will help performance
|
|
var parentPos = domGeometry.position(parent, true),
|
|
pcs = domStyle.getComputedStyle(parent);
|
|
if(/relative|absolute/.test(pcs.position)){
|
|
sawPosAbsolute = false;
|
|
}
|
|
if(!sawPosAbsolute && /hidden|auto|scroll/.test(pcs.overflow)){
|
|
var bottomYCoord = Math.min(aroundNodePos.y + aroundNodePos.h, parentPos.y + parentPos.h);
|
|
var rightXCoord = Math.min(aroundNodePos.x + aroundNodePos.w, parentPos.x + parentPos.w);
|
|
aroundNodePos.x = Math.max(aroundNodePos.x, parentPos.x);
|
|
aroundNodePos.y = Math.max(aroundNodePos.y, parentPos.y);
|
|
aroundNodePos.h = bottomYCoord - aroundNodePos.y;
|
|
aroundNodePos.w = rightXCoord - aroundNodePos.x;
|
|
}
|
|
if(pcs.position == "absolute"){
|
|
sawPosAbsolute = true;
|
|
}
|
|
parent = parent.parentNode;
|
|
}
|
|
}
|
|
|
|
var x = aroundNodePos.x,
|
|
y = aroundNodePos.y,
|
|
width = "w" in aroundNodePos ? aroundNodePos.w : (aroundNodePos.w = aroundNodePos.width),
|
|
height = "h" in aroundNodePos ? aroundNodePos.h : (kernel.deprecated("place.around: dijit/place.__Rectangle: { x:"+x+", y:"+y+", height:"+aroundNodePos.height+", width:"+width+" } has been deprecated. Please use { x:"+x+", y:"+y+", h:"+aroundNodePos.height+", w:"+width+" }", "", "2.0"), aroundNodePos.h = aroundNodePos.height);
|
|
|
|
// Convert positions arguments into choices argument for _place()
|
|
var choices = [];
|
|
function push(aroundCorner, corner){
|
|
choices.push({
|
|
aroundCorner: aroundCorner,
|
|
corner: corner,
|
|
pos: {
|
|
x: {
|
|
'L': x,
|
|
'R': x + width,
|
|
'M': x + (width >> 1)
|
|
}[aroundCorner.charAt(1)],
|
|
y: {
|
|
'T': y,
|
|
'B': y + height,
|
|
'M': y + (height >> 1)
|
|
}[aroundCorner.charAt(0)]
|
|
}
|
|
})
|
|
}
|
|
array.forEach(positions, function(pos){
|
|
var ltr = leftToRight;
|
|
switch(pos){
|
|
case "above-centered":
|
|
push("TM", "BM");
|
|
break;
|
|
case "below-centered":
|
|
push("BM", "TM");
|
|
break;
|
|
case "after-centered":
|
|
ltr = !ltr;
|
|
// fall through
|
|
case "before-centered":
|
|
push(ltr ? "ML" : "MR", ltr ? "MR" : "ML");
|
|
break;
|
|
case "after":
|
|
ltr = !ltr;
|
|
// fall through
|
|
case "before":
|
|
push(ltr ? "TL" : "TR", ltr ? "TR" : "TL");
|
|
push(ltr ? "BL" : "BR", ltr ? "BR" : "BL");
|
|
break;
|
|
case "below-alt":
|
|
ltr = !ltr;
|
|
// fall through
|
|
case "below":
|
|
// first try to align left borders, next try to align right borders (or reverse for RTL mode)
|
|
push(ltr ? "BL" : "BR", ltr ? "TL" : "TR");
|
|
push(ltr ? "BR" : "BL", ltr ? "TR" : "TL");
|
|
break;
|
|
case "above-alt":
|
|
ltr = !ltr;
|
|
// fall through
|
|
case "above":
|
|
// first try to align left borders, next try to align right borders (or reverse for RTL mode)
|
|
push(ltr ? "TL" : "TR", ltr ? "BL" : "BR");
|
|
push(ltr ? "TR" : "TL", ltr ? "BR" : "BL");
|
|
break;
|
|
default:
|
|
// To assist dijit/_base/place, accept arguments of type {aroundCorner: "BL", corner: "TL"}.
|
|
// Not meant to be used directly.
|
|
push(pos.aroundCorner, pos.corner);
|
|
}
|
|
});
|
|
|
|
var position = _place(node, choices, layoutNode, {w: width, h: height});
|
|
position.aroundNodePos = aroundNodePos;
|
|
|
|
return position;
|
|
}
|
|
};
|
|
|
|
/*=====
|
|
place.__Position = {
|
|
// x: Integer
|
|
// horizontal coordinate in pixels, relative to document body
|
|
// y: Integer
|
|
// vertical coordinate in pixels, relative to document body
|
|
};
|
|
place.__Rectangle = {
|
|
// x: Integer
|
|
// horizontal offset in pixels, relative to document body
|
|
// y: Integer
|
|
// vertical offset in pixels, relative to document body
|
|
// w: Integer
|
|
// width in pixels. Can also be specified as "width" for backwards-compatibility.
|
|
// h: Integer
|
|
// height in pixels. Can also be specified as "height" for backwards-compatibility.
|
|
};
|
|
=====*/
|
|
|
|
return dijit.place = place; // setting dijit.place for back-compat, remove for 2.0
|
|
});
|
|
|
|
},
|
|
'dijit/_HasDropDown':function(){
|
|
define("dijit/_HasDropDown", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/Deferred",
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/dom", // dom.isDescendant
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/dom-class", // domClass.add domClass.contains domClass.remove
|
|
"dojo/dom-geometry", // domGeometry.marginBox domGeometry.position
|
|
"dojo/dom-style", // domStyle.set
|
|
"dojo/has", // has("touch")
|
|
"dojo/keys", // keys.DOWN_ARROW keys.ENTER keys.ESCAPE
|
|
"dojo/_base/lang", // lang.hitch lang.isFunction
|
|
"dojo/on",
|
|
"dojo/window", // winUtils.getBox
|
|
"./registry", // registry.byNode()
|
|
"./focus",
|
|
"./popup",
|
|
"./_FocusMixin"
|
|
], function(declare, Deferred, event,dom, domAttr, domClass, domGeometry, domStyle, has, keys, lang, on,
|
|
winUtils, registry, focus, popup, _FocusMixin){
|
|
|
|
|
|
// module:
|
|
// dijit/_HasDropDown
|
|
|
|
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 subclass
|
|
_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 propagation, so that:
|
|
// 1. TimeTextBox etc. can focus the <input> on mousedown
|
|
// 2. dropDownButtonActive class applied by _CssStateMixin (on button depress)
|
|
// 3. user defined onMouseDown handler fires
|
|
e.preventDefault();
|
|
|
|
this._docHandler = this.connect(this.ownerDocument, "mouseup", "_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 back focus from the dropdown.
|
|
this._focusDropDownTimer = this.defer(function(){
|
|
dropDown.focus();
|
|
delete this._focusDropDownTimer;
|
|
});
|
|
}
|
|
}else{
|
|
// The drop down arrow icon probably can't receive focus, but widget itself should get focus.
|
|
// defer() needed to make it work on IE (test DateTextBox)
|
|
this.defer("focus");
|
|
}
|
|
|
|
if(has("touch")){
|
|
this._justGotMouseUp = true;
|
|
this.defer(function(){
|
|
this._justGotMouseUp = false;
|
|
});
|
|
}
|
|
},
|
|
|
|
_onDropDownClick: function(/*Event*/ e){
|
|
if(has("touch") && !this._justGotMouseUp){
|
|
// If there was no preceding mousedown/mouseup (like on android), then simulate them to
|
|
// toggle the drop down.
|
|
//
|
|
// The if(has("touch") 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 keyboard events
|
|
|
|
this.inherited(arguments);
|
|
|
|
var keyboardEventNode = this.focusNode || this.domNode;
|
|
this.own(
|
|
on(this._buttonNode, "mousedown", lang.hitch(this, "_onDropDownMouseDown")),
|
|
on(this._buttonNode, "click", lang.hitch(this, "_onDropDownClick")),
|
|
on(keyboardEventNode, "keydown", lang.hitch(this, "_onKey")),
|
|
on(keyboardEventNode, "keyup", lang.hitch(this, "_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.keyCode == keys.ESCAPE){
|
|
this.closeDropDown();
|
|
event.stop(e);
|
|
}else if(!this._opened &&
|
|
(e.keyCode == keys.DOWN_ARROW ||
|
|
( (e.keyCode == keys.ENTER || e.keyCode == keys.SPACE) &&
|
|
//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){
|
|
this.defer(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(this.ownerDocument),
|
|
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. If no scroll bar needed,
|
|
// use overflow:visible rather than overflow:hidden so off-by-one errors don't hide drop down border.
|
|
var mb = domGeometry.getMarginSize(ddNode);
|
|
var overHeight = (maxHeight && mb.h > maxHeight);
|
|
domStyle.set(ddNode, {
|
|
overflowX: "visible",
|
|
overflowY: overHeight ? "auto" : "visible"
|
|
});
|
|
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._set("_opened", false); // use set() because _CssStateMixin is watching
|
|
}
|
|
});
|
|
domAttr.set(this._popupStateNode, "popupActive", "true");
|
|
domClass.add(this._popupStateNode, "dijitHasDropDownOpen");
|
|
this._set("_opened", true); // use set() because _CssStateMixin is watching
|
|
this.domNode.setAttribute("aria-expanded", "true");
|
|
|
|
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._focusDropDownTimer){
|
|
this._focusDropDownTimer.remove();
|
|
delete this._focusDropDownTimer;
|
|
}
|
|
if(this._opened){
|
|
this.domNode.setAttribute("aria-expanded", "false");
|
|
if(focus){ this.focus(); }
|
|
popup.close(this.dropDown);
|
|
this._opened = false;
|
|
}
|
|
}
|
|
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/tree/TreeStoreModel':function(){
|
|
define("dijit/tree/TreeStoreModel", [
|
|
"dojo/_base/array", // array.filter array.forEach array.indexOf array.some
|
|
"dojo/aspect", // aspect.after
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/lang" // lang.hitch
|
|
], function(array, aspect, declare, lang){
|
|
|
|
// module:
|
|
// dijit/tree/TreeStoreModel
|
|
|
|
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/api/Read
|
|
// 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.TreeStoreModel: 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("dijit.tree.TreeStoreModel: root query returned " + items.length +
|
|
" items, but must return exactly one");
|
|
}
|
|
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(/* dijit/tree/dndSource.__Item */ args, /*dojo/data/api/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/_MenuBase':function(){
|
|
define("dijit/_MenuBase", [
|
|
"dojo/_base/array", // array.indexOf
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.isDescendant domClass.replace
|
|
"dojo/dom-attr",
|
|
"dojo/dom-class", // domClass.replace
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/mouse", // mouse.enter, mouse.leave
|
|
"dojo/on",
|
|
"dojo/window",
|
|
"./a11yclick",
|
|
"./popup",
|
|
"./registry",
|
|
"./_Widget",
|
|
"./_KeyNavContainer",
|
|
"./_TemplatedMixin"
|
|
], function(array, declare, dom, domAttr, domClass, lang, mouse, on, winUtils,
|
|
a11yclick, pm, registry, _Widget, _KeyNavContainer, _TemplatedMixin){
|
|
|
|
|
|
// module:
|
|
// dijit/_MenuBase
|
|
|
|
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,
|
|
|
|
// autoFocus: Boolean
|
|
// A toggle to control whether or not a Menu gets focused when opened as a drop down from a MenuBar
|
|
// or DropDownButton/ComboButton. Note though that it always get focused when opened via the keyboard.
|
|
autoFocus: false,
|
|
|
|
childSelector: function(/*DOMNode*/ node){
|
|
// summary:
|
|
// Selector (passed to on.selector()) used to identify MenuItem child widgets, but exclude inert children
|
|
// like MenuSeparator. If subclass overrides to a string (ex: "> *"), the subclass must require dojo/query.
|
|
// tags:
|
|
// protected
|
|
|
|
var widget = registry.byNode(node);
|
|
return node.parentNode == this.containerNode && widget && widget.focus;
|
|
},
|
|
|
|
postCreate: function(){
|
|
var self = this,
|
|
matches = typeof this.childSelector == "string" ? this.childSelector : lang.hitch(this, "childSelector");
|
|
this.own(
|
|
on(this.containerNode, on.selector(matches, mouse.enter), function(){
|
|
self.onItemHover(registry.byNode(this));
|
|
}),
|
|
on(this.containerNode, on.selector(matches, mouse.leave), function(){
|
|
self.onItemUnhover(registry.byNode(this));
|
|
}),
|
|
on(this.containerNode, on.selector(matches, a11yclick), function(evt){
|
|
self.onItemClick(registry.byNode(this), evt);
|
|
evt.stopPropagation();
|
|
evt.preventDefault();
|
|
})
|
|
);
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
onExecute: function(){
|
|
// summary:
|
|
// Attach point for notification about when a menu item has been executed.
|
|
// This is an internal mechanism used for Menus to signal to their parent to
|
|
// close them, because they are about to execute the onClick handler. In
|
|
// general developers should not attach to or override this method.
|
|
// tags:
|
|
// protected
|
|
},
|
|
|
|
onCancel: function(/*Boolean*/ /*===== closeAll =====*/){
|
|
// summary:
|
|
// Attach point for notification about when the user cancels the current menu
|
|
// This is an internal mechanism used for Menus to signal to their parent to
|
|
// close them. In general developers should not attach to or override this method.
|
|
// tags:
|
|
// protected
|
|
},
|
|
|
|
_moveToPopup: function(/*Event*/ evt){
|
|
// summary:
|
|
// This handles the right arrow key (left arrow key on RTL systems),
|
|
// which will either open a submenu, or move to the next item in the
|
|
// ancestor MenuBar
|
|
// tags:
|
|
// private
|
|
|
|
if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
|
|
this.onItemClick(this.focusedChild, 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 = this.defer("_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;
|
|
|
|
item._set("hovering", true);
|
|
},
|
|
|
|
_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 = this.defer(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; }
|
|
|
|
item._set("hovering", false);
|
|
},
|
|
|
|
_stopPopupTimer: function(){
|
|
// summary:
|
|
// Cancels the popup timer because the user has stop hovering
|
|
// on the MenuItem, etc.
|
|
// tags:
|
|
// private
|
|
if(this.hover_timer){
|
|
this.hover_timer = this.hover_timer.remove();
|
|
}
|
|
},
|
|
|
|
_stopPendingCloseTimer: function(/*dijit/_WidgetBase*/ popup){
|
|
// summary:
|
|
// Cancels the pending-close timer because the close has been preempted
|
|
// tags:
|
|
// private
|
|
if(popup._pendingClose_timer){
|
|
popup._pendingClose_timer = popup._pendingClose_timer.remove();
|
|
}
|
|
},
|
|
|
|
_stopFocusTimer: function(){
|
|
// summary:
|
|
// Cancels the pending-focus timer because the menu was closed before focus occured
|
|
// tags:
|
|
// private
|
|
if(this._focus_timer){
|
|
this._focus_timer = this._focus_timer.remove();
|
|
}
|
|
},
|
|
|
|
_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/_WidgetBase*/ 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(evt.type == "keypress");
|
|
}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 ? item._onClick(evt) : item.onClick(evt);
|
|
}
|
|
},
|
|
|
|
_openPopup: function(/*Boolean*/ focus){
|
|
// summary:
|
|
// Open the popup to the side of/underneath the current menu item, and optionally focus first 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){
|
|
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(focus && popup.focus){
|
|
// If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar), then focus the popup.
|
|
// If the cursor happens to collide with the popup, it will generate an onmouseover event
|
|
// even though the mouse wasn't moved. Use defer() to call popup.focus so that
|
|
// our focus() call overrides the onmouseover event, rather than vice-versa. (#8742)
|
|
popup._focus_timer = this.defer(lang.hitch(popup, function(){
|
|
this._focus_timer = null;
|
|
this.focus();
|
|
}));
|
|
}
|
|
},
|
|
|
|
_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.onItemUnhover(this.focusedChild);
|
|
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.onItemUnhover(this._hoveredChild); // 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/focus':function(){
|
|
define("dijit/focus", [
|
|
"dojo/aspect",
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // domAttr.get dom.isDescendant
|
|
"dojo/dom-attr", // domAttr.get dom.isDescendant
|
|
"dojo/dom-construct", // connect to domConstruct.empty, domConstruct.destroy
|
|
"dojo/Evented",
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/on",
|
|
"dojo/ready",
|
|
"dojo/sniff", // has("ie")
|
|
"dojo/Stateful",
|
|
"dojo/_base/unload", // unload.addOnWindowUnload
|
|
"dojo/_base/window", // win.body
|
|
"dojo/window", // winUtils.get
|
|
"./a11y", // a11y.isTabNavigable
|
|
"./registry", // registry.byId
|
|
"./main" // to set dijit.focus
|
|
], function(aspect, declare, dom, domAttr, domConstruct, Evented, lang, on, ready, has, Stateful, unload, win, winUtils,
|
|
a11y, registry, dijit){
|
|
|
|
// module:
|
|
// dijit/focus
|
|
|
|
var FocusManager = declare([Stateful, Evented], {
|
|
// summary:
|
|
// Tracks the currently focused node, and which widgets are currently "active".
|
|
// Access via require(["dijit/focus"], function(focus){ ... }).
|
|
//
|
|
// A widget is considered active if it or a descendant widget has focus,
|
|
// or if a non-focusable node of this widget or a descendant was recently clicked.
|
|
//
|
|
// Call focus.watch("curNode", callback) to track the current focused DOMNode,
|
|
// or focus.watch("activeStack", callback) to track the currently focused stack of widgets.
|
|
//
|
|
// Call focus.on("widget-blur", func) or focus.on("widget-focus", ...) to monitor when
|
|
// when widgets become active/inactive
|
|
//
|
|
// Finally, focus(node) will focus a node, suppressing errors if the node doesn't exist.
|
|
|
|
// curNode: DomNode
|
|
// Currently focused item on screen
|
|
curNode: null,
|
|
|
|
// activeStack: dijit/_WidgetBase[]
|
|
// List of currently active widgets (focused widget and it's ancestors)
|
|
activeStack: [],
|
|
|
|
constructor: function(){
|
|
// Don't leave curNode/prevNode pointing to bogus elements
|
|
var check = lang.hitch(this, function(node){
|
|
if(dom.isDescendant(this.curNode, node)){
|
|
this.set("curNode", null);
|
|
}
|
|
if(dom.isDescendant(this.prevNode, node)){
|
|
this.set("prevNode", null);
|
|
}
|
|
});
|
|
aspect.before(domConstruct, "empty", check);
|
|
aspect.before(domConstruct, "destroy", check);
|
|
},
|
|
|
|
registerIframe: function(/*DomNode*/ iframe){
|
|
// summary:
|
|
// Registers listeners on the specified iframe so that any click
|
|
// or focus event on that iframe (or anything in it) is reported
|
|
// as a focus/click event on the `<iframe>` itself.
|
|
// description:
|
|
// Currently only used by editor.
|
|
// returns:
|
|
// Handle with remove() method to deregister.
|
|
return this.registerWin(iframe.contentWindow, iframe);
|
|
},
|
|
|
|
registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){
|
|
// summary:
|
|
// Registers listeners on the specified window (either the main
|
|
// window or an iframe's window) to detect when the user has clicked somewhere
|
|
// or focused somewhere.
|
|
// description:
|
|
// Users should call registerIframe() instead of this method.
|
|
// targetWindow:
|
|
// If specified this is the window associated with the iframe,
|
|
// i.e. iframe.contentWindow.
|
|
// effectiveNode:
|
|
// If specified, report any focus events inside targetWindow as
|
|
// an event on effectiveNode, rather than on evt.target.
|
|
// returns:
|
|
// Handle with remove() method to deregister.
|
|
|
|
// TODO: make this function private in 2.0; Editor/users should call registerIframe(),
|
|
|
|
var _this = this;
|
|
var mousedownListener = function(evt){
|
|
_this._justMouseDowned = true;
|
|
setTimeout(function(){ _this._justMouseDowned = false; }, 0);
|
|
|
|
// workaround weird IE bug where the click is on an orphaned node
|
|
// (first time clicking a Select/DropDownButton inside a TooltipDialog)
|
|
if(has("ie") && evt && evt.srcElement && evt.srcElement.parentNode == null){
|
|
return;
|
|
}
|
|
|
|
_this._onTouchNode(effectiveNode || evt.target || evt.srcElement, "mouse");
|
|
};
|
|
|
|
// Listen for blur and focus events on targetWindow's document.
|
|
// Using attachEvent()/addEventListener() rather than on() to try to catch mouseDown events even
|
|
// if other code calls evt.stopPropagation(). But rethink for 2.0 since that doesn't work for attachEvent(),
|
|
// which watches events at the bubbling phase rather than capturing phase, like addEventListener(..., false).
|
|
// Connect to <html> (rather than document) on IE to avoid memory leaks, but document on other browsers because
|
|
// (at least for FF) the focus event doesn't fire on <html> or <body>.
|
|
var doc = has("ie") ? targetWindow.document.documentElement : targetWindow.document;
|
|
if(doc){
|
|
if(has("ie")){
|
|
targetWindow.document.body.attachEvent('onmousedown', mousedownListener);
|
|
var focusinListener = function(evt){
|
|
// IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1,
|
|
// ignore those events
|
|
var tag = evt.srcElement.tagName.toLowerCase();
|
|
if(tag == "#document" || tag == "body"){ return; }
|
|
|
|
// Previous code called _onTouchNode() for any activate event on a non-focusable node. Can
|
|
// probably just ignore such an event as it will be handled by onmousedown handler above, but
|
|
// leaving the code for now.
|
|
if(a11y.isTabNavigable(evt.srcElement)){
|
|
_this._onFocusNode(effectiveNode || evt.srcElement);
|
|
}else{
|
|
_this._onTouchNode(effectiveNode || evt.srcElement);
|
|
}
|
|
};
|
|
doc.attachEvent('onfocusin', focusinListener);
|
|
var focusoutListener = function(evt){
|
|
_this._onBlurNode(effectiveNode || evt.srcElement);
|
|
};
|
|
doc.attachEvent('onfocusout', focusoutListener);
|
|
|
|
return {
|
|
remove: function(){
|
|
targetWindow.document.detachEvent('onmousedown', mousedownListener);
|
|
doc.detachEvent('onfocusin', focusinListener);
|
|
doc.detachEvent('onfocusout', focusoutListener);
|
|
doc = null; // prevent memory leak (apparent circular reference via closure)
|
|
}
|
|
};
|
|
}else{
|
|
doc.body.addEventListener('mousedown', mousedownListener, true);
|
|
doc.body.addEventListener('touchstart', mousedownListener, true);
|
|
var focusListener = function(evt){
|
|
_this._onFocusNode(effectiveNode || evt.target);
|
|
};
|
|
doc.addEventListener('focus', focusListener, true);
|
|
var blurListener = function(evt){
|
|
_this._onBlurNode(effectiveNode || evt.target);
|
|
};
|
|
doc.addEventListener('blur', blurListener, true);
|
|
|
|
return {
|
|
remove: function(){
|
|
doc.body.removeEventListener('mousedown', mousedownListener, true);
|
|
doc.body.removeEventListener('touchstart', mousedownListener, true);
|
|
doc.removeEventListener('focus', focusListener, true);
|
|
doc.removeEventListener('blur', blurListener, true);
|
|
doc = null; // prevent memory leak (apparent circular reference via closure)
|
|
}
|
|
};
|
|
}
|
|
}
|
|
},
|
|
|
|
_onBlurNode: function(/*DomNode*/ node){
|
|
// summary:
|
|
// Called when focus leaves a node.
|
|
// Usually ignored, _unless_ it *isn't* followed by touching another node,
|
|
// which indicates that we tabbed off the last field on the page,
|
|
// in which case every widget is marked inactive
|
|
|
|
// If the blur event isn't followed by a focus event, it means the user clicked on something unfocusable,
|
|
// so clear focus.
|
|
if(this._clearFocusTimer){
|
|
clearTimeout(this._clearFocusTimer);
|
|
}
|
|
this._clearFocusTimer = setTimeout(lang.hitch(this, function(){
|
|
this.set("prevNode", this.curNode);
|
|
this.set("curNode", null);
|
|
}), 0);
|
|
|
|
if(this._justMouseDowned){
|
|
// the mouse down caused a new widget to be marked as active; this blur event
|
|
// is coming late, so ignore it.
|
|
return;
|
|
}
|
|
|
|
// If the blur event isn't followed by a focus or touch event then mark all widgets as inactive.
|
|
if(this._clearActiveWidgetsTimer){
|
|
clearTimeout(this._clearActiveWidgetsTimer);
|
|
}
|
|
this._clearActiveWidgetsTimer = setTimeout(lang.hitch(this, function(){
|
|
delete this._clearActiveWidgetsTimer;
|
|
this._setStack([]);
|
|
}), 0);
|
|
},
|
|
|
|
_onTouchNode: function(/*DomNode*/ node, /*String*/ by){
|
|
// summary:
|
|
// Callback when node is focused or mouse-downed
|
|
// node:
|
|
// The node that was touched.
|
|
// by:
|
|
// "mouse" if the focus/touch was caused by a mouse down event
|
|
|
|
// ignore the recent blurNode event
|
|
if(this._clearActiveWidgetsTimer){
|
|
clearTimeout(this._clearActiveWidgetsTimer);
|
|
delete this._clearActiveWidgetsTimer;
|
|
}
|
|
|
|
// compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem)
|
|
var newStack=[];
|
|
try{
|
|
while(node){
|
|
var popupParent = domAttr.get(node, "dijitPopupParent");
|
|
if(popupParent){
|
|
node=registry.byId(popupParent).domNode;
|
|
}else if(node.tagName && node.tagName.toLowerCase() == "body"){
|
|
// is this the root of the document or just the root of an iframe?
|
|
if(node === win.body()){
|
|
// node is the root of the main document
|
|
break;
|
|
}
|
|
// otherwise, find the iframe this node refers to (can't access it via parentNode,
|
|
// need to do this trick instead). window.frameElement is supported in IE/FF/Webkit
|
|
node=winUtils.get(node.ownerDocument).frameElement;
|
|
}else{
|
|
// if this node is the root node of a widget, then add widget id to stack,
|
|
// except ignore clicks on disabled widgets (actually focusing a disabled widget still works,
|
|
// to support MenuItem)
|
|
var id = node.getAttribute && node.getAttribute("widgetId"),
|
|
widget = id && registry.byId(id);
|
|
if(widget && !(by == "mouse" && widget.get("disabled"))){
|
|
newStack.unshift(id);
|
|
}
|
|
node=node.parentNode;
|
|
}
|
|
}
|
|
}catch(e){ /* squelch */ }
|
|
|
|
this._setStack(newStack, by);
|
|
},
|
|
|
|
_onFocusNode: function(/*DomNode*/ node){
|
|
// summary:
|
|
// Callback when node is focused
|
|
|
|
if(!node){
|
|
return;
|
|
}
|
|
|
|
if(node.nodeType == 9){
|
|
// Ignore focus events on the document itself. This is here so that
|
|
// (for example) clicking the up/down arrows of a spinner
|
|
// (which don't get focus) won't cause that widget to blur. (FF issue)
|
|
return;
|
|
}
|
|
|
|
// There was probably a blur event right before this event, but since we have a new focus, don't
|
|
// do anything with the blur
|
|
if(this._clearFocusTimer){
|
|
clearTimeout(this._clearFocusTimer);
|
|
delete this._clearFocusTimer;
|
|
}
|
|
|
|
this._onTouchNode(node);
|
|
|
|
if(node == this.curNode){ return; }
|
|
this.set("prevNode", this.curNode);
|
|
this.set("curNode", node);
|
|
},
|
|
|
|
_setStack: function(/*String[]*/ newStack, /*String*/ by){
|
|
// summary:
|
|
// The stack of active widgets has changed. Send out appropriate events and records new stack.
|
|
// newStack:
|
|
// array of widget id's, starting from the top (outermost) widget
|
|
// by:
|
|
// "mouse" if the focus/touch was caused by a mouse down event
|
|
|
|
var oldStack = this.activeStack;
|
|
this.set("activeStack", newStack);
|
|
|
|
// compare old stack to new stack to see how many elements they have in common
|
|
for(var nCommon=0; nCommon<Math.min(oldStack.length, newStack.length); nCommon++){
|
|
if(oldStack[nCommon] != newStack[nCommon]){
|
|
break;
|
|
}
|
|
}
|
|
|
|
var widget;
|
|
// for all elements that have gone out of focus, set focused=false
|
|
for(var i=oldStack.length-1; i>=nCommon; i--){
|
|
widget = registry.byId(oldStack[i]);
|
|
if(widget){
|
|
widget._hasBeenBlurred = true; // TODO: used by form widgets, should be moved there
|
|
widget.set("focused", false);
|
|
if(widget._focusManager == this){
|
|
widget._onBlur(by);
|
|
}
|
|
this.emit("widget-blur", widget, by);
|
|
}
|
|
}
|
|
|
|
// for all element that have come into focus, set focused=true
|
|
for(i=nCommon; i<newStack.length; i++){
|
|
widget = registry.byId(newStack[i]);
|
|
if(widget){
|
|
widget.set("focused", true);
|
|
if(widget._focusManager == this){
|
|
widget._onFocus(by);
|
|
}
|
|
this.emit("widget-focus", widget, by);
|
|
}
|
|
}
|
|
},
|
|
|
|
focus: function(node){
|
|
// summary:
|
|
// Focus the specified node, suppressing errors if they occur
|
|
if(node){
|
|
try{ node.focus(); }catch(e){/*quiet*/}
|
|
}
|
|
}
|
|
});
|
|
|
|
var singleton = new FocusManager();
|
|
|
|
// register top window and all the iframes it contains
|
|
ready(function(){
|
|
var handle = singleton.registerWin(winUtils.get(win.doc));
|
|
if(has("ie")){
|
|
unload.addOnWindowUnload(function(){
|
|
if(handle){ // because this gets called twice when doh.robot is running
|
|
handle.remove();
|
|
handle = null;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Setup dijit.focus as a pointer to the singleton but also (for backwards compatibility)
|
|
// as a function to set focus. Remove for 2.0.
|
|
dijit.focus = function(node){
|
|
singleton.focus(node); // indirection here allows dijit/_base/focus.js to override behavior
|
|
};
|
|
for(var attr in singleton){
|
|
if(!/^_/.test(attr)){
|
|
dijit.focus[attr] = typeof singleton[attr] == "function" ? lang.hitch(singleton, attr) : singleton[attr];
|
|
}
|
|
}
|
|
singleton.watch(function(attr, oldVal, newVal){
|
|
dijit.focus[attr] = newVal;
|
|
});
|
|
|
|
return singleton;
|
|
});
|
|
|
|
},
|
|
'dojo/i18n':function(){
|
|
define("dojo/i18n", ["./_base/kernel", "require", "./has", "./_base/array", "./_base/config", "./_base/lang", "./_base/xhr", "./json", "module"],
|
|
function(dojo, require, has, array, config, lang, xhr, json, module){
|
|
|
|
// module:
|
|
// dojo/i18n
|
|
|
|
has.add("dojo-preload-i18n-Api",
|
|
// if true, define the preload localizations machinery
|
|
1
|
|
);
|
|
|
|
1 || has.add("dojo-v1x-i18n-Api",
|
|
// if true, define the v1.x i18n functions
|
|
1
|
|
);
|
|
|
|
var
|
|
thisModule = dojo.i18n =
|
|
{
|
|
// summary:
|
|
// This module implements the dojo/i18n! plugin and the v1.6- i18n API
|
|
// description:
|
|
// We choose to include our own plugin to leverage functionality already contained in dojo
|
|
// and thereby reduce the size of the plugin compared to various loader implementations. Also, this
|
|
// allows foreign AMD loaders to be used without their plugins.
|
|
},
|
|
|
|
nlsRe =
|
|
// regexp for reconstructing the master bundle name from parts of the regexp match
|
|
// nlsRe.exec("foo/bar/baz/nls/en-ca/foo") gives:
|
|
// ["foo/bar/baz/nls/en-ca/foo", "foo/bar/baz/nls/", "/", "/", "en-ca", "foo"]
|
|
// nlsRe.exec("foo/bar/baz/nls/foo") gives:
|
|
// ["foo/bar/baz/nls/foo", "foo/bar/baz/nls/", "/", "/", "foo", ""]
|
|
// so, if match[5] is blank, it means this is the top bundle definition.
|
|
// courtesy of http://requirejs.org
|
|
/(^.*(^|\/)nls)(\/|$)([^\/]*)\/?([^\/]*)/,
|
|
|
|
getAvailableLocales = function(
|
|
root,
|
|
locale,
|
|
bundlePath,
|
|
bundleName
|
|
){
|
|
// summary:
|
|
// return a vector of module ids containing all available locales with respect to the target locale
|
|
// For example, assuming:
|
|
//
|
|
// - the root bundle indicates specific bundles for "fr" and "fr-ca",
|
|
// - bundlePath is "myPackage/nls"
|
|
// - bundleName is "myBundle"
|
|
//
|
|
// Then a locale argument of "fr-ca" would return
|
|
//
|
|
// ["myPackage/nls/myBundle", "myPackage/nls/fr/myBundle", "myPackage/nls/fr-ca/myBundle"]
|
|
//
|
|
// Notice that bundles are returned least-specific to most-specific, starting with the root.
|
|
//
|
|
// If root===false indicates we're working with a pre-AMD i18n bundle that doesn't tell about the available locales;
|
|
// therefore, assume everything is available and get 404 errors that indicate a particular localization is not available
|
|
|
|
for(var result = [bundlePath + bundleName], localeParts = locale.split("-"), current = "", i = 0; i<localeParts.length; i++){
|
|
current += (current ? "-" : "") + localeParts[i];
|
|
if(!root || root[current]){
|
|
result.push(bundlePath + current + "/" + bundleName);
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
|
|
cache = {},
|
|
|
|
getBundleName = function(moduleName, bundleName, locale){
|
|
locale = locale ? locale.toLowerCase() : dojo.locale;
|
|
moduleName = moduleName.replace(/\./g, "/");
|
|
bundleName = bundleName.replace(/\./g, "/");
|
|
return (/root/i.test(locale)) ?
|
|
(moduleName + "/nls/" + bundleName) :
|
|
(moduleName + "/nls/" + locale + "/" + bundleName);
|
|
},
|
|
|
|
getL10nName = dojo.getL10nName = function(moduleName, bundleName, locale){
|
|
return moduleName = module.id + "!" + getBundleName(moduleName, bundleName, locale);
|
|
},
|
|
|
|
doLoad = function(require, bundlePathAndName, bundlePath, bundleName, locale, load){
|
|
// summary:
|
|
// get the root bundle which instructs which other bundles are required to construct the localized bundle
|
|
require([bundlePathAndName], function(root){
|
|
var current = lang.clone(root.root),
|
|
availableLocales = getAvailableLocales(!root._v1x && root, locale, bundlePath, bundleName);
|
|
require(availableLocales, function(){
|
|
for (var i = 1; i<availableLocales.length; i++){
|
|
current = lang.mixin(lang.clone(current), arguments[i]);
|
|
}
|
|
// target may not have been resolve (e.g., maybe only "fr" exists when "fr-ca" was requested)
|
|
var target = bundlePathAndName + "/" + locale;
|
|
cache[target] = current;
|
|
load();
|
|
});
|
|
});
|
|
},
|
|
|
|
normalize = function(id, toAbsMid){
|
|
// summary:
|
|
// id may be relative.
|
|
// preload has form `*preload*<path>/nls/<module>*<flattened locales>` and
|
|
// therefore never looks like a relative
|
|
return /^\./.test(id) ? toAbsMid(id) : id;
|
|
},
|
|
|
|
getLocalesToLoad = function(targetLocale){
|
|
var list = config.extraLocale || [];
|
|
list = lang.isArray(list) ? list : [list];
|
|
list.push(targetLocale);
|
|
return list;
|
|
},
|
|
|
|
load = function(id, require, load){
|
|
// summary:
|
|
// id is in one of the following formats
|
|
//
|
|
// 1. <path>/nls/<bundle>
|
|
// => load the bundle, localized to config.locale; load all bundles localized to
|
|
// config.extraLocale (if any); return the loaded bundle localized to config.locale.
|
|
//
|
|
// 2. <path>/nls/<locale>/<bundle>
|
|
// => load then return the bundle localized to <locale>
|
|
//
|
|
// 3. *preload*<path>/nls/<module>*<JSON array of available locales>
|
|
// => for config.locale and all config.extraLocale, load all bundles found
|
|
// in the best-matching bundle rollup. A value of 1 is returned, which
|
|
// is meaningless other than to say the plugin is executing the requested
|
|
// preloads
|
|
//
|
|
// In cases 1 and 2, <path> is always normalized to an absolute module id upon entry; see
|
|
// normalize. In case 3, it <path> is assumed to be absolute; this is arranged by the builder.
|
|
//
|
|
// To load a bundle means to insert the bundle into the plugin's cache and publish the bundle
|
|
// value to the loader. Given <path>, <bundle>, and a particular <locale>, the cache key
|
|
//
|
|
// <path>/nls/<bundle>/<locale>
|
|
//
|
|
// will hold the value. Similarly, then plugin will publish this value to the loader by
|
|
//
|
|
// define("<path>/nls/<bundle>/<locale>", <bundle-value>);
|
|
//
|
|
// Given this algorithm, other machinery can provide fast load paths be preplacing
|
|
// values in the plugin's cache, which is public. When a load is demanded the
|
|
// cache is inspected before starting any loading. Explicitly placing values in the plugin
|
|
// cache is an advanced/experimental feature that should not be needed; use at your own risk.
|
|
//
|
|
// For the normal AMD algorithm, the root bundle is loaded first, which instructs the
|
|
// plugin what additional localized bundles are required for a particular locale. These
|
|
// additional locales are loaded and a mix of the root and each progressively-specific
|
|
// locale is returned. For example:
|
|
//
|
|
// 1. The client demands "dojo/i18n!some/path/nls/someBundle
|
|
//
|
|
// 2. The loader demands load(some/path/nls/someBundle)
|
|
//
|
|
// 3. This plugin require's "some/path/nls/someBundle", which is the root bundle.
|
|
//
|
|
// 4. Assuming config.locale is "ab-cd-ef" and the root bundle indicates that localizations
|
|
// are available for "ab" and "ab-cd-ef" (note the missing "ab-cd", then the plugin
|
|
// requires "some/path/nls/ab/someBundle" and "some/path/nls/ab-cd-ef/someBundle"
|
|
//
|
|
// 5. Upon receiving all required bundles, the plugin constructs the value of the bundle
|
|
// ab-cd-ef as...
|
|
//
|
|
// mixin(mixin(mixin({}, require("some/path/nls/someBundle"),
|
|
// require("some/path/nls/ab/someBundle")),
|
|
// require("some/path/nls/ab-cd-ef/someBundle"));
|
|
//
|
|
// This value is inserted into the cache and published to the loader at the
|
|
// key/module-id some/path/nls/someBundle/ab-cd-ef.
|
|
//
|
|
// The special preload signature (case 3) instructs the plugin to stop servicing all normal requests
|
|
// (further preload requests will be serviced) until all ongoing preloading has completed.
|
|
//
|
|
// The preload signature instructs the plugin that a special rollup module is available that contains
|
|
// one or more flattened, localized bundles. The JSON array of available locales indicates which locales
|
|
// are available. Here is an example:
|
|
//
|
|
// *preload*some/path/nls/someModule*["root", "ab", "ab-cd-ef"]
|
|
//
|
|
// This indicates the following rollup modules are available:
|
|
//
|
|
// some/path/nls/someModule_ROOT
|
|
// some/path/nls/someModule_ab
|
|
// some/path/nls/someModule_ab-cd-ef
|
|
//
|
|
// Each of these modules is a normal AMD module that contains one or more flattened bundles in a hash.
|
|
// For example, assume someModule contained the bundles some/bundle/path/someBundle and
|
|
// some/bundle/path/someOtherBundle, then some/path/nls/someModule_ab would be expressed as follows:
|
|
//
|
|
// define({
|
|
// some/bundle/path/someBundle:<value of someBundle, flattened with respect to locale ab>,
|
|
// some/bundle/path/someOtherBundle:<value of someOtherBundle, flattened with respect to locale ab>,
|
|
// });
|
|
//
|
|
// E.g., given this design, preloading for locale=="ab" can execute the following algorithm:
|
|
//
|
|
// require(["some/path/nls/someModule_ab"], function(rollup){
|
|
// for(var p in rollup){
|
|
// var id = p + "/ab",
|
|
// cache[id] = rollup[p];
|
|
// define(id, rollup[p]);
|
|
// }
|
|
// });
|
|
//
|
|
// Similarly, if "ab-cd" is requested, the algorithm can determine that "ab" is the best available and
|
|
// load accordingly.
|
|
//
|
|
// The builder will write such rollups for every layer if a non-empty localeList profile property is
|
|
// provided. Further, the builder will include the following cache entry in the cache associated with
|
|
// any layer.
|
|
//
|
|
// "*now":function(r){r(['dojo/i18n!*preload*<path>/nls/<module>*<JSON array of available locales>']);}
|
|
//
|
|
// The *now special cache module instructs the loader to apply the provided function to context-require
|
|
// with respect to the particular layer being defined. This causes the plugin to hold all normal service
|
|
// requests until all preloading is complete.
|
|
//
|
|
// Notice that this algorithm is rarely better than the standard AMD load algorithm. Consider the normal case
|
|
// where the target locale has a single segment and a layer depends on a single bundle:
|
|
//
|
|
// Without Preloads:
|
|
//
|
|
// 1. Layer loads root bundle.
|
|
// 2. bundle is demanded; plugin loads single localized bundle.
|
|
//
|
|
// With Preloads:
|
|
//
|
|
// 1. Layer causes preloading of target bundle.
|
|
// 2. bundle is demanded; service is delayed until preloading complete; bundle is returned.
|
|
//
|
|
// In each case a single transaction is required to load the target bundle. In cases where multiple bundles
|
|
// are required and/or the locale has multiple segments, preloads still requires a single transaction whereas
|
|
// the normal path requires an additional transaction for each additional bundle/locale-segment. However all
|
|
// of these additional transactions can be done concurrently. Owing to this analysis, the entire preloading
|
|
// algorithm can be discard during a build by setting the has feature dojo-preload-i18n-Api to false.
|
|
|
|
if(has("dojo-preload-i18n-Api")){
|
|
var split = id.split("*"),
|
|
preloadDemand = split[1] == "preload";
|
|
if(preloadDemand){
|
|
if(!cache[id]){
|
|
// use cache[id] to prevent multiple preloads of the same preload; this shouldn't happen, but
|
|
// who knows what over-aggressive human optimizers may attempt
|
|
cache[id] = 1;
|
|
preloadL10n(split[2], json.parse(split[3]), 1, require);
|
|
}
|
|
// don't stall the loader!
|
|
load(1);
|
|
}
|
|
if(preloadDemand || waitForPreloads(id, require, load)){
|
|
return;
|
|
}
|
|
}
|
|
|
|
var match = nlsRe.exec(id),
|
|
bundlePath = match[1] + "/",
|
|
bundleName = match[5] || match[4],
|
|
bundlePathAndName = bundlePath + bundleName,
|
|
localeSpecified = (match[5] && match[4]),
|
|
targetLocale = localeSpecified || dojo.locale,
|
|
loadTarget = bundlePathAndName + "/" + targetLocale,
|
|
loadList = localeSpecified ? [targetLocale] : getLocalesToLoad(targetLocale),
|
|
remaining = loadList.length,
|
|
finish = function(){
|
|
if(!--remaining){
|
|
load(lang.delegate(cache[loadTarget]));
|
|
}
|
|
};
|
|
array.forEach(loadList, function(locale){
|
|
var target = bundlePathAndName + "/" + locale;
|
|
if(has("dojo-preload-i18n-Api")){
|
|
checkForLegacyModules(target);
|
|
}
|
|
if(!cache[target]){
|
|
doLoad(require, bundlePathAndName, bundlePath, bundleName, locale, finish);
|
|
}else{
|
|
finish();
|
|
}
|
|
});
|
|
};
|
|
|
|
if(has("dojo-unit-tests")){
|
|
var unitTests = thisModule.unitTests = [];
|
|
}
|
|
|
|
if(has("dojo-preload-i18n-Api") || 1 ){
|
|
var normalizeLocale = thisModule.normalizeLocale = function(locale){
|
|
var result = locale ? locale.toLowerCase() : dojo.locale;
|
|
return result == "root" ? "ROOT" : result;
|
|
},
|
|
|
|
isXd = function(mid, contextRequire){
|
|
return ( 1 && 1 ) ?
|
|
contextRequire.isXdUrl(require.toUrl(mid + ".js")) :
|
|
true;
|
|
},
|
|
|
|
preloading = 0,
|
|
|
|
preloadWaitQueue = [],
|
|
|
|
preloadL10n = thisModule._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated, /*boolean?*/ guaranteedAmdFormat, /*function?*/ contextRequire){
|
|
// summary:
|
|
// Load available flattened resource bundles associated with a particular module for dojo/locale and all dojo/config.extraLocale (if any)
|
|
// description:
|
|
// Only called by built layer files. The entire locale hierarchy is loaded. For example,
|
|
// if locale=="ab-cd", then ROOT, "ab", and "ab-cd" are loaded. This is different than v1.6-
|
|
// in that the v1.6- would only load ab-cd...which was *always* flattened.
|
|
//
|
|
// If guaranteedAmdFormat is true, then the module can be loaded with require thereby circumventing the detection algorithm
|
|
// and the extra possible extra transaction.
|
|
|
|
// If this function is called from legacy code, then guaranteedAmdFormat and contextRequire will be undefined. Since the function
|
|
// needs a require in order to resolve module ids, fall back to the context-require associated with this dojo/i18n module, which
|
|
// itself may have been mapped.
|
|
contextRequire = contextRequire || require;
|
|
|
|
function doRequire(mid, callback){
|
|
if(isXd(mid, contextRequire) || guaranteedAmdFormat){
|
|
contextRequire([mid], callback);
|
|
}else{
|
|
syncRequire([mid], callback, contextRequire);
|
|
}
|
|
}
|
|
|
|
function forEachLocale(locale, func){
|
|
// given locale= "ab-cd-ef", calls func on "ab-cd-ef", "ab-cd", "ab", "ROOT"; stops calling the first time func returns truthy
|
|
var parts = locale.split("-");
|
|
while(parts.length){
|
|
if(func(parts.join("-"))){
|
|
return;
|
|
}
|
|
parts.pop();
|
|
}
|
|
func("ROOT");
|
|
}
|
|
|
|
function preload(locale){
|
|
locale = normalizeLocale(locale);
|
|
forEachLocale(locale, function(loc){
|
|
if(array.indexOf(localesGenerated, loc)>=0){
|
|
var mid = bundlePrefix.replace(/\./g, "/")+"_"+loc;
|
|
preloading++;
|
|
doRequire(mid, function(rollup){
|
|
for(var p in rollup){
|
|
cache[require.toAbsMid(p) + "/" + loc] = rollup[p];
|
|
}
|
|
--preloading;
|
|
while(!preloading && preloadWaitQueue.length){
|
|
load.apply(null, preloadWaitQueue.shift());
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
preload();
|
|
array.forEach(dojo.config.extraLocale, preload);
|
|
},
|
|
|
|
waitForPreloads = function(id, require, load){
|
|
if(preloading){
|
|
preloadWaitQueue.push([id, require, load]);
|
|
}
|
|
return preloading;
|
|
},
|
|
|
|
checkForLegacyModules = function()
|
|
{};
|
|
}
|
|
|
|
if( 1 ){
|
|
// this code path assumes the dojo loader and won't work with a standard AMD loader
|
|
var amdValue = {},
|
|
evalBundle =
|
|
// use the function ctor to keep the minifiers away (also come close to global scope, but this is secondary)
|
|
new Function(
|
|
"__bundle", // the bundle to evalutate
|
|
"__checkForLegacyModules", // a function that checks if __bundle defined __mid in the global space
|
|
"__mid", // the mid that __bundle is intended to define
|
|
"__amdValue",
|
|
|
|
// returns one of:
|
|
// 1 => the bundle was an AMD bundle
|
|
// a legacy bundle object that is the value of __mid
|
|
// instance of Error => could not figure out how to evaluate bundle
|
|
|
|
// used to detect when __bundle calls define
|
|
"var define = function(mid, factory){define.called = 1; __amdValue.result = factory || mid;},"
|
|
+ " require = function(){define.called = 1;};"
|
|
|
|
+ "try{"
|
|
+ "define.called = 0;"
|
|
+ "eval(__bundle);"
|
|
+ "if(define.called==1)"
|
|
// bundle called define; therefore signal it's an AMD bundle
|
|
+ "return __amdValue;"
|
|
|
|
+ "if((__checkForLegacyModules = __checkForLegacyModules(__mid)))"
|
|
// bundle was probably a v1.6- built NLS flattened NLS bundle that defined __mid in the global space
|
|
+ "return __checkForLegacyModules;"
|
|
|
|
+ "}catch(e){}"
|
|
// evaulating the bundle was *neither* an AMD *nor* a legacy flattened bundle
|
|
// either way, re-eval *after* surrounding with parentheses
|
|
|
|
+ "try{"
|
|
+ "return eval('('+__bundle+')');"
|
|
+ "}catch(e){"
|
|
+ "return e;"
|
|
+ "}"
|
|
),
|
|
|
|
syncRequire = function(deps, callback, require){
|
|
var results = [];
|
|
array.forEach(deps, function(mid){
|
|
var url = require.toUrl(mid + ".js");
|
|
|
|
function load(text){
|
|
var result = evalBundle(text, checkForLegacyModules, mid, amdValue);
|
|
if(result===amdValue){
|
|
// the bundle was an AMD module; re-inject it through the normal AMD path
|
|
// we gotta do this since it could be an anonymous module and simply evaluating
|
|
// the text here won't provide the loader with the context to know what
|
|
// module is being defined()'d. With browser caching, this should be free; further
|
|
// this entire code path can be circumvented by using the AMD format to begin with
|
|
results.push(cache[url] = amdValue.result);
|
|
}else{
|
|
if(result instanceof Error){
|
|
console.error("failed to evaluate i18n bundle; url=" + url, result);
|
|
result = {};
|
|
}
|
|
// nls/<locale>/<bundle-name> indicates not the root.
|
|
results.push(cache[url] = (/nls\/[^\/]+\/[^\/]+$/.test(url) ? result : {root:result, _v1x:1}));
|
|
}
|
|
}
|
|
|
|
if(cache[url]){
|
|
results.push(cache[url]);
|
|
}else{
|
|
var bundle = require.syncLoadNls(mid);
|
|
// don't need to check for legacy since syncLoadNls returns a module if the module
|
|
// (1) was already loaded, or (2) was in the cache. In case 1, if syncRequire is called
|
|
// from getLocalization --> load, then load will have called checkForLegacyModules() before
|
|
// calling syncRequire; if syncRequire is called from preloadLocalizations, then we
|
|
// don't care about checkForLegacyModules() because that will be done when a particular
|
|
// bundle is actually demanded. In case 2, checkForLegacyModules() is never relevant
|
|
// because cached modules are always v1.7+ built modules.
|
|
if(bundle){
|
|
results.push(bundle);
|
|
}else{
|
|
if(!xhr){
|
|
try{
|
|
require.getText(url, true, load);
|
|
}catch(e){
|
|
results.push(cache[url] = {});
|
|
}
|
|
}else{
|
|
xhr.get({
|
|
url:url,
|
|
sync:true,
|
|
load:load,
|
|
error:function(){
|
|
results.push(cache[url] = {});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
callback && callback.apply(null, results);
|
|
};
|
|
|
|
checkForLegacyModules = function(target){
|
|
// legacy code may have already loaded [e.g] the raw bundle x/y/z at x.y.z; when true, push into the cache
|
|
for(var result, names = target.split("/"), object = dojo.global[names[0]], i = 1; object && i<names.length-1; object = object[names[i++]]){}
|
|
if(object){
|
|
result = object[names[i]];
|
|
if(!result){
|
|
// fallback for incorrect bundle build of 1.6
|
|
result = object[names[i].replace(/-/g,"_")];
|
|
}
|
|
if(result){
|
|
cache[target] = result;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
thisModule.getLocalization = function(moduleName, bundleName, locale){
|
|
var result,
|
|
l10nName = getBundleName(moduleName, bundleName, locale);
|
|
load(
|
|
l10nName,
|
|
|
|
// isXd() and syncRequire() need a context-require in order to resolve the mid with respect to a reference module.
|
|
// Since this legacy function does not have the concept of a reference module, resolve with respect to this
|
|
// dojo/i18n module, which, itself may have been mapped.
|
|
(!isXd(l10nName, require) ? function(deps, callback){ syncRequire(deps, callback, require); } : require),
|
|
|
|
function(result_){ result = result_; }
|
|
);
|
|
return result;
|
|
};
|
|
|
|
if(has("dojo-unit-tests")){
|
|
unitTests.push(function(doh){
|
|
doh.register("tests.i18n.unit", function(t){
|
|
var check;
|
|
|
|
check = evalBundle("{prop:1}", checkForLegacyModules, "nonsense", amdValue);
|
|
t.is({prop:1}, check); t.is(undefined, check[1]);
|
|
|
|
check = evalBundle("({prop:1})", checkForLegacyModules, "nonsense", amdValue);
|
|
t.is({prop:1}, check); t.is(undefined, check[1]);
|
|
|
|
check = evalBundle("{'prop-x':1}", checkForLegacyModules, "nonsense", amdValue);
|
|
t.is({'prop-x':1}, check); t.is(undefined, check[1]);
|
|
|
|
check = evalBundle("({'prop-x':1})", checkForLegacyModules, "nonsense", amdValue);
|
|
t.is({'prop-x':1}, check); t.is(undefined, check[1]);
|
|
|
|
check = evalBundle("define({'prop-x':1})", checkForLegacyModules, "nonsense", amdValue);
|
|
t.is(amdValue, check); t.is({'prop-x':1}, amdValue.result);
|
|
|
|
check = evalBundle("define('some/module', {'prop-x':1})", checkForLegacyModules, "nonsense", amdValue);
|
|
t.is(amdValue, check); t.is({'prop-x':1}, amdValue.result);
|
|
|
|
check = evalBundle("this is total nonsense and should throw an error", checkForLegacyModules, "nonsense", amdValue);
|
|
t.is(check instanceof Error, true);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
return lang.mixin(thisModule, {
|
|
dynamic:true,
|
|
normalize:normalize,
|
|
load:load,
|
|
cache:cache
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/hccss':function(){
|
|
define("dijit/hccss", ["dojo/dom-class", "dojo/hccss", "dojo/ready", "dojo/_base/window"], function(domClass, has, ready, win){
|
|
|
|
// module:
|
|
// dijit/hccss
|
|
|
|
/*=====
|
|
return function(){
|
|
// summary:
|
|
// Test if computer is in high contrast mode, and sets `dijit_a11y` flag on `<body>` if it is.
|
|
// Deprecated, use ``dojo/hccss`` instead.
|
|
};
|
|
=====*/
|
|
|
|
// Priority is 90 to run ahead of parser priority of 100. For 2.0, remove the ready() call and instead
|
|
// change this module to depend on dojo/domReady!
|
|
ready(90, function(){
|
|
if(has("highcontrast")){
|
|
domClass.add(win.body(), "dijit_a11y");
|
|
}
|
|
});
|
|
|
|
return has;
|
|
});
|
|
|
|
},
|
|
'dijit/tree/ForestStoreModel':function(){
|
|
define("dijit/tree/ForestStoreModel", [
|
|
"dojo/_base/array", // array.indexOf array.some
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/kernel", // global
|
|
"dojo/_base/lang", // lang.hitch
|
|
"./TreeStoreModel"
|
|
], function(array, declare, kernel, lang, TreeStoreModel){
|
|
|
|
// module:
|
|
// dijit/tree/ForestStoreModel
|
|
|
|
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 || kernel.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(/* dijit/tree/dndSource.__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(/* dijit/tree/dndSource.__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/api/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/form/_ComboBoxMenuMixin':function(){
|
|
define("dijit/form/_ComboBoxMenuMixin", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/i18n", // i18n.getLocalization
|
|
"dojo/i18n!./nls/ComboBox"
|
|
], function(array, declare, domAttr, i18n){
|
|
|
|
// module:
|
|
// dijit/form/_ComboBoxMenuMixin
|
|
|
|
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(
|
|
menuitem.ownerDocument.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 || ""));
|
|
|
|
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
|
|
|
|
this.items = results;
|
|
|
|
// 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);
|
|
menuitem.setAttribute("item", i); // index to this.items; use indirection to avoid mem leak
|
|
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");
|
|
},
|
|
|
|
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.selected;
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/_SearchMixin':function(){
|
|
define("dijit/form/_SearchMixin", [
|
|
"dojo/data/util/filter", // patternToRegExp
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/keys", // keys
|
|
"dojo/_base/lang", // lang.clone lang.hitch
|
|
"dojo/query", // query
|
|
"dojo/sniff", // has("ie")
|
|
"dojo/string", // string.substitute
|
|
"dojo/when",
|
|
"../registry" // registry.byId
|
|
], function(filter, declare, event, keys, lang, query, has, string, when, registry){
|
|
|
|
// module:
|
|
// dijit/form/_SearchMixin
|
|
|
|
|
|
return declare("dijit.form._SearchMixin", null, {
|
|
// summary:
|
|
// A mixin that implements the base functionality to search a store based upon user-entered text such as
|
|
// with `dijit/form/ComboBox` or `dijit/form/FilteringSelect`
|
|
// tags:
|
|
// protected
|
|
|
|
// pageSize: Integer
|
|
// Argument to data provider.
|
|
// Specifies maximum number of search results to return per query
|
|
pageSize: Infinity,
|
|
|
|
// store: [const] dojo/store/api/Store
|
|
// Reference to data provider object used by this ComboBox.
|
|
// The store must accept an object hash of properties for its query. See `query` and `queryExpr` for details.
|
|
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.
|
|
// ComboBox overwrites any reference to the `searchAttr` and sets it to the `queryExpr` with the user's input substituted.
|
|
query: {},
|
|
|
|
// searchDelay: Integer
|
|
// Delay in milliseconds between when user types something and we start
|
|
// searching based on that value
|
|
searchDelay: 200,
|
|
|
|
// searchAttr: String
|
|
// Search for items in the data store where this attribute (in the item)
|
|
// matches what the user typed
|
|
searchAttr: "name",
|
|
|
|
// queryExpr: String
|
|
// This specifies what query is sent to the data store,
|
|
// based on what the user has typed. Changing this expression will modify
|
|
// whether the results are only exact matches, a "starting with" match,
|
|
// etc.
|
|
// 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 query should ignore case when matching possible items
|
|
ignoreCase: true,
|
|
|
|
_abortQuery: function(){
|
|
// stop in-progress query
|
|
if(this.searchTimer){
|
|
this.searchTimer = this.searchTimer.remove();
|
|
}
|
|
if(this._queryDeferHandle){
|
|
this._queryDeferHandle = this._queryDeferHandle.remove();
|
|
}
|
|
if(this._fetchHandle){
|
|
if(this._fetchHandle.abort){
|
|
this._cancelingQuery = true;
|
|
this._fetchHandle.abort();
|
|
this._cancelingQuery = false;
|
|
}
|
|
if(this._fetchHandle.cancel){
|
|
this._cancelingQuery = true;
|
|
this._fetchHandle.cancel();
|
|
this._cancelingQuery = false;
|
|
}
|
|
this._fetchHandle = null;
|
|
}
|
|
},
|
|
|
|
_processInput: function(/*Event*/ evt){
|
|
// summary:
|
|
// Handles input (keyboard/paste) events
|
|
if(this.disabled || this.readOnly){ return; }
|
|
var key = evt.charOrCode;
|
|
|
|
// except for cutting/pasting case - ctrl + x/v
|
|
if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == keys.SHIFT){
|
|
return; // throw out weird key combinations and spurious events
|
|
}
|
|
|
|
var doSearch = false;
|
|
this._prev_key_backspace = false;
|
|
|
|
switch(key){
|
|
case keys.DELETE:
|
|
case keys.BACKSPACE:
|
|
this._prev_key_backspace = true;
|
|
this._maskValidSubsetError = true;
|
|
doSearch = true;
|
|
break;
|
|
|
|
default:
|
|
// Non char keys (F1-F12 etc..) shouldn't start a search..
|
|
// 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
|
|
if(!this.store){
|
|
this.onSearch();
|
|
}else{
|
|
this.searchTimer = this.defer("_startSearchFromInput", 1);
|
|
}
|
|
}
|
|
},
|
|
|
|
onSearch: function(/*===== results, query, options =====*/){
|
|
// summary:
|
|
// Callback when a search completes.
|
|
//
|
|
// results: Object
|
|
// An array of items from the originating _SearchMixin's store.
|
|
//
|
|
// query: Object
|
|
// A copy of the originating _SearchMixin's query property.
|
|
//
|
|
// options: Object
|
|
// The additional parameters sent to the originating _SearchMixin's store, including: start, count, queryOptions.
|
|
//
|
|
// tags:
|
|
// callback
|
|
},
|
|
|
|
_startSearchFromInput: function(){
|
|
this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1"));
|
|
},
|
|
|
|
_startSearch: function(/*String*/ text){
|
|
// summary:
|
|
// Starts a search for elements matching text (text=="" means to return all items),
|
|
// and calls onSearch(...) when the search completes, to display the results.
|
|
|
|
this._abortQuery();
|
|
var
|
|
_this = this,
|
|
// Setup parameters to be passed to store.query().
|
|
// Create a new query to prevent accidentally querying for a hidden
|
|
// value from FilteringSelect's keyField
|
|
query = lang.clone(this.query), // #5970
|
|
options = {
|
|
start: 0,
|
|
count: this.pageSize,
|
|
queryOptions: { // remove for 2.0
|
|
ignoreCase: this.ignoreCase,
|
|
deep: true
|
|
}
|
|
},
|
|
qs = string.substitute(this.queryExpr, [text]),
|
|
q,
|
|
startQuery = function(){
|
|
var resPromise = _this._fetchHandle = _this.store.query(query, options);
|
|
if(_this.disabled || _this.readOnly || (q !== _this._lastQuery)){
|
|
return;
|
|
} // avoid getting unwanted notify
|
|
when(resPromise, function(res){
|
|
_this._fetchHandle = null;
|
|
if(!_this.disabled && !_this.readOnly && (q === _this._lastQuery)){ // avoid getting unwanted notify
|
|
when(resPromise.total, function(total){
|
|
res.total = total;
|
|
var pageSize = _this.pageSize;
|
|
if(isNaN(pageSize) || pageSize > res.total){ pageSize = res.total; }
|
|
// Setup method to fetching the next page of results
|
|
res.nextPage = function(direction){
|
|
// tell callback the direction of the paging so the screen
|
|
// reader knows which menu option to shout
|
|
options.direction = direction = direction !== false;
|
|
options.count = pageSize;
|
|
if(direction){
|
|
options.start += res.length;
|
|
if(options.start >= res.total){
|
|
options.count = 0;
|
|
}
|
|
}else{
|
|
options.start -= pageSize;
|
|
if(options.start < 0){
|
|
options.count = Math.max(pageSize + options.start, 0);
|
|
options.start = 0;
|
|
}
|
|
}
|
|
if(options.count <= 0){
|
|
res.length = 0;
|
|
_this.onSearch(res, query, options);
|
|
}else{
|
|
startQuery();
|
|
}
|
|
};
|
|
_this.onSearch(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());
|
|
}
|
|
});
|
|
};
|
|
|
|
lang.mixin(options, this.fetchProperties);
|
|
|
|
// Generate query
|
|
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; };
|
|
}
|
|
|
|
// 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._lastQuery = query[this.searchAttr] = q;
|
|
this._queryDeferHandle = this.defer(startQuery, this.searchDelay);
|
|
},
|
|
|
|
//////////// INITIALIZATION METHODS ///////////////////////////////////////
|
|
|
|
constructor: function(){
|
|
this.query={};
|
|
this.fetchProperties={};
|
|
},
|
|
|
|
postMixInProperties: function(){
|
|
if(!this.store){
|
|
var list = this.list;
|
|
if(list){
|
|
this.store = registry.byId(list);
|
|
}
|
|
}
|
|
this.inherited(arguments);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/parser':function(){
|
|
define(
|
|
"dojo/parser", ["require", "./_base/kernel", "./_base/lang", "./_base/array", "./_base/config", "./_base/html", "./_base/window",
|
|
"./_base/url", "./_base/json", "./aspect", "./date/stamp", "./Deferred", "./has", "./query", "./on", "./ready"],
|
|
function(require, dojo, dlang, darray, config, dhtml, dwindow, _Url, djson, aspect, dates, Deferred, has, query, don, ready){
|
|
|
|
// module:
|
|
// dojo/parser
|
|
|
|
new Date("X"); // workaround for #11279, new Date("") == NaN
|
|
|
|
|
|
// Widgets like BorderContainer add properties to _Widget via dojo.extend().
|
|
// If BorderContainer is loaded after _Widget's parameter list has been cached,
|
|
// we need to refresh that parameter list (for _Widget and all widgets that extend _Widget).
|
|
var extendCnt = 0;
|
|
aspect.after(dlang, "extend", function(){
|
|
extendCnt++;
|
|
}, true);
|
|
|
|
function getNameMap(ctor){
|
|
// summary:
|
|
// Returns map from lowercase name to attribute name in class, ex: {onclick: "onClick"}
|
|
var map = ctor._nameCaseMap, proto = ctor.prototype;
|
|
|
|
// Create the map if it's undefined.
|
|
// Refresh the map if a superclass was possibly extended with new methods since the map was created.
|
|
if(!map || map._extendCnt < extendCnt){
|
|
map = ctor._nameCaseMap = {};
|
|
for(var name in proto){
|
|
if(name.charAt(0) === "_"){ continue; } // skip internal properties
|
|
map[name.toLowerCase()] = name;
|
|
}
|
|
map._extendCnt = extendCnt;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
// Map from widget name or list of widget names(ex: "dijit/form/Button,acme/MyMixin") to a constructor.
|
|
var _ctorMap = {};
|
|
|
|
function getCtor(/*String[]*/ types){
|
|
// summary:
|
|
// Retrieves a constructor. If the types array contains more than one class/MID then the
|
|
// subsequent classes will be mixed into the first class and a unique constructor will be
|
|
// returned for that array.
|
|
|
|
var ts = types.join();
|
|
if(!_ctorMap[ts]){
|
|
var mixins = [];
|
|
for(var i = 0, l = types.length; i < l; i++){
|
|
var t = types[i];
|
|
// TODO: Consider swapping getObject and require in the future
|
|
mixins[mixins.length] = (_ctorMap[t] = _ctorMap[t] || (dlang.getObject(t) || (~t.indexOf('/') && require(t))));
|
|
}
|
|
var ctor = mixins.shift();
|
|
_ctorMap[ts] = mixins.length ? (ctor.createSubclass ? ctor.createSubclass(mixins) : ctor.extend.apply(ctor, mixins)) : ctor;
|
|
}
|
|
|
|
return _ctorMap[ts];
|
|
}
|
|
|
|
var parser = {
|
|
// summary:
|
|
// The Dom/Widget parsing package
|
|
|
|
_clearCache: function(){
|
|
// summary:
|
|
// Clear cached data. Used mainly for benchmarking.
|
|
extendCnt++;
|
|
_ctorMap = {};
|
|
},
|
|
|
|
_functionFromScript: function(script, attrData){
|
|
// summary:
|
|
// Convert a `<script type="dojo/method" args="a, b, c"> ... </script>`
|
|
// into a function
|
|
// script: DOMNode
|
|
// The `<script>` DOMNode
|
|
// attrData: String
|
|
// For HTML5 compliance, searches for attrData + "args" (typically
|
|
// "data-dojo-args") instead of "args"
|
|
var preamble = "",
|
|
suffix = "",
|
|
argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args")),
|
|
withStr = script.getAttribute("with");
|
|
|
|
// Convert any arguments supplied in script tag into an array to be passed to the
|
|
var fnArgs = (argsStr || "").split(/\s*,\s*/);
|
|
|
|
if(withStr && withStr.length){
|
|
darray.forEach(withStr.split(/\s*,\s*/), function(part){
|
|
preamble += "with("+part+"){";
|
|
suffix += "}";
|
|
});
|
|
}
|
|
|
|
return new Function(fnArgs, preamble + script.innerHTML + suffix);
|
|
},
|
|
|
|
instantiate: function(nodes, mixin, options){
|
|
// summary:
|
|
// Takes array of nodes, and turns them into class instances and
|
|
// potentially calls a startup method to allow them to connect with
|
|
// any children.
|
|
// nodes: Array
|
|
// Array of DOM nodes
|
|
// mixin: Object?
|
|
// An object that will be mixed in with each node in the array.
|
|
// Values in the mixin will override values in the node, if they
|
|
// exist.
|
|
// options: Object?
|
|
// An object used to hold kwArgs for instantiation.
|
|
// See parse.options argument for details.
|
|
|
|
mixin = mixin || {};
|
|
options = options || {};
|
|
|
|
var dojoType = (options.scope || dojo._scopeName) + "Type", // typically "dojoType"
|
|
attrData = "data-" + (options.scope || dojo._scopeName) + "-",// typically "data-dojo-"
|
|
dataDojoType = attrData + "type", // typically "data-dojo-type"
|
|
dataDojoMixins = attrData + "mixins"; // typically "data-dojo-mixins"
|
|
|
|
var list = [];
|
|
darray.forEach(nodes, function(node){
|
|
var type = dojoType in mixin ? mixin[dojoType] : node.getAttribute(dataDojoType) || node.getAttribute(dojoType);
|
|
if(type){
|
|
var mixinsValue = node.getAttribute(dataDojoMixins),
|
|
types = mixinsValue ? [type].concat(mixinsValue.split(/\s*,\s*/)) : [type];
|
|
|
|
list.push({
|
|
node: node,
|
|
types: types
|
|
});
|
|
}
|
|
});
|
|
|
|
// Instantiate the nodes and return the objects
|
|
return this._instantiate(list, mixin, options);
|
|
},
|
|
|
|
_instantiate: function(nodes, mixin, options){
|
|
// summary:
|
|
// Takes array of objects representing nodes, and turns them into class instances and
|
|
// potentially calls a startup method to allow them to connect with
|
|
// any children.
|
|
// nodes: Array
|
|
// Array of objects like
|
|
// | {
|
|
// | ctor: Function (may be null)
|
|
// | types: ["dijit/form/Button", "acme/MyMixin"] (used if ctor not specified)
|
|
// | node: DOMNode,
|
|
// | scripts: [ ... ], // array of <script type="dojo/..."> children of node
|
|
// | inherited: { ... } // settings inherited from ancestors like dir, theme, etc.
|
|
// | }
|
|
// mixin: Object
|
|
// An object that will be mixed in with each node in the array.
|
|
// Values in the mixin will override values in the node, if they
|
|
// exist.
|
|
// options: Object
|
|
// An options object used to hold kwArgs for instantiation.
|
|
// See parse.options argument for details.
|
|
|
|
// Call widget constructors
|
|
var thelist = darray.map(nodes, function(obj){
|
|
var ctor = obj.ctor || getCtor(obj.types);
|
|
// If we still haven't resolved a ctor, it is fatal now
|
|
if(!ctor){
|
|
throw new Error("Unable to resolve constructor for: '" + obj.types.join() + "'");
|
|
}
|
|
return this.construct(ctor, obj.node, mixin, options, obj.scripts, obj.inherited);
|
|
}, this);
|
|
|
|
// Call startup on each top level instance if it makes sense (as for
|
|
// widgets). Parent widgets will recursively call startup on their
|
|
// (non-top level) children
|
|
if(!mixin._started && !options.noStart){
|
|
darray.forEach(thelist, function(instance){
|
|
if(typeof instance.startup === "function" && !instance._started){
|
|
instance.startup();
|
|
}
|
|
});
|
|
}
|
|
|
|
return thelist;
|
|
},
|
|
|
|
construct: function(ctor, node, mixin, options, scripts, inherited){
|
|
// summary:
|
|
// Calls new ctor(params, node), where params is the hash of parameters specified on the node,
|
|
// excluding data-dojo-type and data-dojo-mixins. Does not call startup(). Returns the widget.
|
|
// ctor: Function
|
|
// Widget constructor.
|
|
// node: DOMNode
|
|
// This node will be replaced/attached to by the widget. It also specifies the arguments to pass to ctor.
|
|
// mixin: Object?
|
|
// Attributes in this object will be passed as parameters to ctor,
|
|
// overriding attributes specified on the node.
|
|
// options: Object?
|
|
// An options object used to hold kwArgs for instantiation. See parse.options argument for details.
|
|
// scripts: DomNode[]?
|
|
// Array of `<script type="dojo/*">` DOMNodes. If not specified, will search for `<script>` tags inside node.
|
|
// inherited: Object?
|
|
// Settings from dir=rtl or lang=... on a node above this node. Overrides options.inherited.
|
|
|
|
var proto = ctor && ctor.prototype;
|
|
options = options || {};
|
|
|
|
// Setup hash to hold parameter settings for this widget. Start with the parameter
|
|
// settings inherited from ancestors ("dir" and "lang").
|
|
// Inherited setting may later be overridden by explicit settings on node itself.
|
|
var params = {};
|
|
|
|
if(options.defaults){
|
|
// settings for the document itself (or whatever subtree is being parsed)
|
|
dlang.mixin(params, options.defaults);
|
|
}
|
|
if(inherited){
|
|
// settings from dir=rtl or lang=... on a node above this node
|
|
dlang.mixin(params, inherited);
|
|
}
|
|
|
|
// Get list of attributes explicitly listed in the markup
|
|
var attributes;
|
|
if(has("dom-attributes-explicit")){
|
|
// Standard path to get list of user specified attributes
|
|
attributes = node.attributes;
|
|
}else if(has("dom-attributes-specified-flag")){
|
|
// Special processing needed for IE8, to skip a few faux values in attributes[]
|
|
attributes = darray.filter(node.attributes, function(a){ return a.specified;});
|
|
}else{
|
|
// Special path for IE6-7, avoid (sometimes >100) bogus entries in node.attributes
|
|
var clone = /^input$|^img$/i.test(node.nodeName) ? node : node.cloneNode(false),
|
|
attrs = clone.outerHTML.replace(/=[^\s"']+|="[^"]*"|='[^']*'/g, "").replace(/^\s*<[a-zA-Z0-9]*\s*/, "").replace(/\s*>.*$/, "");
|
|
|
|
attributes = darray.map(attrs.split(/\s+/), function(name){
|
|
var lcName = name.toLowerCase();
|
|
return {
|
|
name: name,
|
|
// getAttribute() doesn't work for button.value, returns innerHTML of button.
|
|
// but getAttributeNode().value doesn't work for the form.encType or li.value
|
|
value: (node.nodeName == "LI" && name == "value") || lcName == "enctype" ?
|
|
node.getAttribute(lcName) : node.getAttributeNode(lcName).value
|
|
};
|
|
});
|
|
}
|
|
|
|
// Hash to convert scoped attribute name (ex: data-dojo17-params) to something friendly (ex: data-dojo-params)
|
|
// TODO: remove scope for 2.0
|
|
var scope = options.scope || dojo._scopeName,
|
|
attrData = "data-" + scope + "-", // typically "data-dojo-"
|
|
hash = {};
|
|
if(scope !== "dojo"){
|
|
hash[attrData + "props"] = "data-dojo-props";
|
|
hash[attrData + "type"] = "data-dojo-type";
|
|
hash[attrData + "mixins"] = "data-dojo-mixins";
|
|
hash[scope + "type"] = "dojoType";
|
|
hash[attrData + "id"] = "data-dojo-id";
|
|
}
|
|
|
|
// Read in attributes and process them, including data-dojo-props, data-dojo-type,
|
|
// dojoAttachPoint, etc., as well as normal foo=bar attributes.
|
|
var i=0, item, funcAttrs=[], jsname, extra;
|
|
while(item = attributes[i++]){
|
|
var name = item.name,
|
|
lcName = name.toLowerCase(),
|
|
value = item.value;
|
|
|
|
switch(hash[lcName] || lcName){
|
|
// Already processed, just ignore
|
|
case "data-dojo-type":
|
|
case "dojotype":
|
|
case "data-dojo-mixins":
|
|
break;
|
|
|
|
// Data-dojo-props. Save for later to make sure it overrides direct foo=bar settings
|
|
case "data-dojo-props":
|
|
extra = value;
|
|
break;
|
|
|
|
// data-dojo-id or jsId. TODO: drop jsId in 2.0
|
|
case "data-dojo-id":
|
|
case "jsid":
|
|
jsname = value;
|
|
break;
|
|
|
|
// For the benefit of _Templated
|
|
case "data-dojo-attach-point":
|
|
case "dojoattachpoint":
|
|
params.dojoAttachPoint = value;
|
|
break;
|
|
case "data-dojo-attach-event":
|
|
case "dojoattachevent":
|
|
params.dojoAttachEvent = value;
|
|
break;
|
|
|
|
// Special parameter handling needed for IE
|
|
case "class":
|
|
params["class"] = node.className;
|
|
break;
|
|
case "style":
|
|
params["style"] = node.style && node.style.cssText;
|
|
break;
|
|
default:
|
|
// Normal attribute, ex: value="123"
|
|
|
|
// Find attribute in widget corresponding to specified name.
|
|
// May involve case conversion, ex: onclick --> onClick
|
|
if(!(name in proto)){
|
|
var map = getNameMap(ctor);
|
|
name = map[lcName] || name;
|
|
}
|
|
|
|
// Set params[name] to value, doing type conversion
|
|
if(name in proto){
|
|
switch(typeof proto[name]){
|
|
case "string":
|
|
params[name] = value;
|
|
break;
|
|
case "number":
|
|
params[name] = value.length ? Number(value) : NaN;
|
|
break;
|
|
case "boolean":
|
|
// for checked/disabled value might be "" or "checked". interpret as true.
|
|
params[name] = value.toLowerCase() != "false";
|
|
break;
|
|
case "function":
|
|
if(value === "" || value.search(/[^\w\.]+/i) != -1){
|
|
// The user has specified some text for a function like "return x+5"
|
|
params[name] = new Function(value);
|
|
}else{
|
|
// The user has specified the name of a global function like "myOnClick"
|
|
// or a single word function "return"
|
|
params[name] = dlang.getObject(value, false) || new Function(value);
|
|
}
|
|
funcAttrs.push(name); // prevent "double connect", see #15026
|
|
break;
|
|
default:
|
|
var pVal = proto[name];
|
|
params[name] =
|
|
(pVal && "length" in pVal) ? (value ? value.split(/\s*,\s*/) : []) : // array
|
|
(pVal instanceof Date) ?
|
|
(value == "" ? new Date("") : // the NaN of dates
|
|
value == "now" ? new Date() : // current date
|
|
dates.fromISOString(value)) :
|
|
(pVal instanceof _Url) ? (dojo.baseUrl + value) :
|
|
djson.fromJson(value);
|
|
}
|
|
}else{
|
|
params[name] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove function attributes from DOMNode to prevent "double connect" problem, see #15026.
|
|
// Do this as a separate loop since attributes[] is often a live collection (depends on the browser though).
|
|
for(var j=0; j<funcAttrs.length; j++){
|
|
var lcfname = funcAttrs[j].toLowerCase();
|
|
node.removeAttribute(lcfname);
|
|
node[lcfname] = null;
|
|
}
|
|
|
|
// Mix things found in data-dojo-props into the params, overriding any direct settings
|
|
if(extra){
|
|
try{
|
|
extra = djson.fromJson.call(options.propsThis, "{" + extra + "}");
|
|
dlang.mixin(params, extra);
|
|
}catch(e){
|
|
// give the user a pointer to their invalid parameters. FIXME: can we kill this in production?
|
|
throw new Error(e.toString() + " in data-dojo-props='" + extra + "'");
|
|
}
|
|
}
|
|
|
|
// Any parameters specified in "mixin" override everything else.
|
|
dlang.mixin(params, mixin);
|
|
|
|
// Get <script> nodes associated with this widget, if they weren't specified explicitly
|
|
if(!scripts){
|
|
scripts = (ctor && (ctor._noScript || proto._noScript) ? [] : query("> script[type^='dojo/']", node));
|
|
}
|
|
|
|
// Process <script type="dojo/*"> script tags
|
|
// <script type="dojo/method" event="foo"> tags are added to params, and passed to
|
|
// the widget on instantiation.
|
|
// <script type="dojo/method"> tags (with no event) are executed after instantiation
|
|
// <script type="dojo/connect" data-dojo-event="foo"> tags are dojo.connected after instantiation
|
|
// <script type="dojo/watch" data-dojo-prop="foo"> tags are dojo.watch after instantiation
|
|
// <script type="dojo/on" data-dojo-event="foo"> tags are dojo.on after instantiation
|
|
// note: dojo/* script tags cannot exist in self closing widgets, like <input />
|
|
var aspects = [], // aspects to connect after instantiation
|
|
calls = [], // functions to call after instantiation
|
|
watches = [], // functions to watch after instantiation
|
|
ons = []; // functions to on after instantiation
|
|
|
|
if(scripts){
|
|
for(i=0; i<scripts.length; i++){
|
|
var script = scripts[i];
|
|
node.removeChild(script);
|
|
// FIXME: drop event="" support in 2.0. use data-dojo-event="" instead
|
|
var event = (script.getAttribute(attrData + "event") || script.getAttribute("event")),
|
|
prop = script.getAttribute(attrData + "prop"),
|
|
method = script.getAttribute(attrData + "method"),
|
|
advice = script.getAttribute(attrData + "advice"),
|
|
scriptType = script.getAttribute("type"),
|
|
nf = this._functionFromScript(script, attrData);
|
|
if(event){
|
|
if(scriptType == "dojo/connect"){
|
|
aspects.push({ method: event, func: nf });
|
|
}else if(scriptType == "dojo/on"){
|
|
ons.push({ event: event, func: nf });
|
|
}else{
|
|
params[event] = nf;
|
|
}
|
|
}else if(scriptType == "dojo/aspect"){
|
|
aspects.push({ method: method, advice: advice, func: nf });
|
|
}else if(scriptType == "dojo/watch"){
|
|
watches.push({ prop: prop, func: nf });
|
|
}else{
|
|
calls.push(nf);
|
|
}
|
|
}
|
|
}
|
|
|
|
// create the instance
|
|
var markupFactory = ctor.markupFactory || proto.markupFactory;
|
|
var instance = markupFactory ? markupFactory(params, node, ctor) : new ctor(params, node);
|
|
|
|
// map it to the JS namespace if that makes sense
|
|
if(jsname){
|
|
dlang.setObject(jsname, instance);
|
|
}
|
|
|
|
// process connections and startup functions
|
|
for(i=0; i<aspects.length; i++){
|
|
aspect[aspects[i].advice || "after"](instance, aspects[i].method, dlang.hitch(instance, aspects[i].func), true);
|
|
}
|
|
for(i=0; i<calls.length; i++){
|
|
calls[i].call(instance);
|
|
}
|
|
for(i=0; i<watches.length; i++){
|
|
instance.watch(watches[i].prop, watches[i].func);
|
|
}
|
|
for(i=0; i<ons.length; i++){
|
|
don(instance, ons[i].event, ons[i].func);
|
|
}
|
|
|
|
return instance;
|
|
},
|
|
|
|
scan: function(root, options){
|
|
// summary:
|
|
// Scan a DOM tree and return an array of objects representing the DOMNodes
|
|
// that need to be turned into widgets.
|
|
// description:
|
|
// Search specified node (or document root node) recursively for class instances
|
|
// and return an array of objects that represent potential widgets to be
|
|
// instantiated. Searches for either data-dojo-type="MID" or dojoType="MID" where
|
|
// "MID" is a module ID like "dijit/form/Button" or a fully qualified Class name
|
|
// like "dijit/form/Button". If the MID is not currently available, scan will
|
|
// attempt to require() in the module.
|
|
//
|
|
// See parser.parse() for details of markup.
|
|
// root: DomNode?
|
|
// A default starting root node from which to start the parsing. Can be
|
|
// omitted, defaulting to the entire document. If omitted, the `options`
|
|
// object can be passed in this place. If the `options` object has a
|
|
// `rootNode` member, that is used.
|
|
// options: Object
|
|
// a kwArgs options object, see parse() for details
|
|
//
|
|
// returns: Promise
|
|
// A promise that is resolved with the nodes that have been parsed.
|
|
|
|
var list = [], // Output List
|
|
mids = [], // An array of modules that are not yet loaded
|
|
midsHash = {}; // Used to keep the mids array unique
|
|
|
|
var dojoType = (options.scope || dojo._scopeName) + "Type", // typically "dojoType"
|
|
attrData = "data-" + (options.scope || dojo._scopeName) + "-", // typically "data-dojo-"
|
|
dataDojoType = attrData + "type", // typically "data-dojo-type"
|
|
dataDojoTextDir = attrData + "textdir", // typically "data-dojo-textdir"
|
|
dataDojoMixins = attrData + "mixins"; // typically "data-dojo-mixins"
|
|
|
|
// Info on DOMNode currently being processed
|
|
var node = root.firstChild;
|
|
|
|
// Info on parent of DOMNode currently being processed
|
|
// - inherited: dir, lang, and textDir setting of parent, or inherited by parent
|
|
// - parent: pointer to identical structure for my parent (or null if no parent)
|
|
// - scripts: if specified, collects <script type="dojo/..."> type nodes from children
|
|
var inherited = options.inherited;
|
|
if(!inherited){
|
|
function findAncestorAttr(node, attr){
|
|
return (node.getAttribute && node.getAttribute(attr)) ||
|
|
(node.parentNode && findAncestorAttr(node.parentNode, attr));
|
|
}
|
|
inherited = {
|
|
dir: findAncestorAttr(root, "dir"),
|
|
lang: findAncestorAttr(root, "lang"),
|
|
textDir: findAncestorAttr(root, dataDojoTextDir)
|
|
};
|
|
for(var key in inherited){
|
|
if(!inherited[key]){ delete inherited[key]; }
|
|
}
|
|
}
|
|
|
|
// Metadata about parent node
|
|
var parent = {
|
|
inherited: inherited
|
|
};
|
|
|
|
// For collecting <script type="dojo/..."> type nodes (when null, we don't need to collect)
|
|
var scripts;
|
|
|
|
// when true, only look for <script type="dojo/..."> tags, and don't recurse to children
|
|
var scriptsOnly;
|
|
|
|
function getEffective(parent){
|
|
// summary:
|
|
// Get effective dir, lang, textDir settings for specified obj
|
|
// (matching "parent" object structure above), and do caching.
|
|
// Take care not to return null entries.
|
|
if(!parent.inherited){
|
|
parent.inherited = {};
|
|
var node = parent.node,
|
|
grandparent = getEffective(parent.parent);
|
|
var inherited = {
|
|
dir: node.getAttribute("dir") || grandparent.dir,
|
|
lang: node.getAttribute("lang") || grandparent.lang,
|
|
textDir: node.getAttribute(dataDojoTextDir) || grandparent.textDir
|
|
};
|
|
for(var key in inherited){
|
|
if(inherited[key]){
|
|
parent.inherited[key] = inherited[key];
|
|
}
|
|
}
|
|
}
|
|
return parent.inherited;
|
|
}
|
|
|
|
// DFS on DOM tree, collecting nodes with data-dojo-type specified.
|
|
while(true){
|
|
if(!node){
|
|
// Finished this level, continue to parent's next sibling
|
|
if(!parent || !parent.node){
|
|
break;
|
|
}
|
|
node = parent.node.nextSibling;
|
|
scriptsOnly = false;
|
|
parent = parent.parent;
|
|
scripts = parent.scripts;
|
|
continue;
|
|
}
|
|
|
|
if(node.nodeType != 1){
|
|
// Text or comment node, skip to next sibling
|
|
node = node.nextSibling;
|
|
continue;
|
|
}
|
|
|
|
if(scripts && node.nodeName.toLowerCase() == "script"){
|
|
// Save <script type="dojo/..."> for parent, then continue to next sibling
|
|
type = node.getAttribute("type");
|
|
if(type && /^dojo\/\w/i.test(type)){
|
|
scripts.push(node);
|
|
}
|
|
node = node.nextSibling;
|
|
continue;
|
|
}
|
|
if(scriptsOnly){
|
|
// scriptsOnly flag is set, we have already collected scripts if the parent wants them, so now we shouldn't
|
|
// continue further analysis of the node and will continue to the next sibling
|
|
node = node.nextSibling;
|
|
continue;
|
|
}
|
|
|
|
// Check for data-dojo-type attribute, fallback to backward compatible dojoType
|
|
// TODO: Remove dojoType in 2.0
|
|
var type = node.getAttribute(dataDojoType) || node.getAttribute(dojoType);
|
|
|
|
// Short circuit for leaf nodes containing nothing [but text]
|
|
var firstChild = node.firstChild;
|
|
if(!type && (!firstChild || (firstChild.nodeType == 3 && !firstChild.nextSibling))){
|
|
node = node.nextSibling;
|
|
continue;
|
|
}
|
|
|
|
// Meta data about current node
|
|
var current;
|
|
|
|
var ctor = null;
|
|
if(type){
|
|
// If dojoType/data-dojo-type specified, add to output array of nodes to instantiate.
|
|
var mixinsValue = node.getAttribute(dataDojoMixins),
|
|
types = mixinsValue ? [type].concat(mixinsValue.split(/\s*,\s*/)) : [type];
|
|
|
|
// Note: won't find classes declared via dojo/Declaration or any modules that haven't been
|
|
// loaded yet so use try/catch to avoid throw from require()
|
|
try{
|
|
ctor = getCtor(types);
|
|
}catch(e){}
|
|
|
|
// If the constructor was not found, check to see if it has modules that can be loaded
|
|
if(!ctor){
|
|
darray.forEach(types, function(t){
|
|
if(~t.indexOf('/') && !midsHash[t]){
|
|
// If the type looks like a MID and it currently isn't in the array of MIDs to load, add it.
|
|
midsHash[t] = true;
|
|
mids[mids.length] = t;
|
|
}
|
|
});
|
|
}
|
|
|
|
var childScripts = ctor && !ctor.prototype._noScript ? [] : null; // <script> nodes that are parent's children
|
|
|
|
// Setup meta data about this widget node, and save it to list of nodes to instantiate
|
|
current = {
|
|
types: types,
|
|
ctor: ctor,
|
|
parent: parent,
|
|
node: node,
|
|
scripts: childScripts
|
|
};
|
|
current.inherited = getEffective(current); // dir & lang settings for current node, explicit or inherited
|
|
list.push(current);
|
|
}else{
|
|
// Meta data about this non-widget node
|
|
current = {
|
|
node: node,
|
|
scripts: scripts,
|
|
parent: parent
|
|
};
|
|
}
|
|
|
|
// Recurse, collecting <script type="dojo/..."> children, and also looking for
|
|
// descendant nodes with dojoType specified (unless the widget has the stopParser flag).
|
|
// When finished with children, go to my next sibling.
|
|
node = firstChild;
|
|
scripts = childScripts;
|
|
scriptsOnly = ctor && ctor.prototype.stopParser && !(options.template);
|
|
parent = current;
|
|
}
|
|
|
|
var d = new Deferred();
|
|
|
|
// If there are modules to load then require them in
|
|
if(mids.length){
|
|
// Warn that there are modules being auto-required
|
|
if(has("dojo-debug-messages")){
|
|
console.warn("WARNING: Modules being Auto-Required: " + mids.join(", "));
|
|
}
|
|
require(mids, function(){
|
|
// Go through list of widget nodes, filling in missing constructors, and filtering out nodes that shouldn't
|
|
// be instantiated due to a stopParser flag on an ancestor that we belatedly learned about due to
|
|
// auto-require of a module like ContentPane. Assumes list is in DFS order.
|
|
d.resolve(darray.filter(list, function(widget){
|
|
if(!widget.ctor){
|
|
// Attempt to find the constructor again. Still won't find classes defined via
|
|
// dijit/Declaration so need to try/catch.
|
|
try{
|
|
widget.ctor = getCtor(widget.types);
|
|
}catch(e){}
|
|
}
|
|
|
|
// Get the parent widget
|
|
var parent = widget.parent;
|
|
while(parent && !parent.types){
|
|
parent = parent.parent;
|
|
}
|
|
|
|
// Return false if this node should be skipped due to stopParser on an ancestor.
|
|
// Since list[] is in DFS order, this loop will always set parent.instantiateChildren before
|
|
// trying to compute widget.instantiate.
|
|
var proto = widget.ctor && widget.ctor.prototype;
|
|
widget.instantiateChildren = !(proto && proto.stopParser && !(options.template));
|
|
widget.instantiate = !parent || (parent.instantiate && parent.instantiateChildren);
|
|
return widget.instantiate;
|
|
}));
|
|
});
|
|
}else{
|
|
// There were no modules to load, so just resolve with the parsed nodes. This separate code path is for
|
|
// efficiency, to avoid running the require() and the callback code above.
|
|
d.resolve(list);
|
|
}
|
|
|
|
// Return the promise
|
|
return d.promise;
|
|
},
|
|
|
|
_require: function(/*DOMNode*/ script){
|
|
// summary:
|
|
// Helper for _scanAMD(). Takes a `<script type=dojo/require>bar: "acme/bar", ...</script>` node,
|
|
// calls require() to load the specified modules and (asynchronously) assign them to the specified global
|
|
// variables, and returns a Promise for when that operation completes.
|
|
//
|
|
// In the example above, it is effectively doing a require(["acme/bar", ...], function(a){ bar = a; }).
|
|
|
|
var hash = djson.fromJson("{" + script.innerHTML + "}"),
|
|
vars = [],
|
|
mids = [],
|
|
d = new Deferred();
|
|
|
|
for(var name in hash){
|
|
vars.push(name);
|
|
mids.push(hash[name]);
|
|
}
|
|
|
|
require(mids, function(){
|
|
for(var i=0; i<vars.length; i++){
|
|
dlang.setObject(vars[i], arguments[i]);
|
|
}
|
|
d.resolve(arguments);
|
|
});
|
|
|
|
return d.promise;
|
|
},
|
|
|
|
_scanAmd: function(root){
|
|
// summary:
|
|
// Scans the DOM for any declarative requires and returns their values.
|
|
// description:
|
|
// Looks for `<script type=dojo/require>bar: "acme/bar", ...</script>` node, calls require() to load the
|
|
// specified modules and (asynchronously) assign them to the specified global variables,
|
|
// and returns a Promise for when those operations complete.
|
|
// root: DomNode
|
|
// The node to base the scan from.
|
|
|
|
// Promise that resolves when all the <script type=dojo/require> nodes have finished loading.
|
|
var deferred = new Deferred(),
|
|
promise = deferred.promise;
|
|
deferred.resolve(true);
|
|
|
|
var self = this;
|
|
query("script[type='dojo/require']", root).forEach(function(node){
|
|
// Fire off require() call for specified modules. Chain this require to fire after
|
|
// any previous requires complete, so that layers can be loaded before individual module require()'s fire.
|
|
promise = promise.then(function(){ return self._require(node); });
|
|
|
|
// Remove from DOM so it isn't seen again
|
|
node.parentNode.removeChild(node);
|
|
});
|
|
|
|
return promise;
|
|
},
|
|
|
|
parse: function(rootNode, options){
|
|
// summary:
|
|
// Scan the DOM for class instances, and instantiate them.
|
|
// description:
|
|
// Search specified node (or root node) recursively for class instances,
|
|
// and instantiate them. Searches for either data-dojo-type="Class" or
|
|
// dojoType="Class" where "Class" is a a fully qualified class name,
|
|
// like `dijit/form/Button`
|
|
//
|
|
// Using `data-dojo-type`:
|
|
// Attributes using can be mixed into the parameters used to instantiate the
|
|
// Class by using a `data-dojo-props` attribute on the node being converted.
|
|
// `data-dojo-props` should be a string attribute to be converted from JSON.
|
|
//
|
|
// Using `dojoType`:
|
|
// Attributes are read from the original domNode and converted to appropriate
|
|
// types by looking up the Class prototype values. This is the default behavior
|
|
// from Dojo 1.0 to Dojo 1.5. `dojoType` support is deprecated, and will
|
|
// go away in Dojo 2.0.
|
|
// rootNode: DomNode?
|
|
// A default starting root node from which to start the parsing. Can be
|
|
// omitted, defaulting to the entire document. If omitted, the `options`
|
|
// object can be passed in this place. If the `options` object has a
|
|
// `rootNode` member, that is used.
|
|
// options: Object?
|
|
// A hash of options.
|
|
//
|
|
// - noStart: Boolean?:
|
|
// when set will prevent the parser from calling .startup()
|
|
// when locating the nodes.
|
|
// - rootNode: DomNode?:
|
|
// identical to the function's `rootNode` argument, though
|
|
// allowed to be passed in via this `options object.
|
|
// - template: Boolean:
|
|
// If true, ignores ContentPane's stopParser flag and parses contents inside of
|
|
// a ContentPane inside of a template. This allows dojoAttachPoint on widgets/nodes
|
|
// nested inside the ContentPane to work.
|
|
// - inherited: Object:
|
|
// Hash possibly containing dir and lang settings to be applied to
|
|
// parsed widgets, unless there's another setting on a sub-node that overrides
|
|
// - scope: String:
|
|
// Root for attribute names to search for. If scopeName is dojo,
|
|
// will search for data-dojo-type (or dojoType). For backwards compatibility
|
|
// reasons defaults to dojo._scopeName (which is "dojo" except when
|
|
// multi-version support is used, when it will be something like dojo16, dojo20, etc.)
|
|
// - propsThis: Object:
|
|
// If specified, "this" referenced from data-dojo-props will refer to propsThis.
|
|
// Intended for use from the widgets-in-template feature of `dijit._WidgetsInTemplateMixin`
|
|
// returns: Mixed
|
|
// Returns a blended object that is an array of the instantiated objects, but also can include
|
|
// a promise that is resolved with the instantiated objects. This is done for backwards
|
|
// compatibility. If the parser auto-requires modules, it will always behave in a promise
|
|
// fashion and `parser.parse().then(function(instances){...})` should be used.
|
|
// example:
|
|
// Parse all widgets on a page:
|
|
// | parser.parse();
|
|
// example:
|
|
// Parse all classes within the node with id="foo"
|
|
// | parser.parse(dojo.byId('foo'));
|
|
// example:
|
|
// Parse all classes in a page, but do not call .startup() on any
|
|
// child
|
|
// | parser.parse({ noStart: true })
|
|
// example:
|
|
// Parse all classes in a node, but do not call .startup()
|
|
// | parser.parse(someNode, { noStart:true });
|
|
// | // or
|
|
// | parser.parse({ noStart:true, rootNode: someNode });
|
|
|
|
// determine the root node and options based on the passed arguments.
|
|
var root;
|
|
if(!options && rootNode && rootNode.rootNode){
|
|
options = rootNode;
|
|
root = options.rootNode;
|
|
}else if(rootNode && dlang.isObject(rootNode) && !("nodeType" in rootNode)){
|
|
options = rootNode;
|
|
}else{
|
|
root = rootNode;
|
|
}
|
|
root = root ? dhtml.byId(root) : dwindow.body();
|
|
|
|
options = options || {};
|
|
|
|
var mixin = options.template ? { template: true } : {},
|
|
instances = [],
|
|
self = this;
|
|
|
|
// First scan for any <script type=dojo/require> nodes, and execute.
|
|
// Then scan for all nodes with data-dojo-type, and load any unloaded modules.
|
|
// Then build the object instances. Add instances to already existing (but empty) instances[] array,
|
|
// which may already have been returned to caller. Also, use otherwise to collect and throw any errors
|
|
// that occur during the parse().
|
|
var p =
|
|
this._scanAmd(root, options).then(function(){
|
|
return self.scan(root, options);
|
|
}).then(function(parsedNodes){
|
|
return instances = instances.concat(self._instantiate(parsedNodes, mixin, options));
|
|
}).otherwise(function(e){
|
|
// TODO Modify to follow better pattern for promise error managment when available
|
|
console.error("dojo/parser::parse() error", e);
|
|
throw e;
|
|
});
|
|
|
|
// Blend the array with the promise
|
|
dlang.mixin(instances, p);
|
|
return instances;
|
|
}
|
|
};
|
|
|
|
if( 1 ){
|
|
dojo.parser = parser;
|
|
}
|
|
|
|
// Register the parser callback. It should be the first callback
|
|
// after the a11y test.
|
|
if(config.parseOnLoad){
|
|
ready(100, parser, "parse");
|
|
}
|
|
|
|
return parser;
|
|
});
|
|
|
|
},
|
|
'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\" role=\"presentation\"\n/></span>\n",
|
|
'dojo/dnd/Manager':function(){
|
|
define("dojo/dnd/Manager", [
|
|
"../_base/array", "../_base/declare", "../_base/event", "../_base/lang", "../_base/window",
|
|
"../dom-class", "../Evented", "../has", "../keys", "../on", "../topic", "../touch",
|
|
"./common", "./autoscroll", "./Avatar"
|
|
], function(array, declare, event, lang, win, domClass, Evented, has, keys, on, topic, touch,
|
|
dnd, autoscroll, Avatar){
|
|
|
|
// module:
|
|
// dojo/dnd/Manager
|
|
|
|
var Manager = declare("dojo.dnd.Manager", [Evented], {
|
|
// summary:
|
|
// the manager of DnD operations (usually a singleton)
|
|
constructor: function(){
|
|
this.avatar = null;
|
|
this.source = null;
|
|
this.nodes = [];
|
|
this.copy = true;
|
|
this.target = null;
|
|
this.canDropFlag = false;
|
|
this.events = [];
|
|
},
|
|
|
|
// avatar's offset from the mouse
|
|
OFFSET_X: has("touch") ? 0 : 16,
|
|
OFFSET_Y: has("touch") ? -64 : 16,
|
|
|
|
// methods
|
|
overSource: function(source){
|
|
// summary:
|
|
// called when a source detected a mouse-over condition
|
|
// source: Object
|
|
// the reporter
|
|
if(this.avatar){
|
|
this.target = (source && source.targetState != "Disabled") ? source : null;
|
|
this.canDropFlag = Boolean(this.target);
|
|
this.avatar.update();
|
|
}
|
|
topic.publish("/dnd/source/over", source);
|
|
},
|
|
outSource: function(source){
|
|
// summary:
|
|
// called when a source detected a mouse-out condition
|
|
// source: Object
|
|
// the reporter
|
|
if(this.avatar){
|
|
if(this.target == source){
|
|
this.target = null;
|
|
this.canDropFlag = false;
|
|
this.avatar.update();
|
|
topic.publish("/dnd/source/over", null);
|
|
}
|
|
}else{
|
|
topic.publish("/dnd/source/over", null);
|
|
}
|
|
},
|
|
startDrag: function(source, nodes, copy){
|
|
// summary:
|
|
// called to initiate the DnD operation
|
|
// source: Object
|
|
// the source which provides items
|
|
// nodes: Array
|
|
// the list of transferred items
|
|
// copy: Boolean
|
|
// copy items, if true, move items otherwise
|
|
|
|
// Tell autoscroll that a drag is starting
|
|
autoscroll.autoScrollStart(win.doc);
|
|
|
|
this.source = source;
|
|
this.nodes = nodes;
|
|
this.copy = Boolean(copy); // normalizing to true boolean
|
|
this.avatar = this.makeAvatar();
|
|
win.body().appendChild(this.avatar.node);
|
|
topic.publish("/dnd/start", source, nodes, this.copy);
|
|
this.events = [
|
|
on(win.doc, touch.move, lang.hitch(this, "onMouseMove")),
|
|
on(win.doc, touch.release, lang.hitch(this, "onMouseUp")),
|
|
on(win.doc, "keydown", lang.hitch(this, "onKeyDown")),
|
|
on(win.doc, "keyup", lang.hitch(this, "onKeyUp")),
|
|
// cancel text selection and text dragging
|
|
on(win.doc, "dragstart", event.stop),
|
|
on(win.body(), "selectstart", event.stop)
|
|
];
|
|
var c = "dojoDnd" + (copy ? "Copy" : "Move");
|
|
domClass.add(win.body(), c);
|
|
},
|
|
canDrop: function(flag){
|
|
// summary:
|
|
// called to notify if the current target can accept items
|
|
var canDropFlag = Boolean(this.target && flag);
|
|
if(this.canDropFlag != canDropFlag){
|
|
this.canDropFlag = canDropFlag;
|
|
this.avatar.update();
|
|
}
|
|
},
|
|
stopDrag: function(){
|
|
// summary:
|
|
// stop the DnD in progress
|
|
domClass.remove(win.body(), ["dojoDndCopy", "dojoDndMove"]);
|
|
array.forEach(this.events, function(handle){ handle.remove(); });
|
|
this.events = [];
|
|
this.avatar.destroy();
|
|
this.avatar = null;
|
|
this.source = this.target = null;
|
|
this.nodes = [];
|
|
},
|
|
makeAvatar: function(){
|
|
// summary:
|
|
// makes the avatar; it is separate to be overwritten dynamically, if needed
|
|
return new Avatar(this);
|
|
},
|
|
updateAvatar: function(){
|
|
// summary:
|
|
// updates the avatar; it is separate to be overwritten dynamically, if needed
|
|
this.avatar.update();
|
|
},
|
|
|
|
// mouse event processors
|
|
onMouseMove: function(e){
|
|
// summary:
|
|
// event processor for onmousemove
|
|
// e: Event
|
|
// mouse event
|
|
var a = this.avatar;
|
|
if(a){
|
|
autoscroll.autoScrollNodes(e);
|
|
//autoscroll.autoScroll(e);
|
|
var s = a.node.style;
|
|
s.left = (e.pageX + this.OFFSET_X) + "px";
|
|
s.top = (e.pageY + this.OFFSET_Y) + "px";
|
|
var copy = Boolean(this.source.copyState(dnd.getCopyKeyState(e)));
|
|
if(this.copy != copy){
|
|
this._setCopyStatus(copy);
|
|
}
|
|
}
|
|
if(has("touch")){
|
|
// Prevent page from scrolling so that user can drag instead.
|
|
e.preventDefault();
|
|
}
|
|
},
|
|
onMouseUp: function(e){
|
|
// summary:
|
|
// event processor for onmouseup
|
|
// e: Event
|
|
// mouse event
|
|
if(this.avatar){
|
|
if(this.target && this.canDropFlag){
|
|
var copy = Boolean(this.source.copyState(dnd.getCopyKeyState(e)));
|
|
topic.publish("/dnd/drop/before", this.source, this.nodes, copy, this.target, e);
|
|
topic.publish("/dnd/drop", this.source, this.nodes, copy, this.target, e);
|
|
}else{
|
|
topic.publish("/dnd/cancel");
|
|
}
|
|
this.stopDrag();
|
|
}
|
|
},
|
|
|
|
// keyboard event processors
|
|
onKeyDown: function(e){
|
|
// summary:
|
|
// event processor for onkeydown:
|
|
// watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag
|
|
// e: Event
|
|
// keyboard event
|
|
if(this.avatar){
|
|
switch(e.keyCode){
|
|
case keys.CTRL:
|
|
var copy = Boolean(this.source.copyState(true));
|
|
if(this.copy != copy){
|
|
this._setCopyStatus(copy);
|
|
}
|
|
break;
|
|
case keys.ESCAPE:
|
|
topic.publish("/dnd/cancel");
|
|
this.stopDrag();
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
onKeyUp: function(e){
|
|
// summary:
|
|
// event processor for onkeyup, watching for CTRL for copy/move status
|
|
// e: Event
|
|
// keyboard event
|
|
if(this.avatar && e.keyCode == keys.CTRL){
|
|
var copy = Boolean(this.source.copyState(false));
|
|
if(this.copy != copy){
|
|
this._setCopyStatus(copy);
|
|
}
|
|
}
|
|
},
|
|
|
|
// utilities
|
|
_setCopyStatus: function(copy){
|
|
// summary:
|
|
// changes the copy status
|
|
// copy: Boolean
|
|
// the copy status
|
|
this.copy = copy;
|
|
this.source._markDndStatus(this.copy);
|
|
this.updateAvatar();
|
|
domClass.replace(win.body(),
|
|
"dojoDnd" + (this.copy ? "Copy" : "Move"),
|
|
"dojoDnd" + (this.copy ? "Move" : "Copy"));
|
|
}
|
|
});
|
|
|
|
// dnd._manager:
|
|
// The manager singleton variable. Can be overwritten if needed.
|
|
dnd._manager = null;
|
|
|
|
Manager.manager = dnd.manager = function(){
|
|
// summary:
|
|
// Returns the current DnD manager. Creates one if it is not created yet.
|
|
if(!dnd._manager){
|
|
dnd._manager = new Manager();
|
|
}
|
|
return dnd._manager; // Object
|
|
};
|
|
|
|
return Manager;
|
|
});
|
|
|
|
},
|
|
'dijit/form/ToggleButton':function(){
|
|
define("dijit/form/ToggleButton", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"./Button",
|
|
"./_ToggleButtonMixin"
|
|
], function(declare, kernel, Button, _ToggleButtonMixin){
|
|
|
|
// module:
|
|
// dijit/form/ToggleButton
|
|
|
|
|
|
return declare("dijit.form.ToggleButton", [Button, _ToggleButtonMixin], {
|
|
// summary:
|
|
// A templated button widget that can be in two states (checked or not).
|
|
// Can be base class for things like tabs or checkbox or radio buttons.
|
|
|
|
baseClass: "dijitToggleButton",
|
|
|
|
setChecked: function(/*Boolean*/ checked){
|
|
// summary:
|
|
// Deprecated. Use set('checked', true/false) instead.
|
|
kernel.deprecated("setChecked("+checked+") is deprecated. Use set('checked',"+checked+") instead.", "", "2.0");
|
|
this.set('checked', checked);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/date/stamp':function(){
|
|
define("dojo/date/stamp", ["../_base/lang", "../_base/array"], function(lang, array){
|
|
|
|
// module:
|
|
// dojo/date/stamp
|
|
|
|
var stamp = {
|
|
// summary:
|
|
// TODOC
|
|
};
|
|
lang.setObject("dojo.date.stamp", stamp);
|
|
|
|
// Methods to convert dates to or from a wire (string) format using well-known conventions
|
|
|
|
stamp.fromISOString = function(/*String*/ formattedString, /*Number?*/ defaultTime){
|
|
// summary:
|
|
// Returns a Date object given a string formatted according to a subset of the ISO-8601 standard.
|
|
//
|
|
// description:
|
|
// Accepts a string formatted according to a profile of ISO8601 as defined by
|
|
// [RFC3339](http://www.ietf.org/rfc/rfc3339.txt), except that partial input is allowed.
|
|
// Can also process dates as specified [by the W3C](http://www.w3.org/TR/NOTE-datetime)
|
|
// The following combinations are valid:
|
|
//
|
|
// - dates only
|
|
// - yyyy
|
|
// - yyyy-MM
|
|
// - yyyy-MM-dd
|
|
// - times only, with an optional time zone appended
|
|
// - THH:mm
|
|
// - THH:mm:ss
|
|
// - THH:mm:ss.SSS
|
|
// - and "datetimes" which could be any combination of the above
|
|
//
|
|
// timezones may be specified as Z (for UTC) or +/- followed by a time expression HH:mm
|
|
// Assumes the local time zone if not specified. Does not validate. Improperly formatted
|
|
// input may return null. Arguments which are out of bounds will be handled
|
|
// by the Date constructor (e.g. January 32nd typically gets resolved to February 1st)
|
|
// Only years between 100 and 9999 are supported.
|
|
// formattedString:
|
|
// A string such as 2005-06-30T08:05:00-07:00 or 2005-06-30 or T08:05:00
|
|
// defaultTime:
|
|
// Used for defaults for fields omitted in the formattedString.
|
|
// Uses 1970-01-01T00:00:00.0Z by default.
|
|
|
|
if(!stamp._isoRegExp){
|
|
stamp._isoRegExp =
|
|
//TODO: could be more restrictive and check for 00-59, etc.
|
|
/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/;
|
|
}
|
|
|
|
var match = stamp._isoRegExp.exec(formattedString),
|
|
result = null;
|
|
|
|
if(match){
|
|
match.shift();
|
|
if(match[1]){match[1]--;} // Javascript Date months are 0-based
|
|
if(match[6]){match[6] *= 1000;} // Javascript Date expects fractional seconds as milliseconds
|
|
|
|
if(defaultTime){
|
|
// mix in defaultTime. Relatively expensive, so use || operators for the fast path of defaultTime === 0
|
|
defaultTime = new Date(defaultTime);
|
|
array.forEach(array.map(["FullYear", "Month", "Date", "Hours", "Minutes", "Seconds", "Milliseconds"], function(prop){
|
|
return defaultTime["get" + prop]();
|
|
}), function(value, index){
|
|
match[index] = match[index] || value;
|
|
});
|
|
}
|
|
result = new Date(match[0]||1970, match[1]||0, match[2]||1, match[3]||0, match[4]||0, match[5]||0, match[6]||0); //TODO: UTC defaults
|
|
if(match[0] < 100){
|
|
result.setFullYear(match[0] || 1970);
|
|
}
|
|
|
|
var offset = 0,
|
|
zoneSign = match[7] && match[7].charAt(0);
|
|
if(zoneSign != 'Z'){
|
|
offset = ((match[8] || 0) * 60) + (Number(match[9]) || 0);
|
|
if(zoneSign != '-'){ offset *= -1; }
|
|
}
|
|
if(zoneSign){
|
|
offset -= result.getTimezoneOffset();
|
|
}
|
|
if(offset){
|
|
result.setTime(result.getTime() + offset * 60000);
|
|
}
|
|
}
|
|
|
|
return result; // Date or null
|
|
};
|
|
|
|
/*=====
|
|
var __Options = {
|
|
// selector: String
|
|
// "date" or "time" for partial formatting of the Date object.
|
|
// Both date and time will be formatted by default.
|
|
// zulu: Boolean
|
|
// if true, UTC/GMT is used for a timezone
|
|
// milliseconds: Boolean
|
|
// if true, output milliseconds
|
|
};
|
|
=====*/
|
|
|
|
stamp.toISOString = function(/*Date*/ dateObject, /*__Options?*/ options){
|
|
// summary:
|
|
// Format a Date object as a string according a subset of the ISO-8601 standard
|
|
//
|
|
// description:
|
|
// When options.selector is omitted, output follows [RFC3339](http://www.ietf.org/rfc/rfc3339.txt)
|
|
// The local time zone is included as an offset from GMT, except when selector=='time' (time without a date)
|
|
// Does not check bounds. Only years between 100 and 9999 are supported.
|
|
//
|
|
// dateObject:
|
|
// A Date object
|
|
|
|
var _ = function(n){ return (n < 10) ? "0" + n : n; };
|
|
options = options || {};
|
|
var formattedDate = [],
|
|
getter = options.zulu ? "getUTC" : "get",
|
|
date = "";
|
|
if(options.selector != "time"){
|
|
var year = dateObject[getter+"FullYear"]();
|
|
date = ["0000".substr((year+"").length)+year, _(dateObject[getter+"Month"]()+1), _(dateObject[getter+"Date"]())].join('-');
|
|
}
|
|
formattedDate.push(date);
|
|
if(options.selector != "date"){
|
|
var time = [_(dateObject[getter+"Hours"]()), _(dateObject[getter+"Minutes"]()), _(dateObject[getter+"Seconds"]())].join(':');
|
|
var millis = dateObject[getter+"Milliseconds"]();
|
|
if(options.milliseconds){
|
|
time += "."+ (millis < 100 ? "0" : "") + _(millis);
|
|
}
|
|
if(options.zulu){
|
|
time += "Z";
|
|
}else if(options.selector != "time"){
|
|
var timezoneOffset = dateObject.getTimezoneOffset();
|
|
var absOffset = Math.abs(timezoneOffset);
|
|
time += (timezoneOffset > 0 ? "-" : "+") +
|
|
_(Math.floor(absOffset/60)) + ":" + _(absOffset%60);
|
|
}
|
|
formattedDate.push(time);
|
|
}
|
|
return formattedDate.join('T'); // String
|
|
};
|
|
|
|
return stamp;
|
|
});
|
|
|
|
},
|
|
'dojo/Stateful':function(){
|
|
define("dojo/Stateful", ["./_base/declare", "./_base/lang", "./_base/array", "dojo/when"], function(declare, lang, array, when){
|
|
// module:
|
|
// dojo/Stateful
|
|
|
|
return declare("dojo.Stateful", null, {
|
|
// summary:
|
|
// Base class for objects that provide named properties with optional getter/setter
|
|
// control and the ability to watch for property changes
|
|
//
|
|
// The class also provides the functionality to auto-magically manage getters
|
|
// and setters for object attributes/properties.
|
|
//
|
|
// Getters and Setters should follow the format of _xxxGetter or _xxxSetter where
|
|
// the xxx is a name of the attribute to handle. So an attribute of "foo"
|
|
// would have a custom getter of _fooGetter and a custom setter of _fooSetter.
|
|
//
|
|
// example:
|
|
// | var obj = new dojo.Stateful();
|
|
// | obj.watch("foo", function(){
|
|
// | console.log("foo changed to " + this.get("foo"));
|
|
// | });
|
|
// | obj.set("foo","bar");
|
|
|
|
// _attrPairNames: Hash
|
|
// Used across all instances a hash to cache attribute names and their getter
|
|
// and setter names.
|
|
_attrPairNames: {},
|
|
|
|
_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]; }
|
|
return (apn[name] = {
|
|
s: "_" + name + "Setter",
|
|
g: "_" + name + "Getter"
|
|
});
|
|
},
|
|
|
|
postscript: function(/*Object?*/ params){
|
|
// Automatic setting of params during construction
|
|
if (params){ this.set(params); }
|
|
},
|
|
|
|
_get: function(name, names){
|
|
// summary:
|
|
// Private function that does a get based off a hash of names
|
|
// names:
|
|
// Hash of names of custom attributes
|
|
return typeof this[names.g] === "function" ? this[names.g]() : this[name];
|
|
},
|
|
get: function(/*String*/name){
|
|
// summary:
|
|
// Get a property on a Stateful instance.
|
|
// name:
|
|
// The property to get.
|
|
// returns:
|
|
// The property value on this Stateful instance.
|
|
// description:
|
|
// Get a named property on a Stateful object. The property may
|
|
// potentially be retrieved via a getter method in subclasses. In the base class
|
|
// this just retrieves the object's property.
|
|
// For example:
|
|
// | stateful = new dojo.Stateful({foo: 3});
|
|
// | stateful.get("foo") // returns 3
|
|
// | stateful.foo // returns 3
|
|
|
|
return this._get(name, this._getAttrNames(name)); //Any
|
|
},
|
|
set: function(/*String*/name, /*Object*/value){
|
|
// summary:
|
|
// Set a property on a Stateful instance
|
|
// name:
|
|
// The property to set.
|
|
// value:
|
|
// The value to set in the property.
|
|
// returns:
|
|
// The function returns this dojo.Stateful instance.
|
|
// description:
|
|
// Sets named properties on a stateful object and notifies any watchers of
|
|
// the property. A programmatic setter may be defined in subclasses.
|
|
// For example:
|
|
// | stateful = new dojo.Stateful();
|
|
// | stateful.watch(function(name, oldValue, value){
|
|
// | // this will be called on the set below
|
|
// | }
|
|
// | stateful.set(foo, 5);
|
|
//
|
|
// set() may also be called with a hash of name/value pairs, ex:
|
|
// | myObj.set({
|
|
// | foo: "Howdy",
|
|
// | bar: 3
|
|
// | })
|
|
// This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
|
|
|
|
// If an object is used, iterate through object
|
|
if(typeof name === "object"){
|
|
for(var x in name){
|
|
if(name.hasOwnProperty(x) && x !="_watchCallbacks"){
|
|
this.set(x, name[x]);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
var names = this._getAttrNames(name),
|
|
oldValue = this._get(name, names),
|
|
setter = this[names.s],
|
|
result;
|
|
if(typeof setter === "function"){
|
|
// use the explicit setter
|
|
result = setter.apply(this, Array.prototype.slice.call(arguments, 1));
|
|
}else{
|
|
// no setter so set attribute directly
|
|
this[name] = value;
|
|
}
|
|
if(this._watchCallbacks){
|
|
var self = this;
|
|
// If setter returned a promise, wait for it to complete, otherwise call watches immediatly
|
|
when(result, function(){
|
|
self._watchCallbacks(name, oldValue, value);
|
|
});
|
|
}
|
|
return this; // dojo/Stateful
|
|
},
|
|
_changeAttrValue: function(name, value){
|
|
// summary:
|
|
// Internal helper for directly changing an attribute value.
|
|
//
|
|
// name: String
|
|
// The property to set.
|
|
// value: Mixed
|
|
// The value to set in the property.
|
|
//
|
|
// description:
|
|
// Directly change the value of an attribute on an object, bypassing any
|
|
// accessor setter. Also handles the calling of watch and emitting events.
|
|
// It is designed to be used by descendent class when there are two values
|
|
// of attributes that are linked, but calling .set() is not appropriate.
|
|
|
|
var oldValue = this.get(name);
|
|
this[name] = value;
|
|
if(this._watchCallbacks){
|
|
this._watchCallbacks(name, oldValue, value);
|
|
}
|
|
return this; // dojo/Stateful
|
|
},
|
|
watch: function(/*String?*/name, /*Function*/callback){
|
|
// summary:
|
|
// Watches a property for changes
|
|
// name:
|
|
// Indicates the property to watch. This is optional (the callback may be the
|
|
// only parameter), and if omitted, all the properties will be watched
|
|
// returns:
|
|
// An object handle for the watch. The unwatch method of this object
|
|
// can be used to discontinue watching this property:
|
|
// | var watchHandle = obj.watch("foo", callback);
|
|
// | watchHandle.unwatch(); // callback won't be called now
|
|
// callback:
|
|
// The function to execute when the property changes. This will be called after
|
|
// the property has been changed. The callback will be called with the |this|
|
|
// set to the instance, the first argument as the name of the property, the
|
|
// second argument as the old value and the third argument as the new value.
|
|
|
|
var callbacks = this._watchCallbacks;
|
|
if(!callbacks){
|
|
var self = this;
|
|
callbacks = this._watchCallbacks = function(name, oldValue, value, ignoreCatchall){
|
|
var notify = function(propertyCallbacks){
|
|
if(propertyCallbacks){
|
|
propertyCallbacks = propertyCallbacks.slice();
|
|
for(var i = 0, l = propertyCallbacks.length; i < l; i++){
|
|
propertyCallbacks[i].call(self, name, oldValue, value);
|
|
}
|
|
}
|
|
};
|
|
notify(callbacks['_' + name]);
|
|
if(!ignoreCatchall){
|
|
notify(callbacks["*"]); // the catch-all
|
|
}
|
|
}; // we use a function instead of an object so it will be ignored by JSON conversion
|
|
}
|
|
if(!callback && typeof name === "function"){
|
|
callback = name;
|
|
name = "*";
|
|
}else{
|
|
// prepend with dash to prevent name conflicts with function (like "name" property)
|
|
name = '_' + name;
|
|
}
|
|
var propertyCallbacks = callbacks[name];
|
|
if(typeof propertyCallbacks !== "object"){
|
|
propertyCallbacks = callbacks[name] = [];
|
|
}
|
|
propertyCallbacks.push(callback);
|
|
|
|
// TODO: Remove unwatch in 2.0
|
|
var handle = {};
|
|
handle.unwatch = handle.remove = function(){
|
|
var index = array.indexOf(propertyCallbacks, callback);
|
|
if(index > -1){
|
|
propertyCallbacks.splice(index, 1);
|
|
}
|
|
};
|
|
return handle; //Object
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'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/keys", // keys
|
|
"dojo/_base/lang", // lang.getObject lang.hitch
|
|
"dojo/sniff", // has("ie") has("dijit-legacy-requires")
|
|
"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,
|
|
keys, lang, has, topic, focus, manager, ready,
|
|
_Widget, _Container, _TemplatedMixin, _CssStateMixin, StackContainer, ContentPane, template){
|
|
|
|
// module:
|
|
// dijit/layout/AccordionContainer
|
|
|
|
|
|
// 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 ? "true" : "false");
|
|
this.focusNode.setAttribute("aria-selected", isSelected ? "true" : "false");
|
|
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/_WidgetBase
|
|
// 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){
|
|
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/_WidgetBase*/ child, /*Integer?*/ insertIndex){
|
|
// Overrides _LayoutWidget.addChild().
|
|
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/_WidgetBase?*/ newWidget, /*dijit/_WidgetBase?*/ 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/_WidgetBase*/ 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(has("dijit-legacy-requires")){
|
|
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/form/ComboButton':function(){
|
|
require({cache:{
|
|
'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\" role=\"presentation\"\n\t\t/></td></tr></tbody\n></table>\n"}});
|
|
define("dijit/form/ComboButton", [
|
|
"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){
|
|
|
|
// module:
|
|
// dijit/form/ComboButton
|
|
|
|
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 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/form/_AutoCompleterMixin':function(){
|
|
define("dijit/form/_AutoCompleterMixin", [
|
|
"dojo/data/util/filter", // patternToRegExp
|
|
"dojo/_base/declare", // declare
|
|
"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/sniff", // has("ie")
|
|
"dojo/string", // string.substitute
|
|
"./DataList",
|
|
"../registry", // registry.byId
|
|
"./_TextBoxMixin", // defines _TextBoxMixin.selectInputText
|
|
"./_SearchMixin"
|
|
], function(filter, declare, domAttr, event, keys, lang, query, regexp, has, string,
|
|
DataList, registry, _TextBoxMixin, SearchMixin){
|
|
|
|
// module:
|
|
// dijit/form/_AutoCompleterMixin
|
|
|
|
return declare("dijit.form._AutoCompleterMixin", SearchMixin, {
|
|
// 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/store/api/Store implementation that
|
|
// provides the data for this ComboBox, it's the currently selected item.
|
|
item: null,
|
|
|
|
// 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",
|
|
|
|
// 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",
|
|
|
|
// 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 = element.ownerDocument.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 ? "true" : "false");
|
|
},
|
|
|
|
_onKey: function(/*Event*/ evt){
|
|
// summary:
|
|
// Handles keyboard events
|
|
|
|
if(evt.charCode >= 32){ return; } // alphanumeric reserved for searching
|
|
|
|
var key = evt.charCode || evt.keyCode;
|
|
|
|
// except for cutting/pasting case - ctrl + x/v
|
|
if(key == keys.ALT || key == keys.CTRL || key == keys.META || key == keys.SHIFT){
|
|
return; // throw out spurious events
|
|
}
|
|
|
|
var pw = this.dropDown;
|
|
var highlighted = null;
|
|
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(evt.altKey || evt.ctrlKey || evt.metaKey){ return; } // don't process keys with modifiers - but we want shift+TAB
|
|
|
|
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); // prevent submit
|
|
break;
|
|
}else if(highlighted == pw.previousButton){
|
|
this._nextSearch(-1);
|
|
event.stop(evt); // prevent submit
|
|
break;
|
|
}
|
|
event.stop(evt); // prevent submit if ENTER was to choose an item
|
|
}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
|
|
}
|
|
// 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;
|
|
}
|
|
},
|
|
|
|
_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
|
|
var wasSelected = this.dropDown.getHighlightedOption();
|
|
this.dropDown.clearResultList();
|
|
if(!results.length && options.start == 0){ // if no results and not just the previous choices button
|
|
this.closeDropDown();
|
|
return;
|
|
}
|
|
this._nextSearch = this.dropDown.onPage = lang.hitch(this, function(direction){
|
|
results.nextPage(direction !== -1);
|
|
this.focus();
|
|
});
|
|
|
|
// 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.
|
|
|
|
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("direction" in options){
|
|
if(options.direction){
|
|
this.dropDown.highlightFirstOption();
|
|
}else if(!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(this.dropDown.containerNode.firstChild.nextSibling); // 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{
|
|
var item = this.dropDown.items[node.getAttribute("item")];
|
|
newValue = (this.store._oldAPI ? // remove getValue() for 2.0 (old dojo.data API)
|
|
this.store.getValue(item, this.searchAttr) : item[this.searchAttr]).toString();
|
|
this.set('item', 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.item = undefined; // undefined means item needs to be set
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
_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.
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
_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 ///////////////////////////////////////
|
|
|
|
postMixInProperties: function(){
|
|
this.inherited(arguments);
|
|
if(!this.store){
|
|
var srcNodeRef = this.srcNodeRef;
|
|
// 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];
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
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){
|
|
if(!label[0].id){ label[0].id = this.id + "_label"; }
|
|
this.domNode.setAttribute("aria-labelledby", label[0].id);
|
|
|
|
}
|
|
this.inherited(arguments);
|
|
this.connect(this, "onSearch", "_openResultList");
|
|
},
|
|
|
|
_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._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
|
|
//If < appears in label, and user presses t, we don't want to highlight the t in the escaped "<"
|
|
//first find out every occurences of "find", wrap each occurence in a pair of "\uFFFF" characters (which
|
|
//should not appear in any string). then html escape the whole string, and replace '\uFFFF" with the
|
|
//HTML highlight markup.
|
|
return this._escapeHtml(label.replace(
|
|
new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers),
|
|
'\uFFFF$1\uFFFF')).replace(
|
|
/\uFFFF([^\uFFFF]+)\uFFFF/g, '<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, store){
|
|
// summary:
|
|
// Computes the label to display based on the dojo.data store item.
|
|
// item: Object
|
|
// The item from the store
|
|
// store: dojo/store/api/Store
|
|
// The store.
|
|
// 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 == null /* or undefined */){ 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\" class=\"dijitTabInnerDiv dijitTabContent dijitButtonContents\" data-dojo-attach-point=\"focusNode\">\n\t<img role=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" data-dojo-attach-point=\"iconNode\"/>\n\t<span data-dojo-attach-point=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n</div>",
|
|
'dijit/form/MappedTextBox':function(){
|
|
define("dijit/form/MappedTextBox", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-construct", // domConstruct.place
|
|
"./ValidationTextBox"
|
|
], function(declare, domConstruct, ValidationTextBox){
|
|
|
|
// module:
|
|
// dijit/form/MappedTextBox
|
|
|
|
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(){
|
|
require({cache:{
|
|
'url:dijit/form/templates/DropDownBox.html':"<div class=\"dijit dijitReset dijitInline dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdata-dojo-attach-point=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"button presentation\" aria-hidden=\"true\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdata-dojo-attach-point=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"}});
|
|
define("dijit/form/ComboBoxMixin", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/Deferred",
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/_base/lang", // lang.mixin
|
|
"dojo/store/util/QueryResults",
|
|
"./_AutoCompleterMixin",
|
|
"./_ComboBoxMenu",
|
|
"../_HasDropDown",
|
|
"dojo/text!./templates/DropDownBox.html"
|
|
], function(declare, Deferred, kernel, lang, QueryResults, _AutoCompleterMixin, _ComboBoxMenu, _HasDropDown, template){
|
|
|
|
|
|
// module:
|
|
// dijit/form/ComboBoxMixin
|
|
|
|
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/api/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(); });
|
|
deferred.total = new Deferred();
|
|
var fetchHandle = this.fetch(lang.mixin({
|
|
query: query,
|
|
onBegin: function(count){
|
|
deferred.total.resolve(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.
|
|
var store = this.params.store || this.store;
|
|
if(store){
|
|
this._setStoreAttr(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 && !this.store._oldAPI){
|
|
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("dijit/form/_TextBoxMixin", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.byId
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/keys", // keys.ALT keys.CAPS_LOCK keys.CTRL keys.META keys.SHIFT
|
|
"dojo/_base/lang", // lang.mixin
|
|
"dojo/on", // on
|
|
"../main" // for exporting dijit._setSelectionRange, dijit.selectInputText
|
|
], function(array, declare, dom, event, keys, lang, on, dijit){
|
|
|
|
// module:
|
|
// dijit/form/_TextBoxMixin
|
|
|
|
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 /* and !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 /* or 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 /* or 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(/*Event*/ evt){
|
|
// 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._processInput(evt);
|
|
},
|
|
|
|
_processInput: function(/*Event*/ evt){
|
|
// summary:
|
|
// Default action handler for user input events
|
|
|
|
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 charOrCode;
|
|
if(e.type == "keydown"){
|
|
charOrCode = e.keyCode;
|
|
switch(charOrCode){ // ignore state keys
|
|
case keys.SHIFT:
|
|
case keys.ALT:
|
|
case keys.CTRL:
|
|
case keys.META:
|
|
case keys.CAPS_LOCK:
|
|
case keys.NUM_LOCK:
|
|
case keys.SCROLL_LOCK:
|
|
return;
|
|
}
|
|
if(!e.ctrlKey && !e.metaKey && !e.altKey){ // no modifiers
|
|
switch(charOrCode){ // ignore location keys
|
|
case keys.NUMPAD_0:
|
|
case keys.NUMPAD_1:
|
|
case keys.NUMPAD_2:
|
|
case keys.NUMPAD_3:
|
|
case keys.NUMPAD_4:
|
|
case keys.NUMPAD_5:
|
|
case keys.NUMPAD_6:
|
|
case keys.NUMPAD_7:
|
|
case keys.NUMPAD_8:
|
|
case keys.NUMPAD_9:
|
|
case keys.NUMPAD_MULTIPLY:
|
|
case keys.NUMPAD_PLUS:
|
|
case keys.NUMPAD_ENTER:
|
|
case keys.NUMPAD_MINUS:
|
|
case keys.NUMPAD_PERIOD:
|
|
case keys.NUMPAD_DIVIDE:
|
|
return;
|
|
}
|
|
if((charOrCode >= 65 && charOrCode <= 90) || (charOrCode >= 48 && charOrCode <= 57) || charOrCode == keys.SPACE){
|
|
return; // keypress will handle simple non-modified printable keys
|
|
}
|
|
var named = false;
|
|
for(var i in keys){
|
|
if(keys[i] === e.keyCode){
|
|
named = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!named){ return; } // only allow named ones through
|
|
}
|
|
}
|
|
charOrCode = e.charCode >= 32 ? String.fromCharCode(e.charCode) : e.charCode;
|
|
if(!charOrCode){
|
|
charOrCode = (e.keyCode >= 65 && e.keyCode <= 90) || (e.keyCode >= 48 && e.keyCode <= 57) || e.keyCode == keys.SPACE ? String.fromCharCode(e.keyCode) : e.keyCode;
|
|
}
|
|
if(!charOrCode){
|
|
charOrCode = 229; // IME
|
|
}
|
|
if(e.type == "keypress"){
|
|
if(typeof charOrCode != "string"){ return; }
|
|
if((charOrCode >= 'a' && charOrCode <= 'z') || (charOrCode >= 'A' && charOrCode <= 'Z') || (charOrCode >= '0' && charOrCode <= '9') || (charOrCode === ' ')){
|
|
if(e.ctrlKey || e.metaKey || e.altKey){ return; } // can only be stopped reliably in keydown
|
|
}
|
|
}
|
|
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 = { faux: true }, attr;
|
|
for(attr in e){
|
|
if(attr != "layerX" && attr != "layerY"){ // prevent WebKit warnings
|
|
var v = e[attr];
|
|
if(typeof v != "function" && typeof v != "undefined"){ faux[attr] = v; }
|
|
}
|
|
}
|
|
lang.mixin(faux, {
|
|
charOrCode: charOrCode,
|
|
_wasConsumed: false,
|
|
preventDefault: function(){
|
|
faux._wasConsumed = true;
|
|
e.preventDefault();
|
|
},
|
|
stopPropagation: function(){ e.stopPropagation(); }
|
|
});
|
|
// give web page author a chance to consume the event
|
|
//console.log(faux.type + ', charOrCode = (' + (typeof charOrCode) + ') ' + charOrCode + ', ctrl ' + !!faux.ctrlKey + ', alt ' + !!faux.altKey + ', meta ' + !!faux.metaKey + ', shift ' + !!faux.shiftKey);
|
|
if(this.onInput(faux) === false){ // return false means stop
|
|
faux.preventDefault();
|
|
faux.stopPropagation();
|
|
}
|
|
if(faux._wasConsumed){ return; } // if preventDefault was called
|
|
this.defer(function(){ this._onInput(faux); }); // widget notification after key has posted
|
|
};
|
|
this.own(on(this.textbox, "keydown, keypress, paste, cut, input, compositionend", lang.hitch(this, handleEvent)));
|
|
},
|
|
|
|
_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);
|
|
},
|
|
|
|
_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);
|
|
this._selectOnClickHandle = null;
|
|
|
|
// 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);
|
|
}
|
|
});
|
|
// in case the mouseup never comes
|
|
this.defer(function(){
|
|
if(this._selectOnClickHandle){
|
|
this.disconnect(this._selectOnClickHandle);
|
|
this._selectOnClickHandle = null;
|
|
}
|
|
}, 500); // if mouseup not received soon, then treat it as some gesture
|
|
}
|
|
// 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/sniff", // has("ie") has("opera")
|
|
"./TextBox"
|
|
], function(declare, domClass, has, TextBox){
|
|
|
|
// module:
|
|
// dijit/form/SimpleTextarea
|
|
|
|
|
|
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 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(this.ownerDocument.selection){ //IE
|
|
textarea.focus();
|
|
var range = this.ownerDocument.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,innerDiv,tabContent\" class=\"dijitTabInner dijitTabContent\">\n\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitTabButtonIcon\" data-dojo-attach-point='iconNode'/>\n\t<span data-dojo-attach-point='containerNode,focusNode' class='tabLabel'></span>\n\t<span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" data-dojo-attach-point='closeNode'\n\t\t role=\"presentation\">\n\t\t<span data-dojo-attach-point='closeText' class='dijitTabCloseText'>[x]</span\n\t\t\t\t></span>\n</div>\n",
|
|
'dijit/_base/window':function(){
|
|
define("dijit/_base/window", [
|
|
"dojo/window", // windowUtils.get
|
|
"../main" // export symbol to dijit
|
|
], function(windowUtils, dijit){
|
|
// module:
|
|
// dijit/_base/window
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// Back compatibility module, new code should use windowUtils directly instead of using this module.
|
|
};
|
|
=====*/
|
|
|
|
dijit.getDocumentWindow = function(doc){
|
|
return windowUtils.get(doc);
|
|
};
|
|
});
|
|
|
|
},
|
|
'dijit/PopupMenuItem':function(){
|
|
define("dijit/PopupMenuItem", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-style", // domStyle.set
|
|
"dojo/query", // query
|
|
"./registry", // registry.byNode
|
|
"./MenuItem",
|
|
"./hccss"
|
|
], function(declare, domStyle, query, registry, MenuItem){
|
|
|
|
// module:
|
|
// dijit/PopupMenuItem
|
|
|
|
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);
|
|
}
|
|
this.ownerDocumentBody.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);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/hccss':function(){
|
|
define("dojo/hccss", [
|
|
"require", // require.toUrl
|
|
"./_base/config", // config.blankGif
|
|
"./dom-class", // domClass.add
|
|
"./dom-style", // domStyle.getComputedStyle
|
|
"./has",
|
|
"./ready", // ready
|
|
"./_base/window" // win.body
|
|
], function(require, config, domClass, domStyle, has, ready, win){
|
|
|
|
// module:
|
|
// dojo/hccss
|
|
|
|
/*=====
|
|
return function(){
|
|
// summary:
|
|
// Test if computer is in high contrast mode (i.e. if browser is not displaying background images).
|
|
// Defines `has("highcontrast")` and sets `dj_a11y` CSS class on `<body>` if machine is in high contrast mode.
|
|
// Returns `has()` method;
|
|
};
|
|
=====*/
|
|
|
|
// Has() test for when background images aren't displayed. Don't call has("highcontrast") before dojo/domReady!.
|
|
has.add("highcontrast", function(){
|
|
// note: if multiple documents, doesn't matter which one we use
|
|
var div = win.doc.createElement("div");
|
|
div.style.cssText = "border: 1px solid; border-color:red green; position: absolute; height: 5px; top: -999px;" +
|
|
"background-image: url(" + (config.blankGif || require.toUrl("./resources/blank.gif")) + ");";
|
|
win.body().appendChild(div);
|
|
|
|
var cs = domStyle.getComputedStyle(div),
|
|
bkImg = cs.backgroundImage,
|
|
hc = (cs.borderTopColor == cs.borderRightColor) ||
|
|
(bkImg && (bkImg == "none" || bkImg == "url(invalid-url:)" ));
|
|
|
|
if(has("ie") <= 8){
|
|
div.outerHTML = ""; // prevent mixed-content warning, see http://support.microsoft.com/kb/925014
|
|
}else{
|
|
win.body().removeChild(div);
|
|
}
|
|
|
|
return hc;
|
|
});
|
|
|
|
// Priority is 90 to run ahead of parser priority of 100. For 2.0, remove the ready() call and instead
|
|
// change this module to depend on dojo/domReady!
|
|
ready(90, function(){
|
|
if(has("highcontrast")){
|
|
domClass.add(win.body(), "dj_a11y");
|
|
}
|
|
});
|
|
|
|
return has;
|
|
});
|
|
|
|
},
|
|
'dijit/form/RadioButton':function(){
|
|
define("dijit/form/RadioButton", [
|
|
"dojo/_base/declare", // declare
|
|
"./CheckBox",
|
|
"./_RadioButtonMixin"
|
|
], function(declare, CheckBox, _RadioButtonMixin){
|
|
|
|
// module:
|
|
// dijit/form/RadioButton
|
|
|
|
return declare("dijit.form.RadioButton", [CheckBox, _RadioButtonMixin], {
|
|
// summary:
|
|
// Same as an HTML radio, but with fancy styling.
|
|
|
|
baseClass: "dijitRadio"
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/main':function(){
|
|
define("dijit/main", [
|
|
"dojo/_base/kernel"
|
|
], function(dojo){
|
|
// module:
|
|
// dijit/main
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// The dijit package main module.
|
|
// Deprecated. Users should access individual modules (ex: dijit/registry) directly.
|
|
};
|
|
=====*/
|
|
|
|
return dojo.dijit;
|
|
});
|
|
|
|
},
|
|
'dijit/_OnDijitClickMixin':function(){
|
|
define("dijit/_OnDijitClickMixin", [
|
|
"dojo/on",
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/keys", // keys.ENTER keys.SPACE
|
|
"dojo/_base/declare", // declare
|
|
"dojo/has", // has("dom-addeventlistener")
|
|
"dojo/_base/unload", // unload.addOnWindowUnload
|
|
"dojo/_base/window", // win.doc.addEventListener win.doc.attachEvent win.doc.detachEvent
|
|
"./a11yclick"
|
|
], function(on, array, keys, declare, has, unload, win, a11yclick){
|
|
|
|
// module:
|
|
// dijit/_OnDijitClickMixin
|
|
|
|
var ret = declare("dijit._OnDijitClickMixin", null, {
|
|
connect: function(
|
|
/*Object|null*/ obj,
|
|
/*String|Function*/ event,
|
|
/*String|Function*/ method){
|
|
// summary:
|
|
// Connects specified obj/event to specified method of this object
|
|
// and registers for disconnect() on widget destroy.
|
|
// description:
|
|
// Provide widget-specific analog to connect.connect, except with the
|
|
// implicit use of this widget as the target object.
|
|
// This version of connect also provides a special "ondijitclick"
|
|
// event which triggers on a click or space or enter keyup.
|
|
// Events connected with `this.connect` are disconnected upon
|
|
// destruction.
|
|
// returns:
|
|
// A handle that can be passed to `disconnect` in order to disconnect before
|
|
// the widget is destroyed.
|
|
// example:
|
|
// | var btn = new Button();
|
|
// | // when foo.bar() is called, call the listener we're going to
|
|
// | // provide in the scope of btn
|
|
// | btn.connect(foo, "bar", function(){
|
|
// | console.debug(this.toString());
|
|
// | });
|
|
// tags:
|
|
// protected
|
|
|
|
return this.inherited(arguments, [obj, event == "ondijitclick" ? a11yclick : event, method]);
|
|
}
|
|
});
|
|
|
|
ret.a11yclick = a11yclick; // back compat
|
|
|
|
return ret;
|
|
});
|
|
|
|
},
|
|
'dijit/InlineEditBox':function(){
|
|
require({cache:{
|
|
'url:dijit/templates/InlineEditBox.html':"<span data-dojo-attach-point=\"editNode\" role=\"presentation\" class=\"dijitReset dijitInline dijitOffScreen\"\n\tdata-dojo-attach-event=\"onkeypress: _onKeyPress\"\n\t><span data-dojo-attach-point=\"editorPlaceholder\"></span\n\t><span data-dojo-attach-point=\"buttonContainer\"\n\t\t><button data-dojo-type=\"dijit/form/Button\" data-dojo-props=\"label: '${buttonSave}', 'class': 'saveButton'\"\n\t\t\tdata-dojo-attach-point=\"saveButton\" data-dojo-attach-event=\"onClick:save\"></button\n\t\t><button data-dojo-type=\"dijit/form/Button\" data-dojo-props=\"label: '${buttonCancel}', 'class': 'cancelButton'\"\n\t\t\tdata-dojo-attach-point=\"cancelButton\" data-dojo-attach-event=\"onClick:cancel\"></button\n\t></span\n></span>\n"}});
|
|
define("dijit/InlineEditBox", [
|
|
"require",
|
|
"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/sniff", // has("ie")
|
|
"dojo/when",
|
|
"./focus",
|
|
"./_Widget",
|
|
"./_TemplatedMixin",
|
|
"./_WidgetsInTemplateMixin",
|
|
"./_Container",
|
|
"./form/Button",
|
|
"./form/_TextBoxMixin",
|
|
"./form/TextBox",
|
|
"dojo/text!./templates/InlineEditBox.html",
|
|
"dojo/i18n!./nls/common"
|
|
], function(require, array, declare, domAttr, domClass, domConstruct, domStyle, event, i18n, kernel, keys, lang, has, when,
|
|
fm, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, _Container, Button, _TextBoxMixin, TextBox, template){
|
|
|
|
// module:
|
|
// dijit/InlineEditBox
|
|
|
|
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
|
|
// TODO: remove getObject() for 2.0
|
|
var Cls = typeof this.editor == "string" ? (lang.getObject(this.editor) || require(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", "position", "left", "top", "right", "bottom", "float", "clear", "display"], 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);
|
|
}
|
|
}
|
|
},
|
|
|
|
startup: function(){
|
|
this.editWidget.startup();
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
_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();
|
|
|
|
if(this.editWidget.focusNode){
|
|
// IE can take 30ms to report the focus event, but focus manager needs to know before a 0ms timeout.
|
|
fm._onFocusNode(this.editWidget.focusNode);
|
|
|
|
if(this.editWidget.focusNode.tagName == "INPUT"){
|
|
this.defer(function(){
|
|
_TextBoxMixin.selectInputText(this.editWidget.focusNode);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
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
|
|
// MID (ex: "dijit/form/TextBox") or constructor 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(/*===== params, srcNodeRef =====*/){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified:
|
|
//
|
|
// - use srcNodeRef.innerHTML as my value
|
|
// - replace srcNodeRef with my generated DOM tree
|
|
|
|
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 ? "true" : "false");
|
|
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...
|
|
this.defer("edit");
|
|
},
|
|
|
|
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._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.wrapperWidget._started){
|
|
this.wrapperWidget.startup();
|
|
}
|
|
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
|
|
// visibility: hidden removes focus outline
|
|
|
|
domClass.add(this.displayNode, "dijitOffScreen");
|
|
domClass.remove(ww.domNode, "dijitOffScreen");
|
|
domStyle.set(ww.domNode, { visibility: "visible" });
|
|
domAttr.set(this.displayNode, "tabIndex", "-1"); // needed by WebKit for TAB from editor to skip displayNode
|
|
|
|
// After edit widget has finished initializing (in particular need to wait for dijit.Editor),
|
|
// or immediately if there is no onLoadDeferred Deferred,
|
|
// replace the display widget with edit widget, leaving them both displayed for a brief time so that
|
|
// focus can be shifted without incident.
|
|
when(ww.editWidget.onLoadDeferred, lang.hitch(ww, function(){
|
|
this.defer(function(){ // defer needed so that the change of focus doesn't happen on mousedown which also sets focus
|
|
this.focus(); // both nodes are showing, so we can switch focus safely
|
|
this._resetValue = this.getValue();
|
|
});
|
|
}));
|
|
},
|
|
|
|
_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...
|
|
this.defer(function(){
|
|
if(this.wrapperWidget){
|
|
this.wrapperWidget.destroy();
|
|
delete this.wrapperWidget;
|
|
}
|
|
});
|
|
*/
|
|
}
|
|
},
|
|
|
|
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, { visibility: "hidden" }); // hide the editor from mouse/keyboard events
|
|
domClass.add(ww.domNode, "dijitOffScreen");
|
|
domClass.remove(this.displayNode, "dijitOffScreen");
|
|
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
|
|
this.defer(function(){
|
|
this.onChange(val);
|
|
}); // defer 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
|
|
this.defer("onCancel"); // defer 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/selector/acme':function(){
|
|
define("dojo/selector/acme", [
|
|
"../dom", "../sniff", "../_base/array", "../_base/lang", "../_base/window"
|
|
], function(dom, has, array, lang, win){
|
|
|
|
// module:
|
|
// dojo/selector/acme
|
|
|
|
/*
|
|
acme architectural overview:
|
|
|
|
acme is a relatively full-featured CSS3 query library. It is
|
|
designed to take any valid CSS3 selector and return the nodes matching
|
|
the selector. To do this quickly, it processes queries in several
|
|
steps, applying caching where profitable.
|
|
|
|
The steps (roughly in reverse order of the way they appear in the code):
|
|
1.) check to see if we already have a "query dispatcher"
|
|
- if so, use that with the given parameterization. Skip to step 4.
|
|
2.) attempt to determine which branch to dispatch the query to:
|
|
- JS (optimized DOM iteration)
|
|
- native (FF3.1+, Safari 3.1+, IE 8+)
|
|
3.) tokenize and convert to executable "query dispatcher"
|
|
- this is where the lion's share of the complexity in the
|
|
system lies. In the DOM version, the query dispatcher is
|
|
assembled as a chain of "yes/no" test functions pertaining to
|
|
a section of a simple query statement (".blah:nth-child(odd)"
|
|
but not "div div", which is 2 simple statements). Individual
|
|
statement dispatchers are cached (to prevent re-definition)
|
|
as are entire dispatch chains (to make re-execution of the
|
|
same query fast)
|
|
4.) the resulting query dispatcher is called in the passed scope
|
|
(by default the top-level document)
|
|
- for DOM queries, this results in a recursive, top-down
|
|
evaluation of nodes based on each simple query section
|
|
- for native implementations, this may mean working around spec
|
|
bugs. So be it.
|
|
5.) matched nodes are pruned to ensure they are unique (if necessary)
|
|
*/
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Toolkit aliases
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// if you are extracting acme for use in your own system, you will
|
|
// need to provide these methods and properties. No other porting should be
|
|
// necessary, save for configuring the system to use a class other than
|
|
// dojo/NodeList as the return instance instantiator
|
|
var trim = lang.trim;
|
|
var each = array.forEach;
|
|
|
|
var getDoc = function(){ return win.doc; };
|
|
// NOTE(alex): the spec is idiotic. CSS queries should ALWAYS be case-sensitive, but nooooooo
|
|
var cssCaseBug = (getDoc().compatMode) == "BackCompat";
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Global utilities
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
var specials = ">~+";
|
|
|
|
// global thunk to determine whether we should treat the current query as
|
|
// case sensitive or not. This switch is flipped by the query evaluator
|
|
// based on the document passed as the context to search.
|
|
var caseSensitive = false;
|
|
|
|
// how high?
|
|
var yesman = function(){ return true; };
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Tokenizer
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
var getQueryParts = function(query){
|
|
// summary:
|
|
// state machine for query tokenization
|
|
// description:
|
|
// instead of using a brittle and slow regex-based CSS parser,
|
|
// acme implements an AST-style query representation. This
|
|
// representation is only generated once per query. For example,
|
|
// the same query run multiple times or under different root nodes
|
|
// does not re-parse the selector expression but instead uses the
|
|
// cached data structure. The state machine implemented here
|
|
// terminates on the last " " (space) character and returns an
|
|
// ordered array of query component structures (or "parts"). Each
|
|
// part represents an operator or a simple CSS filtering
|
|
// expression. The structure for parts is documented in the code
|
|
// below.
|
|
|
|
|
|
// NOTE:
|
|
// this code is designed to run fast and compress well. Sacrifices
|
|
// to readability and maintainability have been made. Your best
|
|
// bet when hacking the tokenizer is to put The Donnas on *really*
|
|
// loud (may we recommend their "Spend The Night" release?) and
|
|
// just assume you're gonna make mistakes. Keep the unit tests
|
|
// open and run them frequently. Knowing is half the battle ;-)
|
|
if(specials.indexOf(query.slice(-1)) >= 0){
|
|
// if we end with a ">", "+", or "~", that means we're implicitly
|
|
// searching all children, so make it explicit
|
|
query += " * ";
|
|
}else{
|
|
// if you have not provided a terminator, one will be provided for
|
|
// you...
|
|
query += " ";
|
|
}
|
|
|
|
var ts = function(/*Integer*/ s, /*Integer*/ e){
|
|
// trim and slice.
|
|
|
|
// take an index to start a string slice from and an end position
|
|
// and return a trimmed copy of that sub-string
|
|
return trim(query.slice(s, e));
|
|
};
|
|
|
|
// the overall data graph of the full query, as represented by queryPart objects
|
|
var queryParts = [];
|
|
|
|
|
|
// state keeping vars
|
|
var inBrackets = -1, inParens = -1, inMatchFor = -1,
|
|
inPseudo = -1, inClass = -1, inId = -1, inTag = -1, currentQuoteChar,
|
|
lc = "", cc = "", pStart;
|
|
|
|
// iteration vars
|
|
var x = 0, // index in the query
|
|
ql = query.length,
|
|
currentPart = null, // data structure representing the entire clause
|
|
_cp = null; // the current pseudo or attr matcher
|
|
|
|
// several temporary variables are assigned to this structure during a
|
|
// potential sub-expression match:
|
|
// attr:
|
|
// a string representing the current full attribute match in a
|
|
// bracket expression
|
|
// type:
|
|
// if there's an operator in a bracket expression, this is
|
|
// used to keep track of it
|
|
// value:
|
|
// the internals of parenthetical expression for a pseudo. for
|
|
// :nth-child(2n+1), value might be "2n+1"
|
|
|
|
var endTag = function(){
|
|
// called when the tokenizer hits the end of a particular tag name.
|
|
// Re-sets state variables for tag matching and sets up the matcher
|
|
// to handle the next type of token (tag or operator).
|
|
if(inTag >= 0){
|
|
var tv = (inTag == x) ? null : ts(inTag, x); // .toLowerCase();
|
|
currentPart[ (specials.indexOf(tv) < 0) ? "tag" : "oper" ] = tv;
|
|
inTag = -1;
|
|
}
|
|
};
|
|
|
|
var endId = function(){
|
|
// called when the tokenizer might be at the end of an ID portion of a match
|
|
if(inId >= 0){
|
|
currentPart.id = ts(inId, x).replace(/\\/g, "");
|
|
inId = -1;
|
|
}
|
|
};
|
|
|
|
var endClass = function(){
|
|
// called when the tokenizer might be at the end of a class name
|
|
// match. CSS allows for multiple classes, so we augment the
|
|
// current item with another class in its list
|
|
if(inClass >= 0){
|
|
currentPart.classes.push(ts(inClass + 1, x).replace(/\\/g, ""));
|
|
inClass = -1;
|
|
}
|
|
};
|
|
|
|
var endAll = function(){
|
|
// at the end of a simple fragment, so wall off the matches
|
|
endId();
|
|
endTag();
|
|
endClass();
|
|
};
|
|
|
|
var endPart = function(){
|
|
endAll();
|
|
if(inPseudo >= 0){
|
|
currentPart.pseudos.push({ name: ts(inPseudo + 1, x) });
|
|
}
|
|
// hint to the selector engine to tell it whether or not it
|
|
// needs to do any iteration. Many simple selectors don't, and
|
|
// we can avoid significant construction-time work by advising
|
|
// the system to skip them
|
|
currentPart.loops = (
|
|
currentPart.pseudos.length ||
|
|
currentPart.attrs.length ||
|
|
currentPart.classes.length );
|
|
|
|
currentPart.oquery = currentPart.query = ts(pStart, x); // save the full expression as a string
|
|
|
|
|
|
// otag/tag are hints to suggest to the system whether or not
|
|
// it's an operator or a tag. We save a copy of otag since the
|
|
// tag name is cast to upper-case in regular HTML matches. The
|
|
// system has a global switch to figure out if the current
|
|
// expression needs to be case sensitive or not and it will use
|
|
// otag or tag accordingly
|
|
currentPart.otag = currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*");
|
|
|
|
if(currentPart.tag){
|
|
// if we're in a case-insensitive HTML doc, we likely want
|
|
// the toUpperCase when matching on element.tagName. If we
|
|
// do it here, we can skip the string op per node
|
|
// comparison
|
|
currentPart.tag = currentPart.tag.toUpperCase();
|
|
}
|
|
|
|
// add the part to the list
|
|
if(queryParts.length && (queryParts[queryParts.length-1].oper)){
|
|
// operators are always infix, so we remove them from the
|
|
// list and attach them to the next match. The evaluator is
|
|
// responsible for sorting out how to handle them.
|
|
currentPart.infixOper = queryParts.pop();
|
|
currentPart.query = currentPart.infixOper.query + " " + currentPart.query;
|
|
/*
|
|
console.debug( "swapping out the infix",
|
|
currentPart.infixOper,
|
|
"and attaching it to",
|
|
currentPart);
|
|
*/
|
|
}
|
|
queryParts.push(currentPart);
|
|
|
|
currentPart = null;
|
|
};
|
|
|
|
// iterate over the query, character by character, building up a
|
|
// list of query part objects
|
|
for(; lc=cc, cc=query.charAt(x), x < ql; x++){
|
|
// cc: the current character in the match
|
|
// lc: the last character (if any)
|
|
|
|
// someone is trying to escape something, so don't try to match any
|
|
// fragments. We assume we're inside a literal.
|
|
if(lc == "\\"){ continue; }
|
|
if(!currentPart){ // a part was just ended or none has yet been created
|
|
// NOTE: I hate all this alloc, but it's shorter than writing tons of if's
|
|
pStart = x;
|
|
// rules describe full CSS sub-expressions, like:
|
|
// #someId
|
|
// .className:first-child
|
|
// but not:
|
|
// thinger > div.howdy[type=thinger]
|
|
// the indidual components of the previous query would be
|
|
// split into 3 parts that would be represented a structure like:
|
|
// [
|
|
// {
|
|
// query: "thinger",
|
|
// tag: "thinger",
|
|
// },
|
|
// {
|
|
// query: "div.howdy[type=thinger]",
|
|
// classes: ["howdy"],
|
|
// infixOper: {
|
|
// query: ">",
|
|
// oper: ">",
|
|
// }
|
|
// },
|
|
// ]
|
|
currentPart = {
|
|
query: null, // the full text of the part's rule
|
|
pseudos: [], // CSS supports multiple pseud-class matches in a single rule
|
|
attrs: [], // CSS supports multi-attribute match, so we need an array
|
|
classes: [], // class matches may be additive, e.g.: .thinger.blah.howdy
|
|
tag: null, // only one tag...
|
|
oper: null, // ...or operator per component. Note that these wind up being exclusive.
|
|
id: null, // the id component of a rule
|
|
getTag: function(){
|
|
return caseSensitive ? this.otag : this.tag;
|
|
}
|
|
};
|
|
|
|
// if we don't have a part, we assume we're going to start at
|
|
// the beginning of a match, which should be a tag name. This
|
|
// might fault a little later on, but we detect that and this
|
|
// iteration will still be fine.
|
|
inTag = x;
|
|
}
|
|
|
|
// Skip processing all quoted characters.
|
|
// If we are inside quoted text then currentQuoteChar stores the character that began the quote,
|
|
// thus that character that will end it.
|
|
if(currentQuoteChar){
|
|
if(cc == currentQuoteChar){
|
|
currentQuoteChar = null;
|
|
}
|
|
continue;
|
|
}else if (cc == "'" || cc == '"'){
|
|
currentQuoteChar = cc;
|
|
continue;
|
|
}
|
|
|
|
if(inBrackets >= 0){
|
|
// look for a the close first
|
|
if(cc == "]"){ // if we're in a [...] clause and we end, do assignment
|
|
if(!_cp.attr){
|
|
// no attribute match was previously begun, so we
|
|
// assume this is an attribute existence match in the
|
|
// form of [someAttributeName]
|
|
_cp.attr = ts(inBrackets+1, x);
|
|
}else{
|
|
// we had an attribute already, so we know that we're
|
|
// matching some sort of value, as in [attrName=howdy]
|
|
_cp.matchFor = ts((inMatchFor||inBrackets+1), x);
|
|
}
|
|
var cmf = _cp.matchFor;
|
|
if(cmf){
|
|
// try to strip quotes from the matchFor value. We want
|
|
// [attrName=howdy] to match the same
|
|
// as [attrName = 'howdy' ]
|
|
if( (cmf.charAt(0) == '"') || (cmf.charAt(0) == "'") ){
|
|
_cp.matchFor = cmf.slice(1, -1);
|
|
}
|
|
}
|
|
// remove backslash escapes from an attribute match, since DOM
|
|
// querying will get attribute values without backslashes
|
|
if(_cp.matchFor){
|
|
_cp.matchFor = _cp.matchFor.replace(/\\/g, "");
|
|
}
|
|
|
|
// end the attribute by adding it to the list of attributes.
|
|
currentPart.attrs.push(_cp);
|
|
_cp = null; // necessary?
|
|
inBrackets = inMatchFor = -1;
|
|
}else if(cc == "="){
|
|
// if the last char was an operator prefix, make sure we
|
|
// record it along with the "=" operator.
|
|
var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : "";
|
|
_cp.type = addToCc+cc;
|
|
_cp.attr = ts(inBrackets+1, x-addToCc.length);
|
|
inMatchFor = x+1;
|
|
}
|
|
// now look for other clause parts
|
|
}else if(inParens >= 0){
|
|
// if we're in a parenthetical expression, we need to figure
|
|
// out if it's attached to a pseudo-selector rule like
|
|
// :nth-child(1)
|
|
if(cc == ")"){
|
|
if(inPseudo >= 0){
|
|
_cp.value = ts(inParens+1, x);
|
|
}
|
|
inPseudo = inParens = -1;
|
|
}
|
|
}else if(cc == "#"){
|
|
// start of an ID match
|
|
endAll();
|
|
inId = x+1;
|
|
}else if(cc == "."){
|
|
// start of a class match
|
|
endAll();
|
|
inClass = x;
|
|
}else if(cc == ":"){
|
|
// start of a pseudo-selector match
|
|
endAll();
|
|
inPseudo = x;
|
|
}else if(cc == "["){
|
|
// start of an attribute match.
|
|
endAll();
|
|
inBrackets = x;
|
|
// provide a new structure for the attribute match to fill-in
|
|
_cp = {
|
|
/*=====
|
|
attr: null, type: null, matchFor: null
|
|
=====*/
|
|
};
|
|
}else if(cc == "("){
|
|
// we really only care if we've entered a parenthetical
|
|
// expression if we're already inside a pseudo-selector match
|
|
if(inPseudo >= 0){
|
|
// provide a new structure for the pseudo match to fill-in
|
|
_cp = {
|
|
name: ts(inPseudo+1, x),
|
|
value: null
|
|
};
|
|
currentPart.pseudos.push(_cp);
|
|
}
|
|
inParens = x;
|
|
}else if(
|
|
(cc == " ") &&
|
|
// if it's a space char and the last char is too, consume the
|
|
// current one without doing more work
|
|
(lc != cc)
|
|
){
|
|
endPart();
|
|
}
|
|
}
|
|
return queryParts;
|
|
};
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// DOM query infrastructure
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
var agree = function(first, second){
|
|
// the basic building block of the yes/no chaining system. agree(f1,
|
|
// f2) generates a new function which returns the boolean results of
|
|
// both of the passed functions to a single logical-anded result. If
|
|
// either are not passed, the other is used exclusively.
|
|
if(!first){ return second; }
|
|
if(!second){ return first; }
|
|
|
|
return function(){
|
|
return first.apply(window, arguments) && second.apply(window, arguments);
|
|
};
|
|
};
|
|
|
|
var getArr = function(i, arr){
|
|
// helps us avoid array alloc when we don't need it
|
|
var r = arr||[]; // FIXME: should this be 'new d._NodeListCtor()' ?
|
|
if(i){ r.push(i); }
|
|
return r;
|
|
};
|
|
|
|
var _isElement = function(n){ return (1 == n.nodeType); };
|
|
|
|
// FIXME: need to coalesce _getAttr with defaultGetter
|
|
var blank = "";
|
|
var _getAttr = function(elem, attr){
|
|
if(!elem){ return blank; }
|
|
if(attr == "class"){
|
|
return elem.className || blank;
|
|
}
|
|
if(attr == "for"){
|
|
return elem.htmlFor || blank;
|
|
}
|
|
if(attr == "style"){
|
|
return elem.style.cssText || blank;
|
|
}
|
|
return (caseSensitive ? elem.getAttribute(attr) : elem.getAttribute(attr, 2)) || blank;
|
|
};
|
|
|
|
var attrs = {
|
|
"*=": function(attr, value){
|
|
return function(elem){
|
|
// E[foo*="bar"]
|
|
// an E element whose "foo" attribute value contains
|
|
// the substring "bar"
|
|
return (_getAttr(elem, attr).indexOf(value)>=0);
|
|
};
|
|
},
|
|
"^=": function(attr, value){
|
|
// E[foo^="bar"]
|
|
// an E element whose "foo" attribute value begins exactly
|
|
// with the string "bar"
|
|
return function(elem){
|
|
return (_getAttr(elem, attr).indexOf(value)==0);
|
|
};
|
|
},
|
|
"$=": function(attr, value){
|
|
// E[foo$="bar"]
|
|
// an E element whose "foo" attribute value ends exactly
|
|
// with the string "bar"
|
|
return function(elem){
|
|
var ea = " "+_getAttr(elem, attr);
|
|
var lastIndex = ea.lastIndexOf(value);
|
|
return lastIndex > -1 && (lastIndex==(ea.length-value.length));
|
|
};
|
|
},
|
|
"~=": function(attr, value){
|
|
// E[foo~="bar"]
|
|
// an E element whose "foo" attribute value is a list of
|
|
// space-separated values, one of which is exactly equal
|
|
// to "bar"
|
|
|
|
// return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]";
|
|
var tval = " "+value+" ";
|
|
return function(elem){
|
|
var ea = " "+_getAttr(elem, attr)+" ";
|
|
return (ea.indexOf(tval)>=0);
|
|
};
|
|
},
|
|
"|=": function(attr, value){
|
|
// E[hreflang|="en"]
|
|
// an E element whose "hreflang" attribute has a
|
|
// hyphen-separated list of values beginning (from the
|
|
// left) with "en"
|
|
var valueDash = value+"-";
|
|
return function(elem){
|
|
var ea = _getAttr(elem, attr);
|
|
return (
|
|
(ea == value) ||
|
|
(ea.indexOf(valueDash)==0)
|
|
);
|
|
};
|
|
},
|
|
"=": function(attr, value){
|
|
return function(elem){
|
|
return (_getAttr(elem, attr) == value);
|
|
};
|
|
}
|
|
};
|
|
|
|
// avoid testing for node type if we can. Defining this in the negative
|
|
// here to avoid negation in the fast path.
|
|
var _noNES = (typeof getDoc().firstChild.nextElementSibling == "undefined");
|
|
var _ns = !_noNES ? "nextElementSibling" : "nextSibling";
|
|
var _ps = !_noNES ? "previousElementSibling" : "previousSibling";
|
|
var _simpleNodeTest = (_noNES ? _isElement : yesman);
|
|
|
|
var _lookLeft = function(node){
|
|
// look left
|
|
while(node = node[_ps]){
|
|
if(_simpleNodeTest(node)){ return false; }
|
|
}
|
|
return true;
|
|
};
|
|
|
|
var _lookRight = function(node){
|
|
// look right
|
|
while(node = node[_ns]){
|
|
if(_simpleNodeTest(node)){ return false; }
|
|
}
|
|
return true;
|
|
};
|
|
|
|
var getNodeIndex = function(node){
|
|
var root = node.parentNode;
|
|
root = root.nodeType != 7 ? root : root.nextSibling; // PROCESSING_INSTRUCTION_NODE
|
|
var i = 0,
|
|
tret = root.children || root.childNodes,
|
|
ci = (node["_i"]||node.getAttribute("_i")||-1),
|
|
cl = (root["_l"]|| (typeof root.getAttribute !== "undefined" ? root.getAttribute("_l") : -1));
|
|
|
|
if(!tret){ return -1; }
|
|
var l = tret.length;
|
|
|
|
// we calculate the parent length as a cheap way to invalidate the
|
|
// cache. It's not 100% accurate, but it's much more honest than what
|
|
// other libraries do
|
|
if( cl == l && ci >= 0 && cl >= 0 ){
|
|
// if it's legit, tag and release
|
|
return ci;
|
|
}
|
|
|
|
// else re-key things
|
|
if(has("ie") && typeof root.setAttribute !== "undefined"){
|
|
root.setAttribute("_l", l);
|
|
}else{
|
|
root["_l"] = l;
|
|
}
|
|
ci = -1;
|
|
for(var te = root["firstElementChild"]||root["firstChild"]; te; te = te[_ns]){
|
|
if(_simpleNodeTest(te)){
|
|
if(has("ie")){
|
|
te.setAttribute("_i", ++i);
|
|
}else{
|
|
te["_i"] = ++i;
|
|
}
|
|
if(node === te){
|
|
// NOTE:
|
|
// shortcutting the return at this step in indexing works
|
|
// very well for benchmarking but we avoid it here since
|
|
// it leads to potential O(n^2) behavior in sequential
|
|
// getNodexIndex operations on a previously un-indexed
|
|
// parent. We may revisit this at a later time, but for
|
|
// now we just want to get the right answer more often
|
|
// than not.
|
|
ci = i;
|
|
}
|
|
}
|
|
}
|
|
return ci;
|
|
};
|
|
|
|
var isEven = function(elem){
|
|
return !((getNodeIndex(elem)) % 2);
|
|
};
|
|
|
|
var isOdd = function(elem){
|
|
return ((getNodeIndex(elem)) % 2);
|
|
};
|
|
|
|
var pseudos = {
|
|
"checked": function(name, condition){
|
|
return function(elem){
|
|
return !!("checked" in elem ? elem.checked : elem.selected);
|
|
};
|
|
},
|
|
"disabled": function(name, condition){
|
|
return function(elem){
|
|
return elem.disabled;
|
|
};
|
|
},
|
|
"enabled": function(name, condition){
|
|
return function(elem){
|
|
return !elem.disabled;
|
|
};
|
|
},
|
|
"first-child": function(){ return _lookLeft; },
|
|
"last-child": function(){ return _lookRight; },
|
|
"only-child": function(name, condition){
|
|
return function(node){
|
|
return _lookLeft(node) && _lookRight(node);
|
|
};
|
|
},
|
|
"empty": function(name, condition){
|
|
return function(elem){
|
|
// DomQuery and jQuery get this wrong, oddly enough.
|
|
// The CSS 3 selectors spec is pretty explicit about it, too.
|
|
var cn = elem.childNodes;
|
|
var cnl = elem.childNodes.length;
|
|
// if(!cnl){ return true; }
|
|
for(var x=cnl-1; x >= 0; x--){
|
|
var nt = cn[x].nodeType;
|
|
if((nt === 1)||(nt == 3)){ return false; }
|
|
}
|
|
return true;
|
|
};
|
|
},
|
|
"contains": function(name, condition){
|
|
var cz = condition.charAt(0);
|
|
if( cz == '"' || cz == "'" ){ //remove quote
|
|
condition = condition.slice(1, -1);
|
|
}
|
|
return function(elem){
|
|
return (elem.innerHTML.indexOf(condition) >= 0);
|
|
};
|
|
},
|
|
"not": function(name, condition){
|
|
var p = getQueryParts(condition)[0];
|
|
var ignores = { el: 1 };
|
|
if(p.tag != "*"){
|
|
ignores.tag = 1;
|
|
}
|
|
if(!p.classes.length){
|
|
ignores.classes = 1;
|
|
}
|
|
var ntf = getSimpleFilterFunc(p, ignores);
|
|
return function(elem){
|
|
return (!ntf(elem));
|
|
};
|
|
},
|
|
"nth-child": function(name, condition){
|
|
var pi = parseInt;
|
|
// avoid re-defining function objects if we can
|
|
if(condition == "odd"){
|
|
return isOdd;
|
|
}else if(condition == "even"){
|
|
return isEven;
|
|
}
|
|
// FIXME: can we shorten this?
|
|
if(condition.indexOf("n") != -1){
|
|
var tparts = condition.split("n", 2);
|
|
var pred = tparts[0] ? ((tparts[0] == '-') ? -1 : pi(tparts[0])) : 1;
|
|
var idx = tparts[1] ? pi(tparts[1]) : 0;
|
|
var lb = 0, ub = -1;
|
|
if(pred > 0){
|
|
if(idx < 0){
|
|
idx = (idx % pred) && (pred + (idx % pred));
|
|
}else if(idx>0){
|
|
if(idx >= pred){
|
|
lb = idx - idx % pred;
|
|
}
|
|
idx = idx % pred;
|
|
}
|
|
}else if(pred<0){
|
|
pred *= -1;
|
|
// idx has to be greater than 0 when pred is negative;
|
|
// shall we throw an error here?
|
|
if(idx > 0){
|
|
ub = idx;
|
|
idx = idx % pred;
|
|
}
|
|
}
|
|
if(pred > 0){
|
|
return function(elem){
|
|
var i = getNodeIndex(elem);
|
|
return (i>=lb) && (ub<0 || i<=ub) && ((i % pred) == idx);
|
|
};
|
|
}else{
|
|
condition = idx;
|
|
}
|
|
}
|
|
var ncount = pi(condition);
|
|
return function(elem){
|
|
return (getNodeIndex(elem) == ncount);
|
|
};
|
|
}
|
|
};
|
|
|
|
var defaultGetter = (has("ie") < 9 || has("ie") == 9 && has("quirks")) ? function(cond){
|
|
var clc = cond.toLowerCase();
|
|
if(clc == "class"){ cond = "className"; }
|
|
return function(elem){
|
|
return (caseSensitive ? elem.getAttribute(cond) : elem[cond]||elem[clc]);
|
|
};
|
|
} : function(cond){
|
|
return function(elem){
|
|
return (elem && elem.getAttribute && elem.hasAttribute(cond));
|
|
};
|
|
};
|
|
|
|
var getSimpleFilterFunc = function(query, ignores){
|
|
// generates a node tester function based on the passed query part. The
|
|
// query part is one of the structures generated by the query parser
|
|
// when it creates the query AST. The "ignores" object specifies which
|
|
// (if any) tests to skip, allowing the system to avoid duplicating
|
|
// work where it may have already been taken into account by other
|
|
// factors such as how the nodes to test were fetched in the first
|
|
// place
|
|
if(!query){ return yesman; }
|
|
ignores = ignores||{};
|
|
|
|
var ff = null;
|
|
|
|
if(!("el" in ignores)){
|
|
ff = agree(ff, _isElement);
|
|
}
|
|
|
|
if(!("tag" in ignores)){
|
|
if(query.tag != "*"){
|
|
ff = agree(ff, function(elem){
|
|
return (elem && ((caseSensitive ? elem.tagName : elem.tagName.toUpperCase()) == query.getTag()));
|
|
});
|
|
}
|
|
}
|
|
|
|
if(!("classes" in ignores)){
|
|
each(query.classes, function(cname, idx, arr){
|
|
// get the class name
|
|
/*
|
|
var isWildcard = cname.charAt(cname.length-1) == "*";
|
|
if(isWildcard){
|
|
cname = cname.substr(0, cname.length-1);
|
|
}
|
|
// I dislike the regex thing, even if memoized in a cache, but it's VERY short
|
|
var re = new RegExp("(?:^|\\s)" + cname + (isWildcard ? ".*" : "") + "(?:\\s|$)");
|
|
*/
|
|
var re = new RegExp("(?:^|\\s)" + cname + "(?:\\s|$)");
|
|
ff = agree(ff, function(elem){
|
|
return re.test(elem.className);
|
|
});
|
|
ff.count = idx;
|
|
});
|
|
}
|
|
|
|
if(!("pseudos" in ignores)){
|
|
each(query.pseudos, function(pseudo){
|
|
var pn = pseudo.name;
|
|
if(pseudos[pn]){
|
|
ff = agree(ff, pseudos[pn](pn, pseudo.value));
|
|
}
|
|
});
|
|
}
|
|
|
|
if(!("attrs" in ignores)){
|
|
each(query.attrs, function(attr){
|
|
var matcher;
|
|
var a = attr.attr;
|
|
// type, attr, matchFor
|
|
if(attr.type && attrs[attr.type]){
|
|
matcher = attrs[attr.type](a, attr.matchFor);
|
|
}else if(a.length){
|
|
matcher = defaultGetter(a);
|
|
}
|
|
if(matcher){
|
|
ff = agree(ff, matcher);
|
|
}
|
|
});
|
|
}
|
|
|
|
if(!("id" in ignores)){
|
|
if(query.id){
|
|
ff = agree(ff, function(elem){
|
|
return (!!elem && (elem.id == query.id));
|
|
});
|
|
}
|
|
}
|
|
|
|
if(!ff){
|
|
if(!("default" in ignores)){
|
|
ff = yesman;
|
|
}
|
|
}
|
|
return ff;
|
|
};
|
|
|
|
var _nextSibling = function(filterFunc){
|
|
return function(node, ret, bag){
|
|
while(node = node[_ns]){
|
|
if(_noNES && (!_isElement(node))){ continue; }
|
|
if(
|
|
(!bag || _isUnique(node, bag)) &&
|
|
filterFunc(node)
|
|
){
|
|
ret.push(node);
|
|
}
|
|
break;
|
|
}
|
|
return ret;
|
|
};
|
|
};
|
|
|
|
var _nextSiblings = function(filterFunc){
|
|
return function(root, ret, bag){
|
|
var te = root[_ns];
|
|
while(te){
|
|
if(_simpleNodeTest(te)){
|
|
if(bag && !_isUnique(te, bag)){
|
|
break;
|
|
}
|
|
if(filterFunc(te)){
|
|
ret.push(te);
|
|
}
|
|
}
|
|
te = te[_ns];
|
|
}
|
|
return ret;
|
|
};
|
|
};
|
|
|
|
// get an array of child *elements*, skipping text and comment nodes
|
|
var _childElements = function(filterFunc){
|
|
filterFunc = filterFunc||yesman;
|
|
return function(root, ret, bag){
|
|
// get an array of child elements, skipping text and comment nodes
|
|
var te, x = 0, tret = root.children || root.childNodes;
|
|
while(te = tret[x++]){
|
|
if(
|
|
_simpleNodeTest(te) &&
|
|
(!bag || _isUnique(te, bag)) &&
|
|
(filterFunc(te, x))
|
|
){
|
|
ret.push(te);
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
};
|
|
|
|
// test to see if node is below root
|
|
var _isDescendant = function(node, root){
|
|
var pn = node.parentNode;
|
|
while(pn){
|
|
if(pn == root){
|
|
break;
|
|
}
|
|
pn = pn.parentNode;
|
|
}
|
|
return !!pn;
|
|
};
|
|
|
|
var _getElementsFuncCache = {};
|
|
|
|
var getElementsFunc = function(query){
|
|
var retFunc = _getElementsFuncCache[query.query];
|
|
// if we've got a cached dispatcher, just use that
|
|
if(retFunc){ return retFunc; }
|
|
// else, generate a new on
|
|
|
|
// NOTE:
|
|
// this function returns a function that searches for nodes and
|
|
// filters them. The search may be specialized by infix operators
|
|
// (">", "~", or "+") else it will default to searching all
|
|
// descendants (the " " selector). Once a group of children is
|
|
// found, a test function is applied to weed out the ones we
|
|
// don't want. Many common cases can be fast-pathed. We spend a
|
|
// lot of cycles to create a dispatcher that doesn't do more work
|
|
// than necessary at any point since, unlike this function, the
|
|
// dispatchers will be called every time. The logic of generating
|
|
// efficient dispatchers looks like this in pseudo code:
|
|
//
|
|
// # if it's a purely descendant query (no ">", "+", or "~" modifiers)
|
|
// if infixOperator == " ":
|
|
// if only(id):
|
|
// return def(root):
|
|
// return d.byId(id, root);
|
|
//
|
|
// elif id:
|
|
// return def(root):
|
|
// return filter(d.byId(id, root));
|
|
//
|
|
// elif cssClass && getElementsByClassName:
|
|
// return def(root):
|
|
// return filter(root.getElementsByClassName(cssClass));
|
|
//
|
|
// elif only(tag):
|
|
// return def(root):
|
|
// return root.getElementsByTagName(tagName);
|
|
//
|
|
// else:
|
|
// # search by tag name, then filter
|
|
// return def(root):
|
|
// return filter(root.getElementsByTagName(tagName||"*"));
|
|
//
|
|
// elif infixOperator == ">":
|
|
// # search direct children
|
|
// return def(root):
|
|
// return filter(root.children);
|
|
//
|
|
// elif infixOperator == "+":
|
|
// # search next sibling
|
|
// return def(root):
|
|
// return filter(root.nextElementSibling);
|
|
//
|
|
// elif infixOperator == "~":
|
|
// # search rightward siblings
|
|
// return def(root):
|
|
// return filter(nextSiblings(root));
|
|
|
|
var io = query.infixOper;
|
|
var oper = (io ? io.oper : "");
|
|
// the default filter func which tests for all conditions in the query
|
|
// part. This is potentially inefficient, so some optimized paths may
|
|
// re-define it to test fewer things.
|
|
var filterFunc = getSimpleFilterFunc(query, { el: 1 });
|
|
var qt = query.tag;
|
|
var wildcardTag = ("*" == qt);
|
|
var ecs = getDoc()["getElementsByClassName"];
|
|
|
|
if(!oper){
|
|
// if there's no infix operator, then it's a descendant query. ID
|
|
// and "elements by class name" variants can be accelerated so we
|
|
// call them out explicitly:
|
|
if(query.id){
|
|
// testing shows that the overhead of yesman() is acceptable
|
|
// and can save us some bytes vs. re-defining the function
|
|
// everywhere.
|
|
filterFunc = (!query.loops && wildcardTag) ?
|
|
yesman :
|
|
getSimpleFilterFunc(query, { el: 1, id: 1 });
|
|
|
|
retFunc = function(root, arr){
|
|
var te = dom.byId(query.id, (root.ownerDocument||root));
|
|
if(!te || !filterFunc(te)){ return; }
|
|
if(9 == root.nodeType){ // if root's a doc, we just return directly
|
|
return getArr(te, arr);
|
|
}else{ // otherwise check ancestry
|
|
if(_isDescendant(te, root)){
|
|
return getArr(te, arr);
|
|
}
|
|
}
|
|
};
|
|
}else if(
|
|
ecs &&
|
|
// isAlien check. Workaround for Prototype.js being totally evil/dumb.
|
|
/\{\s*\[native code\]\s*\}/.test(String(ecs)) &&
|
|
query.classes.length &&
|
|
!cssCaseBug
|
|
){
|
|
// it's a class-based query and we've got a fast way to run it.
|
|
|
|
// ignore class and ID filters since we will have handled both
|
|
filterFunc = getSimpleFilterFunc(query, { el: 1, classes: 1, id: 1 });
|
|
var classesString = query.classes.join(" ");
|
|
retFunc = function(root, arr, bag){
|
|
var ret = getArr(0, arr), te, x=0;
|
|
var tret = root.getElementsByClassName(classesString);
|
|
while((te = tret[x++])){
|
|
if(filterFunc(te, root) && _isUnique(te, bag)){
|
|
ret.push(te);
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
}else if(!wildcardTag && !query.loops){
|
|
// it's tag only. Fast-path it.
|
|
retFunc = function(root, arr, bag){
|
|
var ret = getArr(0, arr), te, x=0;
|
|
var tag = query.getTag(),
|
|
tret = tag ? root.getElementsByTagName(tag) : [];
|
|
while((te = tret[x++])){
|
|
if(_isUnique(te, bag)){
|
|
ret.push(te);
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
}else{
|
|
// the common case:
|
|
// a descendant selector without a fast path. By now it's got
|
|
// to have a tag selector, even if it's just "*" so we query
|
|
// by that and filter
|
|
filterFunc = getSimpleFilterFunc(query, { el: 1, tag: 1, id: 1 });
|
|
retFunc = function(root, arr, bag){
|
|
var ret = getArr(0, arr), te, x=0;
|
|
// we use getTag() to avoid case sensitivity issues
|
|
var tag = query.getTag(),
|
|
tret = tag ? root.getElementsByTagName(tag) : [];
|
|
while((te = tret[x++])){
|
|
if(filterFunc(te, root) && _isUnique(te, bag)){
|
|
ret.push(te);
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
}
|
|
}else{
|
|
// the query is scoped in some way. Instead of querying by tag we
|
|
// use some other collection to find candidate nodes
|
|
var skipFilters = { el: 1 };
|
|
if(wildcardTag){
|
|
skipFilters.tag = 1;
|
|
}
|
|
filterFunc = getSimpleFilterFunc(query, skipFilters);
|
|
if("+" == oper){
|
|
retFunc = _nextSibling(filterFunc);
|
|
}else if("~" == oper){
|
|
retFunc = _nextSiblings(filterFunc);
|
|
}else if(">" == oper){
|
|
retFunc = _childElements(filterFunc);
|
|
}
|
|
}
|
|
// cache it and return
|
|
return _getElementsFuncCache[query.query] = retFunc;
|
|
};
|
|
|
|
var filterDown = function(root, queryParts){
|
|
// NOTE:
|
|
// this is the guts of the DOM query system. It takes a list of
|
|
// parsed query parts and a root and finds children which match
|
|
// the selector represented by the parts
|
|
var candidates = getArr(root), qp, x, te, qpl = queryParts.length, bag, ret;
|
|
|
|
for(var i = 0; i < qpl; i++){
|
|
ret = [];
|
|
qp = queryParts[i];
|
|
x = candidates.length - 1;
|
|
if(x > 0){
|
|
// if we have more than one root at this level, provide a new
|
|
// hash to use for checking group membership but tell the
|
|
// system not to post-filter us since we will already have been
|
|
// guaranteed to be unique
|
|
bag = {};
|
|
ret.nozip = true;
|
|
}
|
|
var gef = getElementsFunc(qp);
|
|
for(var j = 0; (te = candidates[j]); j++){
|
|
// for every root, get the elements that match the descendant
|
|
// selector, adding them to the "ret" array and filtering them
|
|
// via membership in this level's bag. If there are more query
|
|
// parts, then this level's return will be used as the next
|
|
// level's candidates
|
|
gef(te, ret, bag);
|
|
}
|
|
if(!ret.length){ break; }
|
|
candidates = ret;
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// the query runner
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// these are the primary caches for full-query results. The query
|
|
// dispatcher functions are generated then stored here for hash lookup in
|
|
// the future
|
|
var _queryFuncCacheDOM = {},
|
|
_queryFuncCacheQSA = {};
|
|
|
|
// this is the second level of splitting, from full-length queries (e.g.,
|
|
// "div.foo .bar") into simple query expressions (e.g., ["div.foo",
|
|
// ".bar"])
|
|
var getStepQueryFunc = function(query){
|
|
var qparts = getQueryParts(trim(query));
|
|
|
|
// if it's trivial, avoid iteration and zipping costs
|
|
if(qparts.length == 1){
|
|
// we optimize this case here to prevent dispatch further down the
|
|
// chain, potentially slowing things down. We could more elegantly
|
|
// handle this in filterDown(), but it's slower for simple things
|
|
// that need to be fast (e.g., "#someId").
|
|
var tef = getElementsFunc(qparts[0]);
|
|
return function(root){
|
|
var r = tef(root, []);
|
|
if(r){ r.nozip = true; }
|
|
return r;
|
|
};
|
|
}
|
|
|
|
// otherwise, break it up and return a runner that iterates over the parts recursively
|
|
return function(root){
|
|
return filterDown(root, qparts);
|
|
};
|
|
};
|
|
|
|
// NOTES:
|
|
// * we can't trust QSA for anything but document-rooted queries, so
|
|
// caching is split into DOM query evaluators and QSA query evaluators
|
|
// * caching query results is dirty and leak-prone (or, at a minimum,
|
|
// prone to unbounded growth). Other toolkits may go this route, but
|
|
// they totally destroy their own ability to manage their memory
|
|
// footprint. If we implement it, it should only ever be with a fixed
|
|
// total element reference # limit and an LRU-style algorithm since JS
|
|
// has no weakref support. Caching compiled query evaluators is also
|
|
// potentially problematic, but even on large documents the size of the
|
|
// query evaluators is often < 100 function objects per evaluator (and
|
|
// LRU can be applied if it's ever shown to be an issue).
|
|
// * since IE's QSA support is currently only for HTML documents and even
|
|
// then only in IE 8's "standards mode", we have to detect our dispatch
|
|
// route at query time and keep 2 separate caches. Ugg.
|
|
|
|
// we need to determine if we think we can run a given query via
|
|
// querySelectorAll or if we'll need to fall back on DOM queries to get
|
|
// there. We need a lot of information about the environment and the query
|
|
// to make the determination (e.g. does it support QSA, does the query in
|
|
// question work in the native QSA impl, etc.).
|
|
|
|
// IE QSA queries may incorrectly include comment nodes, so we throw the
|
|
// zipping function into "remove" comments mode instead of the normal "skip
|
|
// it" which every other QSA-clued browser enjoys
|
|
var noZip = has("ie") ? "commentStrip" : "nozip";
|
|
|
|
var qsa = "querySelectorAll";
|
|
var qsaAvail = !!getDoc()[qsa];
|
|
|
|
//Don't bother with n+3 type of matches, IE complains if we modify those.
|
|
var infixSpaceRe = /\\[>~+]|n\+\d|([^ \\])?([>~+])([^ =])?/g;
|
|
var infixSpaceFunc = function(match, pre, ch, post){
|
|
return ch ? (pre ? pre + " " : "") + ch + (post ? " " + post : "") : /*n+3*/ match;
|
|
};
|
|
|
|
//Don't apply the infixSpaceRe to attribute value selectors
|
|
var attRe = /([^[]*)([^\]]*])?/g;
|
|
var attFunc = function(match, nonAtt, att){
|
|
return nonAtt.replace(infixSpaceRe, infixSpaceFunc) + (att||"");
|
|
};
|
|
var getQueryFunc = function(query, forceDOM){
|
|
//Normalize query. The CSS3 selectors spec allows for omitting spaces around
|
|
//infix operators, >, ~ and +
|
|
//Do the work here since detection for spaces is used as a simple "not use QSA"
|
|
//test below.
|
|
query = query.replace(attRe, attFunc);
|
|
|
|
if(qsaAvail){
|
|
// if we've got a cached variant and we think we can do it, run it!
|
|
var qsaCached = _queryFuncCacheQSA[query];
|
|
if(qsaCached && !forceDOM){ return qsaCached; }
|
|
}
|
|
|
|
// else if we've got a DOM cached variant, assume that we already know
|
|
// all we need to and use it
|
|
var domCached = _queryFuncCacheDOM[query];
|
|
if(domCached){ return domCached; }
|
|
|
|
// TODO:
|
|
// today we're caching DOM and QSA branches separately so we
|
|
// recalc useQSA every time. If we had a way to tag root+query
|
|
// efficiently, we'd be in good shape to do a global cache.
|
|
|
|
var qcz = query.charAt(0);
|
|
var nospace = (-1 == query.indexOf(" "));
|
|
|
|
// byId searches are wicked fast compared to QSA, even when filtering
|
|
// is required
|
|
if( (query.indexOf("#") >= 0) && (nospace) ){
|
|
forceDOM = true;
|
|
}
|
|
|
|
var useQSA = (
|
|
qsaAvail && (!forceDOM) &&
|
|
// as per CSS 3, we can't currently start w/ combinator:
|
|
// http://www.w3.org/TR/css3-selectors/#w3cselgrammar
|
|
(specials.indexOf(qcz) == -1) &&
|
|
// IE's QSA impl sucks on pseudos
|
|
(!has("ie") || (query.indexOf(":") == -1)) &&
|
|
|
|
(!(cssCaseBug && (query.indexOf(".") >= 0))) &&
|
|
|
|
// FIXME:
|
|
// need to tighten up browser rules on ":contains" and "|=" to
|
|
// figure out which aren't good
|
|
// Latest webkit (around 531.21.8) does not seem to do well with :checked on option
|
|
// elements, even though according to spec, selected options should
|
|
// match :checked. So go nonQSA for it:
|
|
// http://bugs.dojotoolkit.org/ticket/5179
|
|
(query.indexOf(":contains") == -1) && (query.indexOf(":checked") == -1) &&
|
|
(query.indexOf("|=") == -1) // some browsers don't grok it
|
|
);
|
|
|
|
// TODO:
|
|
// if we've got a descendant query (e.g., "> .thinger" instead of
|
|
// just ".thinger") in a QSA-able doc, but are passed a child as a
|
|
// root, it should be possible to give the item a synthetic ID and
|
|
// trivially rewrite the query to the form "#synid > .thinger" to
|
|
// use the QSA branch
|
|
|
|
|
|
if(useQSA){
|
|
var tq = (specials.indexOf(query.charAt(query.length-1)) >= 0) ?
|
|
(query + " *") : query;
|
|
return _queryFuncCacheQSA[query] = function(root){
|
|
try{
|
|
// the QSA system contains an egregious spec bug which
|
|
// limits us, effectively, to only running QSA queries over
|
|
// entire documents. See:
|
|
// http://ejohn.org/blog/thoughts-on-queryselectorall/
|
|
// despite this, we can also handle QSA runs on simple
|
|
// selectors, but we don't want detection to be expensive
|
|
// so we're just checking for the presence of a space char
|
|
// right now. Not elegant, but it's cheaper than running
|
|
// the query parser when we might not need to
|
|
if(!((9 == root.nodeType) || nospace)){ throw ""; }
|
|
var r = root[qsa](tq);
|
|
// skip expensive duplication checks and just wrap in a NodeList
|
|
r[noZip] = true;
|
|
return r;
|
|
}catch(e){
|
|
// else run the DOM branch on this query, ensuring that we
|
|
// default that way in the future
|
|
return getQueryFunc(query, true)(root);
|
|
}
|
|
};
|
|
}else{
|
|
// DOM branch
|
|
var parts = query.match(/([^\s,](?:"(?:\\.|[^"])+"|'(?:\\.|[^'])+'|[^,])*)/g);
|
|
return _queryFuncCacheDOM[query] = ((parts.length < 2) ?
|
|
// if not a compound query (e.g., ".foo, .bar"), cache and return a dispatcher
|
|
getStepQueryFunc(query) :
|
|
// if it *is* a complex query, break it up into its
|
|
// constituent parts and return a dispatcher that will
|
|
// merge the parts when run
|
|
function(root){
|
|
var pindex = 0, // avoid array alloc for every invocation
|
|
ret = [],
|
|
tp;
|
|
while((tp = parts[pindex++])){
|
|
ret = ret.concat(getStepQueryFunc(tp)(root));
|
|
}
|
|
return ret;
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
var _zipIdx = 0;
|
|
|
|
// NOTE:
|
|
// this function is Moo inspired, but our own impl to deal correctly
|
|
// with XML in IE
|
|
var _nodeUID = has("ie") ? function(node){
|
|
if(caseSensitive){
|
|
// XML docs don't have uniqueID on their nodes
|
|
return (node.getAttribute("_uid") || node.setAttribute("_uid", ++_zipIdx) || _zipIdx);
|
|
|
|
}else{
|
|
return node.uniqueID;
|
|
}
|
|
} :
|
|
function(node){
|
|
return (node._uid || (node._uid = ++_zipIdx));
|
|
};
|
|
|
|
// determine if a node in is unique in a "bag". In this case we don't want
|
|
// to flatten a list of unique items, but rather just tell if the item in
|
|
// question is already in the bag. Normally we'd just use hash lookup to do
|
|
// this for us but IE's DOM is busted so we can't really count on that. On
|
|
// the upside, it gives us a built in unique ID function.
|
|
var _isUnique = function(node, bag){
|
|
if(!bag){ return 1; }
|
|
var id = _nodeUID(node);
|
|
if(!bag[id]){ return bag[id] = 1; }
|
|
return 0;
|
|
};
|
|
|
|
// attempt to efficiently determine if an item in a list is a dupe,
|
|
// returning a list of "uniques", hopefully in document order
|
|
var _zipIdxName = "_zipIdx";
|
|
var _zip = function(arr){
|
|
if(arr && arr.nozip){
|
|
return arr;
|
|
}
|
|
var ret = [];
|
|
if(!arr || !arr.length){ return ret; }
|
|
if(arr[0]){
|
|
ret.push(arr[0]);
|
|
}
|
|
if(arr.length < 2){ return ret; }
|
|
|
|
_zipIdx++;
|
|
|
|
// we have to fork here for IE and XML docs because we can't set
|
|
// expandos on their nodes (apparently). *sigh*
|
|
var x, te;
|
|
if(has("ie") && caseSensitive){
|
|
var szidx = _zipIdx+"";
|
|
arr[0].setAttribute(_zipIdxName, szidx);
|
|
for(x = 1; te = arr[x]; x++){
|
|
if(arr[x].getAttribute(_zipIdxName) != szidx){
|
|
ret.push(te);
|
|
}
|
|
te.setAttribute(_zipIdxName, szidx);
|
|
}
|
|
}else if(has("ie") && arr.commentStrip){
|
|
try{
|
|
for(x = 1; te = arr[x]; x++){
|
|
if(_isElement(te)){
|
|
ret.push(te);
|
|
}
|
|
}
|
|
}catch(e){ /* squelch */ }
|
|
}else{
|
|
if(arr[0]){ arr[0][_zipIdxName] = _zipIdx; }
|
|
for(x = 1; te = arr[x]; x++){
|
|
if(arr[x][_zipIdxName] != _zipIdx){
|
|
ret.push(te);
|
|
}
|
|
te[_zipIdxName] = _zipIdx;
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
// the main executor
|
|
var query = function(/*String*/ query, /*String|DOMNode?*/ root){
|
|
// summary:
|
|
// Returns nodes which match the given CSS3 selector, searching the
|
|
// entire document by default but optionally taking a node to scope
|
|
// the search by. Returns an array.
|
|
// description:
|
|
// dojo.query() is the swiss army knife of DOM node manipulation in
|
|
// Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's
|
|
// "$" function, dojo.query provides robust, high-performance
|
|
// CSS-based node selector support with the option of scoping searches
|
|
// to a particular sub-tree of a document.
|
|
//
|
|
// Supported Selectors:
|
|
// --------------------
|
|
//
|
|
// acme supports a rich set of CSS3 selectors, including:
|
|
//
|
|
// - class selectors (e.g., `.foo`)
|
|
// - node type selectors like `span`
|
|
// - ` ` descendant selectors
|
|
// - `>` child element selectors
|
|
// - `#foo` style ID selectors
|
|
// - `*` universal selector
|
|
// - `~`, the preceded-by sibling selector
|
|
// - `+`, the immediately preceded-by sibling selector
|
|
// - attribute queries:
|
|
// - `[foo]` attribute presence selector
|
|
// - `[foo='bar']` attribute value exact match
|
|
// - `[foo~='bar']` attribute value list item match
|
|
// - `[foo^='bar']` attribute start match
|
|
// - `[foo$='bar']` attribute end match
|
|
// - `[foo*='bar']` attribute substring match
|
|
// - `:first-child`, `:last-child`, and `:only-child` positional selectors
|
|
// - `:empty` content emtpy selector
|
|
// - `:checked` pseudo selector
|
|
// - `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations
|
|
// - `:nth-child(even)`, `:nth-child(odd)` positional selectors
|
|
// - `:not(...)` negation pseudo selectors
|
|
//
|
|
// Any legal combination of these selectors will work with
|
|
// `dojo.query()`, including compound selectors ("," delimited).
|
|
// Very complex and useful searches can be constructed with this
|
|
// palette of selectors and when combined with functions for
|
|
// manipulation presented by dojo/NodeList, many types of DOM
|
|
// manipulation operations become very straightforward.
|
|
//
|
|
// Unsupported Selectors:
|
|
// ----------------------
|
|
//
|
|
// While dojo.query handles many CSS3 selectors, some fall outside of
|
|
// what's reasonable for a programmatic node querying engine to
|
|
// handle. Currently unsupported selectors include:
|
|
//
|
|
// - namespace-differentiated selectors of any form
|
|
// - all `::` pseduo-element selectors
|
|
// - certain pseudo-selectors which don't get a lot of day-to-day use:
|
|
// - `:root`, `:lang()`, `:target`, `:focus`
|
|
// - all visual and state selectors:
|
|
// - `:root`, `:active`, `:hover`, `:visited`, `:link`,
|
|
// `:enabled`, `:disabled`
|
|
// - `:*-of-type` pseudo selectors
|
|
//
|
|
// dojo.query and XML Documents:
|
|
// -----------------------------
|
|
//
|
|
// `dojo.query` (as of dojo 1.2) supports searching XML documents
|
|
// in a case-sensitive manner. If an HTML document is served with
|
|
// a doctype that forces case-sensitivity (e.g., XHTML 1.1
|
|
// Strict), dojo.query() will detect this and "do the right
|
|
// thing". Case sensitivity is dependent upon the document being
|
|
// searched and not the query used. It is therefore possible to
|
|
// use case-sensitive queries on strict sub-documents (iframes,
|
|
// etc.) or XML documents while still assuming case-insensitivity
|
|
// for a host/root document.
|
|
//
|
|
// Non-selector Queries:
|
|
// ---------------------
|
|
//
|
|
// If something other than a String is passed for the query,
|
|
// `dojo.query` will return a new `dojo/NodeList` instance
|
|
// constructed from that parameter alone and all further
|
|
// processing will stop. This means that if you have a reference
|
|
// to a node or NodeList, you can quickly construct a new NodeList
|
|
// from the original by calling `dojo.query(node)` or
|
|
// `dojo.query(list)`.
|
|
//
|
|
// query:
|
|
// The CSS3 expression to match against. For details on the syntax of
|
|
// CSS3 selectors, see <http://www.w3.org/TR/css3-selectors/#selectors>
|
|
// root:
|
|
// A DOMNode (or node id) to scope the search from. Optional.
|
|
// returns: Array
|
|
// example:
|
|
// search the entire document for elements with the class "foo":
|
|
// | dojo.query(".foo");
|
|
// these elements will match:
|
|
// | <span class="foo"></span>
|
|
// | <span class="foo bar"></span>
|
|
// | <p class="thud foo"></p>
|
|
// example:
|
|
// search the entire document for elements with the classes "foo" *and* "bar":
|
|
// | dojo.query(".foo.bar");
|
|
// these elements will match:
|
|
// | <span class="foo bar"></span>
|
|
// while these will not:
|
|
// | <span class="foo"></span>
|
|
// | <p class="thud foo"></p>
|
|
// example:
|
|
// find `<span>` elements which are descendants of paragraphs and
|
|
// which have a "highlighted" class:
|
|
// | dojo.query("p span.highlighted");
|
|
// the innermost span in this fragment matches:
|
|
// | <p class="foo">
|
|
// | <span>...
|
|
// | <span class="highlighted foo bar">...</span>
|
|
// | </span>
|
|
// | </p>
|
|
// example:
|
|
// set an "odd" class on all odd table rows inside of the table
|
|
// `#tabular_data`, using the `>` (direct child) selector to avoid
|
|
// affecting any nested tables:
|
|
// | dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd");
|
|
// example:
|
|
// remove all elements with the class "error" from the document
|
|
// and store them in a list:
|
|
// | var errors = dojo.query(".error").orphan();
|
|
// example:
|
|
// add an onclick handler to every submit button in the document
|
|
// which causes the form to be sent via Ajax instead:
|
|
// | dojo.query("input[type='submit']").onclick(function(e){
|
|
// | dojo.stopEvent(e); // prevent sending the form
|
|
// | var btn = e.target;
|
|
// | dojo.xhrPost({
|
|
// | form: btn.form,
|
|
// | load: function(data){
|
|
// | // replace the form with the response
|
|
// | var div = dojo.doc.createElement("div");
|
|
// | dojo.place(div, btn.form, "after");
|
|
// | div.innerHTML = data;
|
|
// | dojo.style(btn.form, "display", "none");
|
|
// | }
|
|
// | });
|
|
// | });
|
|
|
|
root = root || getDoc();
|
|
|
|
// throw the big case sensitivity switch
|
|
var od = root.ownerDocument || root; // root is either Document or a node inside the document
|
|
caseSensitive = (od.createElement("div").tagName === "div");
|
|
|
|
// NOTE:
|
|
// adding "true" as the 2nd argument to getQueryFunc is useful for
|
|
// testing the DOM branch without worrying about the
|
|
// behavior/performance of the QSA branch.
|
|
var r = getQueryFunc(query)(root);
|
|
|
|
// FIXME:
|
|
// need to investigate this branch WRT #8074 and #8075
|
|
if(r && r.nozip){
|
|
return r;
|
|
}
|
|
return _zip(r); // dojo/NodeList
|
|
};
|
|
query.filter = function(/*Node[]*/ nodeList, /*String*/ filter, /*String|DOMNode?*/ root){
|
|
// summary:
|
|
// function for filtering a NodeList based on a selector, optimized for simple selectors
|
|
var tmpNodeList = [],
|
|
parts = getQueryParts(filter),
|
|
filterFunc =
|
|
(parts.length == 1 && !/[^\w#\.]/.test(filter)) ?
|
|
getSimpleFilterFunc(parts[0]) :
|
|
function(node){
|
|
return array.indexOf(query(filter, dom.byId(root)), node) != -1;
|
|
};
|
|
for(var x = 0, te; te = nodeList[x]; x++){
|
|
if(filterFunc(te)){ tmpNodeList.push(te); }
|
|
}
|
|
return tmpNodeList;
|
|
};
|
|
return query;
|
|
});
|
|
|
|
},
|
|
'dojo/dnd/autoscroll':function(){
|
|
define("dojo/dnd/autoscroll", ["../_base/lang", "../sniff", "../_base/window", "../dom-geometry", "../dom-style", "../window"],
|
|
function(lang, has, win, domGeom, domStyle, winUtils){
|
|
|
|
// module:
|
|
// dojo/dnd/autoscroll
|
|
|
|
var exports = {
|
|
// summary:
|
|
// Used by dojo/dnd/Manager to scroll document or internal node when the user
|
|
// drags near the edge of the viewport or a scrollable node
|
|
};
|
|
lang.setObject("dojo.dnd.autoscroll", exports);
|
|
|
|
exports.getViewport = winUtils.getBox;
|
|
|
|
exports.V_TRIGGER_AUTOSCROLL = 32;
|
|
exports.H_TRIGGER_AUTOSCROLL = 32;
|
|
|
|
exports.V_AUTOSCROLL_VALUE = 16;
|
|
exports.H_AUTOSCROLL_VALUE = 16;
|
|
|
|
// These are set by autoScrollStart().
|
|
// Set to default values in case autoScrollStart() isn't called. (back-compat, remove for 2.0)
|
|
var viewport,
|
|
doc = win.doc,
|
|
maxScrollTop = Infinity,
|
|
maxScrollLeft = Infinity;
|
|
|
|
exports.autoScrollStart = function(d){
|
|
// summary:
|
|
// Called at the start of a drag.
|
|
// d: Document
|
|
// The document of the node being dragged.
|
|
|
|
doc = d;
|
|
viewport = winUtils.getBox(doc);
|
|
|
|
// Save height/width of document at start of drag, before it gets distorted by a user dragging an avatar past
|
|
// the document's edge
|
|
var html = win.body(doc).parentNode;
|
|
maxScrollTop = Math.max(html.scrollHeight - viewport.h, 0);
|
|
maxScrollLeft = Math.max(html.scrollWidth - viewport.w, 0); // usually 0
|
|
};
|
|
|
|
exports.autoScroll = function(e){
|
|
// summary:
|
|
// a handler for mousemove and touchmove events, which scrolls the window, if
|
|
// necessary
|
|
// e: Event
|
|
// mousemove/touchmove event
|
|
|
|
// FIXME: needs more docs!
|
|
var v = viewport || winUtils.getBox(doc), // getBox() call for back-compat, in case autoScrollStart() wasn't called
|
|
html = win.body(doc).parentNode,
|
|
dx = 0, dy = 0;
|
|
if(e.clientX < exports.H_TRIGGER_AUTOSCROLL){
|
|
dx = -exports.H_AUTOSCROLL_VALUE;
|
|
}else if(e.clientX > v.w - exports.H_TRIGGER_AUTOSCROLL){
|
|
dx = Math.min(exports.H_AUTOSCROLL_VALUE, maxScrollLeft - html.scrollLeft); // don't scroll past edge of doc
|
|
}
|
|
if(e.clientY < exports.V_TRIGGER_AUTOSCROLL){
|
|
dy = -exports.V_AUTOSCROLL_VALUE;
|
|
}else if(e.clientY > v.h - exports.V_TRIGGER_AUTOSCROLL){
|
|
dy = Math.min(exports.V_AUTOSCROLL_VALUE, maxScrollTop - html.scrollTop); // don't scroll past edge of doc
|
|
}
|
|
window.scrollBy(dx, dy);
|
|
};
|
|
|
|
exports._validNodes = {"div": 1, "p": 1, "td": 1};
|
|
exports._validOverflow = {"auto": 1, "scroll": 1};
|
|
|
|
exports.autoScrollNodes = function(e){
|
|
// summary:
|
|
// a handler for mousemove and touchmove events, which scrolls the first available
|
|
// Dom element, it falls back to exports.autoScroll()
|
|
// e: Event
|
|
// mousemove/touchmove 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 exports._validNodes)){
|
|
var s = domStyle.getComputedStyle(n),
|
|
overflow = (s.overflow.toLowerCase() in exports._validOverflow),
|
|
overflowX = (s.overflowX.toLowerCase() in exports._validOverflow),
|
|
overflowY = (s.overflowY.toLowerCase() in exports._validOverflow);
|
|
if(overflow || overflowX || overflowY){
|
|
b = domGeom.getContentBox(n, s);
|
|
t = domGeom.position(n, true);
|
|
}
|
|
// overflow-x
|
|
if(overflow || overflowX){
|
|
w = Math.min(exports.H_TRIGGER_AUTOSCROLL, b.w / 2);
|
|
rx = e.pageX - t.x;
|
|
if(has("webkit") || has("opera")){
|
|
// FIXME: this code should not be here, it should be taken into account
|
|
// either by the event fixing code, or the domGeom.position()
|
|
// FIXME: this code doesn't work on Opera 9.5 Beta
|
|
rx += win.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(exports.V_TRIGGER_AUTOSCROLL, b.h / 2);
|
|
ry = e.pageY - t.y;
|
|
if(has("webkit") || has("opera")){
|
|
// FIXME: this code should not be here, it should be taken into account
|
|
// either by the event fixing code, or the domGeom.position()
|
|
// FIXME: this code doesn't work on Opera 9.5 Beta
|
|
ry += win.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;
|
|
}
|
|
}
|
|
exports.autoScroll(e);
|
|
};
|
|
|
|
return exports;
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/_RadioButtonMixin':function(){
|
|
define("dijit/form/_RadioButtonMixin", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/query", // query
|
|
"../registry" // registry.getEnclosingWidget
|
|
], function(array, declare, domAttr, event, lang, query, registry){
|
|
|
|
// module:
|
|
// dijit/form/_RadioButtonMixin
|
|
|
|
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 || this.ownerDocument).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);
|
|
}
|
|
}));
|
|
}
|
|
},
|
|
|
|
_getSubmitValue: function(/*String*/ value){
|
|
return value === null ? "on" : value;
|
|
},
|
|
|
|
_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);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/data/ItemFileWriteStore':function(){
|
|
define("dojo/data/ItemFileWriteStore", ["../_base/lang", "../_base/declare", "../_base/array", "../_base/json", "../_base/kernel",
|
|
"./ItemFileReadStore", "../date/stamp"
|
|
], function(lang, declare, arrayUtil, jsonUtil, kernel, ItemFileReadStore, dateStamp){
|
|
|
|
// module:
|
|
// dojo/data/ItemFileWriteStore
|
|
|
|
return declare("dojo.data.ItemFileWriteStore", ItemFileReadStore, {
|
|
// summary:
|
|
// TODOC
|
|
|
|
constructor: function(/* object */ keywordParameters){
|
|
// keywordParameters:
|
|
// The structure of the typeMap object is as follows:
|
|
// | {
|
|
// | type0: function || object,
|
|
// | type1: function || object,
|
|
// | ...
|
|
// | typeN: function || object
|
|
// | }
|
|
// Where if it is a function, it is assumed to be an object constructor that takes the
|
|
// value of _value as the initialization parameters. It is serialized assuming object.toString()
|
|
// serialization. If it is an object, then it is assumed
|
|
// to be an object of general form:
|
|
// | {
|
|
// | type: function, //constructor.
|
|
// | deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
|
|
// | serialize: function(object) //The function that converts the object back into the proper file format form.
|
|
// | }
|
|
|
|
// ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs
|
|
this._features['dojo.data.api.Write'] = true;
|
|
this._features['dojo.data.api.Notification'] = true;
|
|
|
|
// For keeping track of changes so that we can implement isDirty and revert
|
|
this._pending = {
|
|
_newItems:{},
|
|
_modifiedItems:{},
|
|
_deletedItems:{}
|
|
};
|
|
|
|
if(!this._datatypeMap['Date'].serialize){
|
|
this._datatypeMap['Date'].serialize = function(obj){
|
|
return dateStamp.toISOString(obj, {zulu:true});
|
|
};
|
|
}
|
|
//Disable only if explicitly set to false.
|
|
if(keywordParameters && (keywordParameters.referenceIntegrity === false)){
|
|
this.referenceIntegrity = false;
|
|
}
|
|
|
|
// this._saveInProgress is set to true, briefly, from when save() is first called to when it completes
|
|
this._saveInProgress = false;
|
|
},
|
|
|
|
referenceIntegrity: true, //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively.
|
|
|
|
_assert: function(/* boolean */ condition){
|
|
if(!condition){
|
|
throw new Error("assertion failed in ItemFileWriteStore");
|
|
}
|
|
},
|
|
|
|
_getIdentifierAttribute: function(){
|
|
// this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute)));
|
|
return this.getFeatures()['dojo.data.api.Identity'];
|
|
},
|
|
|
|
|
|
/* dojo/data/api/Write */
|
|
|
|
newItem: function(/* Object? */ keywordArgs, /* Object? */ parentInfo){
|
|
// summary:
|
|
// See dojo/data/api/Write.newItem()
|
|
|
|
this._assert(!this._saveInProgress);
|
|
|
|
if(!this._loadFinished){
|
|
// We need to do this here so that we'll be able to find out what
|
|
// identifierAttribute was specified in the data file.
|
|
this._forceLoad();
|
|
}
|
|
|
|
if(typeof keywordArgs != "object" && typeof keywordArgs != "undefined"){
|
|
throw new Error("newItem() was passed something other than an object");
|
|
}
|
|
var newIdentity = null;
|
|
var identifierAttribute = this._getIdentifierAttribute();
|
|
if(identifierAttribute === Number){
|
|
newIdentity = this._arrayOfAllItems.length;
|
|
}else{
|
|
newIdentity = keywordArgs[identifierAttribute];
|
|
if(typeof newIdentity === "undefined"){
|
|
throw new Error("newItem() was not passed an identity for the new item");
|
|
}
|
|
if(lang.isArray(newIdentity)){
|
|
throw new Error("newItem() was not passed an single-valued identity");
|
|
}
|
|
}
|
|
|
|
// make sure this identity is not already in use by another item, if identifiers were
|
|
// defined in the file. Otherwise it would be the item count,
|
|
// which should always be unique in this case.
|
|
if(this._itemsByIdentity){
|
|
this._assert(typeof this._itemsByIdentity[newIdentity] === "undefined");
|
|
}
|
|
this._assert(typeof this._pending._newItems[newIdentity] === "undefined");
|
|
this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined");
|
|
|
|
var newItem = {};
|
|
newItem[this._storeRefPropName] = this;
|
|
newItem[this._itemNumPropName] = this._arrayOfAllItems.length;
|
|
if(this._itemsByIdentity){
|
|
this._itemsByIdentity[newIdentity] = newItem;
|
|
//We have to set the identifier now, otherwise we can't look it
|
|
//up at calls to setValueorValues in parentInfo handling.
|
|
newItem[identifierAttribute] = [newIdentity];
|
|
}
|
|
this._arrayOfAllItems.push(newItem);
|
|
|
|
//We need to construct some data for the onNew call too...
|
|
var pInfo = null;
|
|
|
|
// Now we need to check to see where we want to assign this thingm if any.
|
|
if(parentInfo && parentInfo.parent && parentInfo.attribute){
|
|
pInfo = {
|
|
item: parentInfo.parent,
|
|
attribute: parentInfo.attribute,
|
|
oldValue: undefined
|
|
};
|
|
|
|
//See if it is multi-valued or not and handle appropriately
|
|
//Generally, all attributes are multi-valued for this store
|
|
//So, we only need to append if there are already values present.
|
|
var values = this.getValues(parentInfo.parent, parentInfo.attribute);
|
|
if(values && values.length > 0){
|
|
var tempValues = values.slice(0, values.length);
|
|
if(values.length === 1){
|
|
pInfo.oldValue = values[0];
|
|
}else{
|
|
pInfo.oldValue = values.slice(0, values.length);
|
|
}
|
|
tempValues.push(newItem);
|
|
this._setValueOrValues(parentInfo.parent, parentInfo.attribute, tempValues, false);
|
|
pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute);
|
|
}else{
|
|
this._setValueOrValues(parentInfo.parent, parentInfo.attribute, newItem, false);
|
|
pInfo.newValue = newItem;
|
|
}
|
|
}else{
|
|
//Toplevel item, add to both top list as well as all list.
|
|
newItem[this._rootItemPropName]=true;
|
|
this._arrayOfTopLevelItems.push(newItem);
|
|
}
|
|
|
|
this._pending._newItems[newIdentity] = newItem;
|
|
|
|
//Clone over the properties to the new item
|
|
for(var key in keywordArgs){
|
|
if(key === this._storeRefPropName || key === this._itemNumPropName){
|
|
// Bummer, the user is trying to do something like
|
|
// newItem({_S:"foo"}). Unfortunately, our superclass,
|
|
// ItemFileReadStore, is already using _S in each of our items
|
|
// to hold private info. To avoid a naming collision, we
|
|
// need to move all our private info to some other property
|
|
// of all the items/objects. So, we need to iterate over all
|
|
// the items and do something like:
|
|
// item.__S = item._S;
|
|
// item._S = undefined;
|
|
// But first we have to make sure the new "__S" variable is
|
|
// not in use, which means we have to iterate over all the
|
|
// items checking for that.
|
|
throw new Error("encountered bug in ItemFileWriteStore.newItem");
|
|
}
|
|
var value = keywordArgs[key];
|
|
if(!lang.isArray(value)){
|
|
value = [value];
|
|
}
|
|
newItem[key] = value;
|
|
if(this.referenceIntegrity){
|
|
for(var i = 0; i < value.length; i++){
|
|
var val = value[i];
|
|
if(this.isItem(val)){
|
|
this._addReferenceToMap(val, newItem, key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.onNew(newItem, pInfo); // dojo/data/api/Notification call
|
|
return newItem; // item
|
|
},
|
|
|
|
_removeArrayElement: function(/* Array */ array, /* anything */ element){
|
|
var index = arrayUtil.indexOf(array, element);
|
|
if(index != -1){
|
|
array.splice(index, 1);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
deleteItem: function(/* dojo/data/api/Item */ item){
|
|
// summary:
|
|
// See dojo/data/api/Write.deleteItem()
|
|
this._assert(!this._saveInProgress);
|
|
this._assertIsItem(item);
|
|
|
|
// Remove this item from the _arrayOfAllItems, but leave a null value in place
|
|
// of the item, so as not to change the length of the array, so that in newItem()
|
|
// we can still safely do: newIdentity = this._arrayOfAllItems.length;
|
|
var indexInArrayOfAllItems = item[this._itemNumPropName];
|
|
var identity = this.getIdentity(item);
|
|
|
|
//If we have reference integrity on, we need to do reference cleanup for the deleted item
|
|
if(this.referenceIntegrity){
|
|
//First scan all the attributes of this items for references and clean them up in the map
|
|
//As this item is going away, no need to track its references anymore.
|
|
|
|
//Get the attributes list before we generate the backup so it
|
|
//doesn't pollute the attributes list.
|
|
var attributes = this.getAttributes(item);
|
|
|
|
//Backup the map, we'll have to restore it potentially, in a revert.
|
|
if(item[this._reverseRefMap]){
|
|
item["backup_" + this._reverseRefMap] = lang.clone(item[this._reverseRefMap]);
|
|
}
|
|
|
|
//TODO: This causes a reversion problem. This list won't be restored on revert since it is
|
|
//attached to the 'value'. item, not ours. Need to back tese up somehow too.
|
|
//Maybe build a map of the backup of the entries and attach it to the deleted item to be restored
|
|
//later. Or just record them and call _addReferenceToMap on them in revert.
|
|
arrayUtil.forEach(attributes, function(attribute){
|
|
arrayUtil.forEach(this.getValues(item, attribute), function(value){
|
|
if(this.isItem(value)){
|
|
//We have to back up all the references we had to others so they can be restored on a revert.
|
|
if(!item["backupRefs_" + this._reverseRefMap]){
|
|
item["backupRefs_" + this._reverseRefMap] = [];
|
|
}
|
|
item["backupRefs_" + this._reverseRefMap].push({id: this.getIdentity(value), attr: attribute});
|
|
this._removeReferenceFromMap(value, item, attribute);
|
|
}
|
|
}, this);
|
|
}, this);
|
|
|
|
//Next, see if we have references to this item, if we do, we have to clean them up too.
|
|
var references = item[this._reverseRefMap];
|
|
if(references){
|
|
//Look through all the items noted as references to clean them up.
|
|
for(var itemId in references){
|
|
var containingItem = null;
|
|
if(this._itemsByIdentity){
|
|
containingItem = this._itemsByIdentity[itemId];
|
|
}else{
|
|
containingItem = this._arrayOfAllItems[itemId];
|
|
}
|
|
//We have a reference to a containing item, now we have to process the
|
|
//attributes and clear all references to the item being deleted.
|
|
if(containingItem){
|
|
for(var attribute in references[itemId]){
|
|
var oldValues = this.getValues(containingItem, attribute) || [];
|
|
var newValues = arrayUtil.filter(oldValues, function(possibleItem){
|
|
return !(this.isItem(possibleItem) && this.getIdentity(possibleItem) == identity);
|
|
}, this);
|
|
//Remove the note of the reference to the item and set the values on the modified attribute.
|
|
this._removeReferenceFromMap(item, containingItem, attribute);
|
|
if(newValues.length < oldValues.length){
|
|
this._setValueOrValues(containingItem, attribute, newValues, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this._arrayOfAllItems[indexInArrayOfAllItems] = null;
|
|
|
|
item[this._storeRefPropName] = null;
|
|
if(this._itemsByIdentity){
|
|
delete this._itemsByIdentity[identity];
|
|
}
|
|
this._pending._deletedItems[identity] = item;
|
|
|
|
//Remove from the toplevel items, if necessary...
|
|
if(item[this._rootItemPropName]){
|
|
this._removeArrayElement(this._arrayOfTopLevelItems, item);
|
|
}
|
|
this.onDelete(item); // dojo/data/api/Notification call
|
|
return true;
|
|
},
|
|
|
|
setValue: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute, /* almost anything */ value){
|
|
// summary:
|
|
// See dojo/data/api/Write.set()
|
|
return this._setValueOrValues(item, attribute, value, true); // boolean
|
|
},
|
|
|
|
setValues: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute, /* array */ values){
|
|
// summary:
|
|
// See dojo/data/api/Write.setValues()
|
|
return this._setValueOrValues(item, attribute, values, true); // boolean
|
|
},
|
|
|
|
unsetAttribute: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute){
|
|
// summary:
|
|
// See dojo/data/api/Write.unsetAttribute()
|
|
return this._setValueOrValues(item, attribute, [], true);
|
|
},
|
|
|
|
_setValueOrValues: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute, /* anything */ newValueOrValues, /*boolean?*/ callOnSet){
|
|
this._assert(!this._saveInProgress);
|
|
|
|
// Check for valid arguments
|
|
this._assertIsItem(item);
|
|
this._assert(lang.isString(attribute));
|
|
this._assert(typeof newValueOrValues !== "undefined");
|
|
|
|
// Make sure the user isn't trying to change the item's identity
|
|
var identifierAttribute = this._getIdentifierAttribute();
|
|
if(attribute == identifierAttribute){
|
|
throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier.");
|
|
}
|
|
|
|
// To implement the Notification API, we need to make a note of what
|
|
// the old attribute value was, so that we can pass that info when
|
|
// we call the onSet method.
|
|
var oldValueOrValues = this._getValueOrValues(item, attribute);
|
|
|
|
var identity = this.getIdentity(item);
|
|
if(!this._pending._modifiedItems[identity]){
|
|
// Before we actually change the item, we make a copy of it to
|
|
// record the original state, so that we'll be able to revert if
|
|
// the revert method gets called. If the item has already been
|
|
// modified then there's no need to do this now, since we already
|
|
// have a record of the original state.
|
|
var copyOfItemState = {};
|
|
for(var key in item){
|
|
if((key === this._storeRefPropName) || (key === this._itemNumPropName) || (key === this._rootItemPropName)){
|
|
copyOfItemState[key] = item[key];
|
|
}else if(key === this._reverseRefMap){
|
|
copyOfItemState[key] = lang.clone(item[key]);
|
|
}else{
|
|
copyOfItemState[key] = item[key].slice(0, item[key].length);
|
|
}
|
|
}
|
|
// Now mark the item as dirty, and save the copy of the original state
|
|
this._pending._modifiedItems[identity] = copyOfItemState;
|
|
}
|
|
|
|
// Okay, now we can actually change this attribute on the item
|
|
var success = false;
|
|
|
|
if(lang.isArray(newValueOrValues) && newValueOrValues.length === 0){
|
|
|
|
// If we were passed an empty array as the value, that counts
|
|
// as "unsetting" the attribute, so we need to remove this
|
|
// attribute from the item.
|
|
success = delete item[attribute];
|
|
newValueOrValues = undefined; // used in the onSet Notification call below
|
|
|
|
if(this.referenceIntegrity && oldValueOrValues){
|
|
var oldValues = oldValueOrValues;
|
|
if(!lang.isArray(oldValues)){
|
|
oldValues = [oldValues];
|
|
}
|
|
for(var i = 0; i < oldValues.length; i++){
|
|
var value = oldValues[i];
|
|
if(this.isItem(value)){
|
|
this._removeReferenceFromMap(value, item, attribute);
|
|
}
|
|
}
|
|
}
|
|
}else{
|
|
var newValueArray;
|
|
if(lang.isArray(newValueOrValues)){
|
|
// Unfortunately, it's not safe to just do this:
|
|
// newValueArray = newValueOrValues;
|
|
// Instead, we need to copy the array, which slice() does very nicely.
|
|
// This is so that our internal data structure won't
|
|
// get corrupted if the user mucks with the values array *after*
|
|
// calling setValues().
|
|
newValueArray = newValueOrValues.slice(0, newValueOrValues.length);
|
|
}else{
|
|
newValueArray = [newValueOrValues];
|
|
}
|
|
|
|
//We need to handle reference integrity if this is on.
|
|
//In the case of set, we need to see if references were added or removed
|
|
//and update the reference tracking map accordingly.
|
|
if(this.referenceIntegrity){
|
|
if(oldValueOrValues){
|
|
var oldValues = oldValueOrValues;
|
|
if(!lang.isArray(oldValues)){
|
|
oldValues = [oldValues];
|
|
}
|
|
//Use an associative map to determine what was added/removed from the list.
|
|
//Should be O(n) performant. First look at all the old values and make a list of them
|
|
//Then for any item not in the old list, we add it. If it was already present, we remove it.
|
|
//Then we pass over the map and any references left it it need to be removed (IE, no match in
|
|
//the new values list).
|
|
var map = {};
|
|
arrayUtil.forEach(oldValues, function(possibleItem){
|
|
if(this.isItem(possibleItem)){
|
|
var id = this.getIdentity(possibleItem);
|
|
map[id.toString()] = true;
|
|
}
|
|
}, this);
|
|
arrayUtil.forEach(newValueArray, function(possibleItem){
|
|
if(this.isItem(possibleItem)){
|
|
var id = this.getIdentity(possibleItem);
|
|
if(map[id.toString()]){
|
|
delete map[id.toString()];
|
|
}else{
|
|
this._addReferenceToMap(possibleItem, item, attribute);
|
|
}
|
|
}
|
|
}, this);
|
|
for(var rId in map){
|
|
var removedItem;
|
|
if(this._itemsByIdentity){
|
|
removedItem = this._itemsByIdentity[rId];
|
|
}else{
|
|
removedItem = this._arrayOfAllItems[rId];
|
|
}
|
|
this._removeReferenceFromMap(removedItem, item, attribute);
|
|
}
|
|
}else{
|
|
//Everything is new (no old values) so we have to just
|
|
//insert all the references, if any.
|
|
for(var i = 0; i < newValueArray.length; i++){
|
|
var value = newValueArray[i];
|
|
if(this.isItem(value)){
|
|
this._addReferenceToMap(value, item, attribute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
item[attribute] = newValueArray;
|
|
success = true;
|
|
}
|
|
|
|
// Now we make the dojo/data/api/Notification call
|
|
if(callOnSet){
|
|
this.onSet(item, attribute, oldValueOrValues, newValueOrValues);
|
|
}
|
|
return success; // boolean
|
|
},
|
|
|
|
_addReferenceToMap: function(/* dojo/data/api/Item */ refItem, /* dojo/data/api/Item */ parentItem, /* string */ attribute){
|
|
// summary:
|
|
// Method to add an reference map entry for an item and attribute.
|
|
// description:
|
|
// Method to add an reference map entry for an item and attribute.
|
|
// refItem:
|
|
// The item that is referenced.
|
|
// parentItem:
|
|
// The item that holds the new reference to refItem.
|
|
// attribute:
|
|
// The attribute on parentItem that contains the new reference.
|
|
|
|
var parentId = this.getIdentity(parentItem);
|
|
var references = refItem[this._reverseRefMap];
|
|
|
|
if(!references){
|
|
references = refItem[this._reverseRefMap] = {};
|
|
}
|
|
var itemRef = references[parentId];
|
|
if(!itemRef){
|
|
itemRef = references[parentId] = {};
|
|
}
|
|
itemRef[attribute] = true;
|
|
},
|
|
|
|
_removeReferenceFromMap: function(/* dojo/data/api/Item */ refItem, /* dojo/data/api/Item */ parentItem, /* string */ attribute){
|
|
// summary:
|
|
// Method to remove an reference map entry for an item and attribute.
|
|
// description:
|
|
// Method to remove an reference map entry for an item and attribute. This will
|
|
// also perform cleanup on the map such that if there are no more references at all to
|
|
// the item, its reference object and entry are removed.
|
|
// refItem:
|
|
// The item that is referenced.
|
|
// parentItem:
|
|
// The item holding a reference to refItem.
|
|
// attribute:
|
|
// The attribute on parentItem that contains the reference.
|
|
var identity = this.getIdentity(parentItem);
|
|
var references = refItem[this._reverseRefMap];
|
|
var itemId;
|
|
if(references){
|
|
for(itemId in references){
|
|
if(itemId == identity){
|
|
delete references[itemId][attribute];
|
|
if(this._isEmpty(references[itemId])){
|
|
delete references[itemId];
|
|
}
|
|
}
|
|
}
|
|
if(this._isEmpty(references)){
|
|
delete refItem[this._reverseRefMap];
|
|
}
|
|
}
|
|
},
|
|
|
|
_dumpReferenceMap: function(){
|
|
// summary:
|
|
// Function to dump the reverse reference map of all items in the store for debug purposes.
|
|
// description:
|
|
// Function to dump the reverse reference map of all items in the store for debug purposes.
|
|
var i;
|
|
for(i = 0; i < this._arrayOfAllItems.length; i++){
|
|
var item = this._arrayOfAllItems[i];
|
|
if(item && item[this._reverseRefMap]){
|
|
console.log("Item: [" + this.getIdentity(item) + "] is referenced by: " + jsonUtil.toJson(item[this._reverseRefMap]));
|
|
}
|
|
}
|
|
},
|
|
|
|
_getValueOrValues: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute){
|
|
var valueOrValues = undefined;
|
|
if(this.hasAttribute(item, attribute)){
|
|
var valueArray = this.getValues(item, attribute);
|
|
if(valueArray.length == 1){
|
|
valueOrValues = valueArray[0];
|
|
}else{
|
|
valueOrValues = valueArray;
|
|
}
|
|
}
|
|
return valueOrValues;
|
|
},
|
|
|
|
_flatten: function(/* anything */ value){
|
|
if(this.isItem(value)){
|
|
// Given an item, return an serializable object that provides a
|
|
// reference to the item.
|
|
// For example, given kermit:
|
|
// var kermit = store.newItem({id:2, name:"Kermit"});
|
|
// we want to return
|
|
// {_reference:2}
|
|
return {_reference: this.getIdentity(value)};
|
|
}else{
|
|
if(typeof value === "object"){
|
|
for(var type in this._datatypeMap){
|
|
var typeMap = this._datatypeMap[type];
|
|
if(lang.isObject(typeMap) && !lang.isFunction(typeMap)){
|
|
if(value instanceof typeMap.type){
|
|
if(!typeMap.serialize){
|
|
throw new Error("ItemFileWriteStore: No serializer defined for type mapping: [" + type + "]");
|
|
}
|
|
return {_type: type, _value: typeMap.serialize(value)};
|
|
}
|
|
}else if(value instanceof typeMap){
|
|
//SImple mapping, therefore, return as a toString serialization.
|
|
return {_type: type, _value: value.toString()};
|
|
}
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
},
|
|
|
|
_getNewFileContentString: function(){
|
|
// summary:
|
|
// Generate a string that can be saved to a file.
|
|
// The result should look similar to:
|
|
// http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json
|
|
var serializableStructure = {};
|
|
|
|
var identifierAttribute = this._getIdentifierAttribute();
|
|
if(identifierAttribute !== Number){
|
|
serializableStructure.identifier = identifierAttribute;
|
|
}
|
|
if(this._labelAttr){
|
|
serializableStructure.label = this._labelAttr;
|
|
}
|
|
serializableStructure.items = [];
|
|
for(var i = 0; i < this._arrayOfAllItems.length; ++i){
|
|
var item = this._arrayOfAllItems[i];
|
|
if(item !== null){
|
|
var serializableItem = {};
|
|
for(var key in item){
|
|
if(key !== this._storeRefPropName && key !== this._itemNumPropName && key !== this._reverseRefMap && key !== this._rootItemPropName){
|
|
var valueArray = this.getValues(item, key);
|
|
if(valueArray.length == 1){
|
|
serializableItem[key] = this._flatten(valueArray[0]);
|
|
}else{
|
|
var serializableArray = [];
|
|
for(var j = 0; j < valueArray.length; ++j){
|
|
serializableArray.push(this._flatten(valueArray[j]));
|
|
serializableItem[key] = serializableArray;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
serializableStructure.items.push(serializableItem);
|
|
}
|
|
}
|
|
var prettyPrint = true;
|
|
return jsonUtil.toJson(serializableStructure, prettyPrint);
|
|
},
|
|
|
|
_isEmpty: function(something){
|
|
// summary:
|
|
// Function to determine if an array or object has no properties or values.
|
|
// something:
|
|
// The array or object to examine.
|
|
var empty = true;
|
|
if(lang.isObject(something)){
|
|
var i;
|
|
for(i in something){
|
|
empty = false;
|
|
break;
|
|
}
|
|
}else if(lang.isArray(something)){
|
|
if(something.length > 0){
|
|
empty = false;
|
|
}
|
|
}
|
|
return empty; //boolean
|
|
},
|
|
|
|
save: function(/* object */ keywordArgs){
|
|
// summary:
|
|
// See dojo/data/api/Write.save()
|
|
this._assert(!this._saveInProgress);
|
|
|
|
// this._saveInProgress is set to true, briefly, from when save is first called to when it completes
|
|
this._saveInProgress = true;
|
|
|
|
var self = this;
|
|
var saveCompleteCallback = function(){
|
|
self._pending = {
|
|
_newItems:{},
|
|
_modifiedItems:{},
|
|
_deletedItems:{}
|
|
};
|
|
|
|
self._saveInProgress = false; // must come after this._pending is cleared, but before any callbacks
|
|
if(keywordArgs && keywordArgs.onComplete){
|
|
var scope = keywordArgs.scope || kernel.global;
|
|
keywordArgs.onComplete.call(scope);
|
|
}
|
|
};
|
|
var saveFailedCallback = function(err){
|
|
self._saveInProgress = false;
|
|
if(keywordArgs && keywordArgs.onError){
|
|
var scope = keywordArgs.scope || kernel.global;
|
|
keywordArgs.onError.call(scope, err);
|
|
}
|
|
};
|
|
|
|
if(this._saveEverything){
|
|
var newFileContentString = this._getNewFileContentString();
|
|
this._saveEverything(saveCompleteCallback, saveFailedCallback, newFileContentString);
|
|
}
|
|
if(this._saveCustom){
|
|
this._saveCustom(saveCompleteCallback, saveFailedCallback);
|
|
}
|
|
if(!this._saveEverything && !this._saveCustom){
|
|
// Looks like there is no user-defined save-handler function.
|
|
// That's fine, it just means the datastore is acting as a "mock-write"
|
|
// store -- changes get saved in memory but don't get saved to disk.
|
|
saveCompleteCallback();
|
|
}
|
|
},
|
|
|
|
revert: function(){
|
|
// summary:
|
|
// See dojo/data/api/Write.revert()
|
|
this._assert(!this._saveInProgress);
|
|
|
|
var identity;
|
|
for(identity in this._pending._modifiedItems){
|
|
// find the original item and the modified item that replaced it
|
|
var copyOfItemState = this._pending._modifiedItems[identity];
|
|
var modifiedItem = null;
|
|
if(this._itemsByIdentity){
|
|
modifiedItem = this._itemsByIdentity[identity];
|
|
}else{
|
|
modifiedItem = this._arrayOfAllItems[identity];
|
|
}
|
|
|
|
// Restore the original item into a full-fledged item again, we want to try to
|
|
// keep the same object instance as if we don't it, causes bugs like #9022.
|
|
copyOfItemState[this._storeRefPropName] = this;
|
|
for(var key in modifiedItem){
|
|
delete modifiedItem[key];
|
|
}
|
|
lang.mixin(modifiedItem, copyOfItemState);
|
|
}
|
|
var deletedItem;
|
|
for(identity in this._pending._deletedItems){
|
|
deletedItem = this._pending._deletedItems[identity];
|
|
deletedItem[this._storeRefPropName] = this;
|
|
var index = deletedItem[this._itemNumPropName];
|
|
|
|
//Restore the reverse refererence map, if any.
|
|
if(deletedItem["backup_" + this._reverseRefMap]){
|
|
deletedItem[this._reverseRefMap] = deletedItem["backup_" + this._reverseRefMap];
|
|
delete deletedItem["backup_" + this._reverseRefMap];
|
|
}
|
|
this._arrayOfAllItems[index] = deletedItem;
|
|
if(this._itemsByIdentity){
|
|
this._itemsByIdentity[identity] = deletedItem;
|
|
}
|
|
if(deletedItem[this._rootItemPropName]){
|
|
this._arrayOfTopLevelItems.push(deletedItem);
|
|
}
|
|
}
|
|
//We have to pass through it again and restore the reference maps after all the
|
|
//undeletes have occurred.
|
|
for(identity in this._pending._deletedItems){
|
|
deletedItem = this._pending._deletedItems[identity];
|
|
if(deletedItem["backupRefs_" + this._reverseRefMap]){
|
|
arrayUtil.forEach(deletedItem["backupRefs_" + this._reverseRefMap], function(reference){
|
|
var refItem;
|
|
if(this._itemsByIdentity){
|
|
refItem = this._itemsByIdentity[reference.id];
|
|
}else{
|
|
refItem = this._arrayOfAllItems[reference.id];
|
|
}
|
|
this._addReferenceToMap(refItem, deletedItem, reference.attr);
|
|
}, this);
|
|
delete deletedItem["backupRefs_" + this._reverseRefMap];
|
|
}
|
|
}
|
|
|
|
for(identity in this._pending._newItems){
|
|
var newItem = this._pending._newItems[identity];
|
|
newItem[this._storeRefPropName] = null;
|
|
// null out the new item, but don't change the array index so
|
|
// so we can keep using _arrayOfAllItems.length.
|
|
this._arrayOfAllItems[newItem[this._itemNumPropName]] = null;
|
|
if(newItem[this._rootItemPropName]){
|
|
this._removeArrayElement(this._arrayOfTopLevelItems, newItem);
|
|
}
|
|
if(this._itemsByIdentity){
|
|
delete this._itemsByIdentity[identity];
|
|
}
|
|
}
|
|
|
|
this._pending = {
|
|
_newItems:{},
|
|
_modifiedItems:{},
|
|
_deletedItems:{}
|
|
};
|
|
return true; // boolean
|
|
},
|
|
|
|
isDirty: function(/* item? */ item){
|
|
// summary:
|
|
// See dojo/data/api/Write.isDirty()
|
|
if(item){
|
|
// return true if the item is dirty
|
|
var identity = this.getIdentity(item);
|
|
return new Boolean(this._pending._newItems[identity] ||
|
|
this._pending._modifiedItems[identity] ||
|
|
this._pending._deletedItems[identity]).valueOf(); // boolean
|
|
}else{
|
|
// return true if the store is dirty -- which means return true
|
|
// if there are any new items, dirty items, or modified items
|
|
return !this._isEmpty(this._pending._newItems) ||
|
|
!this._isEmpty(this._pending._modifiedItems) ||
|
|
!this._isEmpty(this._pending._deletedItems); // boolean
|
|
}
|
|
},
|
|
|
|
/* dojo/data/api/Notification */
|
|
|
|
onSet: function(/* dojo/data/api/Item */ item,
|
|
/*attribute-name-string*/ attribute,
|
|
/*object|array*/ oldValue,
|
|
/*object|array*/ newValue){
|
|
// summary:
|
|
// See dojo/data/api/Notification.onSet()
|
|
|
|
// No need to do anything. This method is here just so that the
|
|
// client code can connect observers to it.
|
|
},
|
|
|
|
onNew: function(/* dojo/data/api/Item */ newItem, /*object?*/ parentInfo){
|
|
// summary:
|
|
// See dojo/data/api/Notification.onNew()
|
|
|
|
// No need to do anything. This method is here just so that the
|
|
// client code can connect observers to it.
|
|
},
|
|
|
|
onDelete: function(/* dojo/data/api/Item */ deletedItem){
|
|
// summary:
|
|
// See dojo/data/api/Notification.onDelete()
|
|
|
|
// No need to do anything. This method is here just so that the
|
|
// client code can connect observers to it.
|
|
},
|
|
|
|
close: function(/* object? */ request){
|
|
// summary:
|
|
// Over-ride of base close function of ItemFileReadStore to add in check for store state.
|
|
// description:
|
|
// Over-ride of base close function of ItemFileReadStore to add in check for store state.
|
|
// If the store is still dirty (unsaved changes), then an error will be thrown instead of
|
|
// clearing the internal state for reload from the url.
|
|
|
|
//Clear if not dirty ... or throw an error
|
|
if(this.clearOnClose){
|
|
if(!this.isDirty()){
|
|
this.inherited(arguments);
|
|
}else{
|
|
//Only throw an error if the store was dirty and we were loading from a url (cannot reload from url until state is saved).
|
|
throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close.");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'url:dijit/templates/TreeNode.html':"<div class=\"dijitTreeNode\" role=\"presentation\"\n\t><div data-dojo-attach-point=\"rowNode\" class=\"dijitTreeRow dijitInline\" role=\"presentation\"\n\t\t><div data-dojo-attach-point=\"indentNode\" class=\"dijitInline\"></div\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\"></span>\n\t\t</span\n\t></div>\n\t<div data-dojo-attach-point=\"containerNode\" class=\"dijitTreeContainer\" role=\"presentation\" style=\"display: none;\"></div>\n</div>\n",
|
|
'dojo/dnd/TimedMoveable':function(){
|
|
define("dojo/dnd/TimedMoveable", ["../_base/declare", "./Moveable" /*=====, "./Mover" =====*/], function(declare, Moveable /*=====, Mover =====*/){
|
|
// module:
|
|
// dojo/dnd/TimedMoveable
|
|
|
|
/*=====
|
|
var __TimedMoveableArgs = declare([Moveable.__MoveableArgs], {
|
|
// timeout: Number
|
|
// delay move by this number of ms,
|
|
// accumulating position changes during the timeout
|
|
timeout: 0
|
|
});
|
|
=====*/
|
|
|
|
// precalculate long expressions
|
|
var oldOnMove = Moveable.prototype.onMove;
|
|
|
|
return declare("dojo.dnd.TimedMoveable", 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: __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(/*Mover*/ mover){
|
|
if(mover._timer){
|
|
// stop timer
|
|
clearTimeout(mover._timer);
|
|
// reflect the last received position
|
|
oldOnMove.call(this, mover, mover._leftTop);
|
|
}
|
|
Moveable.prototype.onMoveStop.apply(this, arguments);
|
|
},
|
|
onMove: function(/*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);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/NodeList-fx':function(){
|
|
define("dojo/NodeList-fx", ["./query", "./_base/lang", "./_base/connect", "./_base/fx", "./fx"],
|
|
function(query, lang, connectLib, baseFx, coreFx){
|
|
|
|
// module:
|
|
// dojo/NodeList-fx
|
|
|
|
/*=====
|
|
return function(){
|
|
// summary:
|
|
// Adds dojo.fx animation support to dojo.query() by extending the NodeList class
|
|
// with additional FX functions. NodeList is the array-like object used to hold query results.
|
|
};
|
|
=====*/
|
|
|
|
var NodeList = query.NodeList;
|
|
|
|
lang.extend(NodeList, {
|
|
_anim: function(obj, method, args){
|
|
args = args||{};
|
|
var a = coreFx.combine(
|
|
this.map(function(item){
|
|
var tmpArgs = { node: item };
|
|
lang.mixin(tmpArgs, args);
|
|
return obj[method](tmpArgs);
|
|
})
|
|
);
|
|
return args.auto ? a.play() && this : a; // dojo/_base/fx.Animation|dojo/NodeList
|
|
},
|
|
|
|
wipeIn: function(args){
|
|
// summary:
|
|
// wipe in all elements of this NodeList via `dojo/fx.wipeIn()`
|
|
//
|
|
// args: Object?
|
|
// Additional dojo/_base/fx.Animation arguments to mix into this set with the addition of
|
|
// an `auto` parameter.
|
|
//
|
|
// returns: dojo/_base/fx.Animation|dojo/NodeList
|
|
// A special args member `auto` can be passed to automatically play the animation.
|
|
// If args.auto is present, the original dojo/NodeList will be returned for further
|
|
// chaining. Otherwise the dojo/_base/fx.Animation instance is returned and must be .play()'ed
|
|
//
|
|
// example:
|
|
// Fade in all tables with class "blah":
|
|
// | dojo.query("table.blah").wipeIn().play();
|
|
//
|
|
// example:
|
|
// Utilizing `auto` to get the NodeList back:
|
|
// | dojo.query(".titles").wipeIn({ auto:true }).onclick(someFunction);
|
|
//
|
|
return this._anim(coreFx, "wipeIn", args); // dojo/_base/fx.Animation|dojo/NodeList
|
|
},
|
|
|
|
wipeOut: function(args){
|
|
// summary:
|
|
// wipe out all elements of this NodeList via `dojo/fx.wipeOut()`
|
|
//
|
|
// args: Object?
|
|
// Additional dojo/_base/fx.Animation arguments to mix into this set with the addition of
|
|
// an `auto` parameter.
|
|
//
|
|
// returns: dojo/_base/fx.Animation|dojo/NodeList
|
|
// A special args member `auto` can be passed to automatically play the animation.
|
|
// If args.auto is present, the original dojo/NodeList will be returned for further
|
|
// chaining. Otherwise the dojo/_base/fx.Animation instance is returned and must be .play()'ed
|
|
//
|
|
// example:
|
|
// Wipe out all tables with class "blah":
|
|
// | dojo.query("table.blah").wipeOut().play();
|
|
return this._anim(coreFx, "wipeOut", args); // dojo/_base/fx.Animation|dojo/NodeList
|
|
},
|
|
|
|
slideTo: function(args){
|
|
// summary:
|
|
// slide all elements of the node list to the specified place via `dojo/fx.slideTo()`
|
|
//
|
|
// args: Object?
|
|
// Additional dojo/_base/fx.Animation arguments to mix into this set with the addition of
|
|
// an `auto` parameter.
|
|
//
|
|
// returns: dojo/_base/fx.Animation|dojo/NodeList
|
|
// A special args member `auto` can be passed to automatically play the animation.
|
|
// If args.auto is present, the original dojo/NodeList will be returned for further
|
|
// chaining. Otherwise the dojo/_base/fx.Animation instance is returned and must be .play()'ed
|
|
//
|
|
// example:
|
|
// | Move all tables with class "blah" to 300/300:
|
|
// | dojo.query("table.blah").slideTo({
|
|
// | left: 40,
|
|
// | top: 50
|
|
// | }).play();
|
|
return this._anim(coreFx, "slideTo", args); // dojo/_base/fx.Animation|dojo/NodeList
|
|
},
|
|
|
|
|
|
fadeIn: function(args){
|
|
// summary:
|
|
// fade in all elements of this NodeList via `dojo.fadeIn`
|
|
//
|
|
// args: Object?
|
|
// Additional dojo/_base/fx.Animation arguments to mix into this set with the addition of
|
|
// an `auto` parameter.
|
|
//
|
|
// returns: dojo/_base/fx.Animation|dojo/NodeList
|
|
// A special args member `auto` can be passed to automatically play the animation.
|
|
// If args.auto is present, the original dojo/NodeList will be returned for further
|
|
// chaining. Otherwise the dojo/_base/fx.Animation instance is returned and must be .play()'ed
|
|
//
|
|
// example:
|
|
// Fade in all tables with class "blah":
|
|
// | dojo.query("table.blah").fadeIn().play();
|
|
return this._anim(baseFx, "fadeIn", args); // dojo/_base/fx.Animation|dojo/NodeList
|
|
},
|
|
|
|
fadeOut: function(args){
|
|
// summary:
|
|
// fade out all elements of this NodeList via `dojo.fadeOut`
|
|
//
|
|
// args: Object?
|
|
// Additional dojo/_base/fx.Animation arguments to mix into this set with the addition of
|
|
// an `auto` parameter.
|
|
//
|
|
// returns: dojo/_base/fx.Animation|dojo/NodeList
|
|
// A special args member `auto` can be passed to automatically play the animation.
|
|
// If args.auto is present, the original dojo/NodeList will be returned for further
|
|
// chaining. Otherwise the dojo/_base/fx.Animation instance is returned and must be .play()'ed
|
|
//
|
|
// example:
|
|
// Fade out all elements with class "zork":
|
|
// | dojo.query(".zork").fadeOut().play();
|
|
// example:
|
|
// Fade them on a delay and do something at the end:
|
|
// | var fo = dojo.query(".zork").fadeOut();
|
|
// | dojo.connect(fo, "onEnd", function(){ /*...*/ });
|
|
// | fo.play();
|
|
// example:
|
|
// Using `auto`:
|
|
// | dojo.query("li").fadeOut({ auto:true }).filter(filterFn).forEach(doit);
|
|
//
|
|
return this._anim(baseFx, "fadeOut", args); // dojo/_base/fx.Animation|dojo/NodeList
|
|
},
|
|
|
|
animateProperty: function(args){
|
|
// summary:
|
|
// Animate all elements of this NodeList across the properties specified.
|
|
// syntax identical to `dojo.animateProperty`
|
|
//
|
|
// args: Object?
|
|
// Additional dojo/_base/fx.Animation arguments to mix into this set with the addition of
|
|
// an `auto` parameter.
|
|
//
|
|
// returns: dojo/_base/fx.Animation|dojo/NodeList
|
|
// A special args member `auto` can be passed to automatically play the animation.
|
|
// If args.auto is present, the original dojo/NodeList will be returned for further
|
|
// chaining. Otherwise the dojo/_base/fx.Animation instance is returned and must be .play()'ed
|
|
//
|
|
// example:
|
|
// | dojo.query(".zork").animateProperty({
|
|
// | duration: 500,
|
|
// | properties: {
|
|
// | color: { start: "black", end: "white" },
|
|
// | left: { end: 300 }
|
|
// | }
|
|
// | }).play();
|
|
//
|
|
// example:
|
|
// | dojo.query(".grue").animateProperty({
|
|
// | auto:true,
|
|
// | properties: {
|
|
// | height:240
|
|
// | }
|
|
// | }).onclick(handler);
|
|
return this._anim(baseFx, "animateProperty", args); // dojo/_base/fx.Animation|dojo/NodeList
|
|
},
|
|
|
|
anim: function( /*Object*/ properties,
|
|
/*Integer?*/ duration,
|
|
/*Function?*/ easing,
|
|
/*Function?*/ onEnd,
|
|
/*Integer?*/ delay){
|
|
// summary:
|
|
// Animate one or more CSS properties for all nodes in this list.
|
|
// The returned animation object will already be playing when it
|
|
// is returned. See the docs for `dojo.anim` for full details.
|
|
// properties: Object
|
|
// the properties to animate. does NOT support the `auto` parameter like other
|
|
// NodeList-fx methods.
|
|
// duration: Integer?
|
|
// Optional. The time to run the animations for
|
|
// easing: Function?
|
|
// Optional. The easing function to use.
|
|
// onEnd: Function?
|
|
// A function to be called when the animation ends
|
|
// delay:
|
|
// how long to delay playing the returned animation
|
|
// example:
|
|
// Another way to fade out:
|
|
// | dojo.query(".thinger").anim({ opacity: 0 });
|
|
// example:
|
|
// animate all elements with the "thigner" class to a width of 500
|
|
// pixels over half a second
|
|
// | dojo.query(".thinger").anim({ width: 500 }, 700);
|
|
var canim = coreFx.combine(
|
|
this.map(function(item){
|
|
return baseFx.animateProperty({
|
|
node: item,
|
|
properties: properties,
|
|
duration: duration||350,
|
|
easing: easing
|
|
});
|
|
})
|
|
);
|
|
if(onEnd){
|
|
connectLib.connect(canim, "onEnd", onEnd);
|
|
}
|
|
return canim.play(delay||0); // dojo/_base/fx.Animation
|
|
}
|
|
});
|
|
|
|
return NodeList;
|
|
});
|
|
|
|
},
|
|
'dijit/form/_ListMouseMixin':function(){
|
|
define("dijit/form/_ListMouseMixin", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/mouse",
|
|
"dojo/on",
|
|
"dojo/touch",
|
|
"./_ListBase"
|
|
], function(declare, mouse, on, touch, _ListBase){
|
|
|
|
// module:
|
|
// dijit/form/_ListMouseMixin
|
|
|
|
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.own(on(this.domNode, touch.press, function(evt){ evt.preventDefault(); })); // prevent focus shift on list scrollbar press
|
|
|
|
this._listConnect(touch.press, "_onMouseDown");
|
|
this._listConnect(touch.release, "_onMouseUp");
|
|
this._listConnect(mouse.enter, "_onMouseOver");
|
|
this._listConnect(mouse.leave, "_onMouseOut");
|
|
},
|
|
|
|
_onMouseDown: function(/*Event*/ evt, /*DomNode*/ target){
|
|
if(this._hoveredNode){
|
|
this.onUnhover(this._hoveredNode);
|
|
this._hoveredNode = null;
|
|
}
|
|
this._isDragging = true;
|
|
this._setSelectedAttr(target);
|
|
},
|
|
|
|
_onMouseUp: function(/*Event*/ evt, /*DomNode*/ target){
|
|
this._isDragging = false;
|
|
var selectedNode = this.selected;
|
|
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, /*DomNode*/ target){
|
|
if(this._hoveredNode){
|
|
this.onUnhover(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, /*DomNode*/ target){
|
|
if(this._cancelDrag){
|
|
var time = (new Date()).getTime();
|
|
if(time > this._cancelDrag){
|
|
this._isDragging = false;
|
|
}
|
|
this._cancelDrag = null;
|
|
}
|
|
this._hoveredNode = target;
|
|
this.onHover(target);
|
|
if(this._isDragging){
|
|
this._setSelectedAttr(target);
|
|
}
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'url:dijit/templates/Tree.html':"<div class=\"dijitTree dijitTreeContainer\" role=\"tree\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" data-dojo-attach-point=\"indentDetector\"></div>\n</div>\n",
|
|
'dojo/cookie':function(){
|
|
define("dojo/cookie", ["./_base/kernel", "./regexp"], function(dojo, regexp){
|
|
|
|
// module:
|
|
// dojo/cookie
|
|
|
|
/*=====
|
|
var __cookieProps = {
|
|
// 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
|
|
};
|
|
=====*/
|
|
|
|
|
|
dojo.cookie = function(/*String*/name, /*String?*/ value, /*__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:
|
|
// | require(["dojo/cookie", "dojo/json"], function(cookie, json){
|
|
// | cookie("configObj", json.stringify(config, {expires: 5 }));
|
|
// | });
|
|
//
|
|
// example:
|
|
// de-serialize a cookie back into a JavaScript object:
|
|
// | require(["dojo/cookie", "dojo/json"], function(cookie, json){
|
|
// | config = json.parse(cookie("configObj"));
|
|
// | });
|
|
//
|
|
// example:
|
|
// delete a cookie:
|
|
// | require(["dojo/cookie"], function(cookie){
|
|
// | 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;
|
|
});
|
|
|
|
},
|
|
'dojo/cache':function(){
|
|
define("dojo/cache", ["./_base/kernel", "./text"], function(dojo){
|
|
// module:
|
|
// dojo/cache
|
|
|
|
// dojo.cache is defined in dojo/text
|
|
return dojo.cache;
|
|
});
|
|
|
|
},
|
|
'url:dijit/form/templates/DropDownBox.html':"<div class=\"dijit dijitReset dijitInline dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdata-dojo-attach-point=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"button presentation\" aria-hidden=\"true\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdata-dojo-attach-point=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n",
|
|
'dijit/ProgressBar':function(){
|
|
require({cache:{
|
|
'url:dijit/templates/ProgressBar.html':"<div class=\"dijitProgressBar dijitProgressBarEmpty\" role=\"progressbar\"\n\t><div data-dojo-attach-point=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\" role=\"presentation\"></div\n\t\t><span style=\"visibility:hidden\"> </span\n\t></div\n\t><div data-dojo-attach-point=\"labelNode\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"></div\n\t><img data-dojo-attach-point=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n"}});
|
|
define("dijit/ProgressBar", [
|
|
"require", // require.toUrl
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.toggle
|
|
"dojo/_base/lang", // lang.mixin
|
|
"dojo/number", // number.format
|
|
"./_Widget",
|
|
"./_TemplatedMixin",
|
|
"dojo/text!./templates/ProgressBar.html"
|
|
], function(require, declare, domClass, lang, number, _Widget, _TemplatedMixin, template){
|
|
|
|
// module:
|
|
// dijit/ProgressBar
|
|
|
|
|
|
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);
|
|
|
|
// Back-compat for when constructor specifies indeterminate or progress, rather than value. Remove for 2.0.
|
|
if(!(this.params && "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");
|
|
}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-valuenow", this.progress);
|
|
}
|
|
|
|
// Even indeterminate ProgressBars should have these attributes
|
|
ap.setAttribute("aria-describedby", this.labelNode.id);
|
|
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/_base/popup':function(){
|
|
define("dijit/_base/popup", [
|
|
"dojo/dom-class", // domClass.contains
|
|
"dojo/_base/window",
|
|
"../popup",
|
|
"../BackgroundIframe" // just loading for back-compat, in case client code is referencing it
|
|
], function(domClass, win, popup){
|
|
|
|
// module:
|
|
// dijit/_base/popup
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// Deprecated. Old module for popups, new code should use dijit/popup directly.
|
|
};
|
|
=====*/
|
|
|
|
|
|
// Hack support for old API passing in node instead of a widget (to various methods)
|
|
var origCreateWrapper = popup._createWrapper;
|
|
popup._createWrapper = function(widget){
|
|
if(!widget.declaredClass){
|
|
// make fake widget to pass to new API
|
|
widget = {
|
|
_popupWrapper: (widget.parentNode && domClass.contains(widget.parentNode, "dijitPopup")) ?
|
|
widget.parentNode : null,
|
|
domNode: widget,
|
|
destroy: function(){},
|
|
ownerDocument: widget.ownerDocument,
|
|
ownerDocumentBody: win.body(widget.ownerDocument)
|
|
};
|
|
}
|
|
return origCreateWrapper.call(this, widget);
|
|
};
|
|
|
|
// Support old format of orient parameter
|
|
var origOpen = popup.open;
|
|
popup.open = function(/*__OpenArgs*/ args){
|
|
// Convert old hash structure (ex: {"BL": "TL", ...}) of orient to format compatible w/new popup.open() API.
|
|
// Don't do conversion for:
|
|
// - null parameter (that means to use the default positioning)
|
|
// - "R" or "L" strings used to indicate positioning for context menus (when there is no around node)
|
|
// - new format, ex: ["below", "above"]
|
|
// - return value from deprecated dijit.getPopupAroundAlignment() method,
|
|
// ex: ["below", "above"]
|
|
if(args.orient && typeof args.orient != "string" && !("length" in args.orient)){
|
|
var ary = [];
|
|
for(var key in args.orient){
|
|
ary.push({aroundCorner: key, corner: args.orient[key]});
|
|
}
|
|
args.orient = ary;
|
|
}
|
|
|
|
return origOpen.call(this, args);
|
|
};
|
|
|
|
return popup;
|
|
});
|
|
|
|
},
|
|
'dijit/ColorPalette':function(){
|
|
require({cache:{
|
|
'url:dijit/templates/ColorPalette.html':"<div class=\"dijitInline dijitColorPalette\">\n\t<table dojoAttachPoint=\"paletteTableNode\" class=\"dijitPaletteTable\" cellSpacing=\"0\" cellPadding=\"0\" role=\"grid\">\n\t\t<tbody data-dojo-attach-point=\"gridNode\"></tbody>\n\t</table>\n</div>\n"}});
|
|
define("dijit/ColorPalette", [
|
|
"require", // require.toUrl
|
|
"dojo/text!./templates/ColorPalette.html",
|
|
"./_Widget", // used also to load dijit/hccss for setting has("highcontrast")
|
|
"./_TemplatedMixin",
|
|
"./_PaletteMixin",
|
|
"./hccss", // has("highcontrast")
|
|
"dojo/i18n", // i18n.getLocalization
|
|
"dojo/_base/Color", // dojo.Color dojo.Color.named
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-construct", // domConstruct.place
|
|
"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, has, i18n, Color,
|
|
declare, domConstruct, string){
|
|
|
|
// module:
|
|
// dijit/ColorPalette
|
|
|
|
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, title){
|
|
// Overrides _PaletteMixin._dyeFactory().
|
|
return new this._dyeClass(value, row, col, title);
|
|
},
|
|
|
|
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, {
|
|
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}' title='${title}' 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}' title='${title}' 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(alias, row, col, title){
|
|
// summary:
|
|
// Constructor for ColorPalette._Color
|
|
// alias: String
|
|
// English name of the color.
|
|
// row: Number
|
|
// Vertical position in grid.
|
|
// column: Number
|
|
// Horizontal position in grid.
|
|
// title: String
|
|
// Localized name of the color.
|
|
this._title = title;
|
|
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(has("highcontrast") ? this.hcTemplate : this.template, {
|
|
// substitution variables for normal mode
|
|
color: this.toHex(),
|
|
blankGif: blankGif,
|
|
alt: this._title,
|
|
title: this._title,
|
|
|
|
// 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",
|
|
'dojo/_base/url':function(){
|
|
define("dojo/_base/url", ["./kernel"], function(dojo){
|
|
// module:
|
|
// dojo/url
|
|
|
|
var
|
|
ore = new RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$"),
|
|
ire = new RegExp("^((([^\\[:]+):)?([^@]+)@)?(\\[([^\\]]+)\\]|([^\\[:]*))(:([0-9]+))?$"),
|
|
_Url = function(){
|
|
var n = null,
|
|
_a = arguments,
|
|
uri = [_a[0]];
|
|
// resolve uri components relative to each other
|
|
for(var i = 1; i<_a.length; i++){
|
|
if(!_a[i]){ continue; }
|
|
|
|
// Safari doesn't support this.constructor so we have to be explicit
|
|
// FIXME: Tracked (and fixed) in Webkit bug 3537.
|
|
// http://bugs.webkit.org/show_bug.cgi?id=3537
|
|
var relobj = new _Url(_a[i]+""),
|
|
uriobj = new _Url(uri[0]+"");
|
|
|
|
if(
|
|
relobj.path == "" &&
|
|
!relobj.scheme &&
|
|
!relobj.authority &&
|
|
!relobj.query
|
|
){
|
|
if(relobj.fragment != n){
|
|
uriobj.fragment = relobj.fragment;
|
|
}
|
|
relobj = uriobj;
|
|
}else if(!relobj.scheme){
|
|
relobj.scheme = uriobj.scheme;
|
|
|
|
if(!relobj.authority){
|
|
relobj.authority = uriobj.authority;
|
|
|
|
if(relobj.path.charAt(0) != "/"){
|
|
var path = uriobj.path.substring(0,
|
|
uriobj.path.lastIndexOf("/") + 1) + relobj.path;
|
|
|
|
var segs = path.split("/");
|
|
for(var j = 0; j < segs.length; j++){
|
|
if(segs[j] == "."){
|
|
// flatten "./" references
|
|
if(j == segs.length - 1){
|
|
segs[j] = "";
|
|
}else{
|
|
segs.splice(j, 1);
|
|
j--;
|
|
}
|
|
}else if(j > 0 && !(j == 1 && segs[0] == "") &&
|
|
segs[j] == ".." && segs[j-1] != ".."){
|
|
// flatten "../" references
|
|
if(j == (segs.length - 1)){
|
|
segs.splice(j, 1);
|
|
segs[j - 1] = "";
|
|
}else{
|
|
segs.splice(j - 1, 2);
|
|
j -= 2;
|
|
}
|
|
}
|
|
}
|
|
relobj.path = segs.join("/");
|
|
}
|
|
}
|
|
}
|
|
|
|
uri = [];
|
|
if(relobj.scheme){
|
|
uri.push(relobj.scheme, ":");
|
|
}
|
|
if(relobj.authority){
|
|
uri.push("//", relobj.authority);
|
|
}
|
|
uri.push(relobj.path);
|
|
if(relobj.query){
|
|
uri.push("?", relobj.query);
|
|
}
|
|
if(relobj.fragment){
|
|
uri.push("#", relobj.fragment);
|
|
}
|
|
}
|
|
|
|
this.uri = uri.join("");
|
|
|
|
// break the uri into its main components
|
|
var r = this.uri.match(ore);
|
|
|
|
this.scheme = r[2] || (r[1] ? "" : n);
|
|
this.authority = r[4] || (r[3] ? "" : n);
|
|
this.path = r[5]; // can never be undefined
|
|
this.query = r[7] || (r[6] ? "" : n);
|
|
this.fragment = r[9] || (r[8] ? "" : n);
|
|
|
|
if(this.authority != n){
|
|
// server based naming authority
|
|
r = this.authority.match(ire);
|
|
|
|
this.user = r[3] || n;
|
|
this.password = r[4] || n;
|
|
this.host = r[6] || r[7]; // ipv6 || ipv4
|
|
this.port = r[9] || n;
|
|
}
|
|
};
|
|
_Url.prototype.toString = function(){ return this.uri; };
|
|
|
|
return dojo._Url = _Url;
|
|
});
|
|
|
|
},
|
|
'url:dijit/templates/MenuItem.html':"<tr class=\"dijitReset dijitMenuItem\" data-dojo-attach-point=\"focusNode\" role=\"menuitem\" tabIndex=\"-1\">\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",
|
|
'dojo/text':function(){
|
|
define("dojo/text", ["./_base/kernel", "require", "./has", "./_base/xhr"], function(dojo, require, has, xhr){
|
|
// module:
|
|
// dojo/text
|
|
|
|
var getText;
|
|
if( 1 ){
|
|
getText= function(url, sync, load){
|
|
xhr("GET", {url: url, sync:!!sync, load: load, headers: dojo.config.textPluginHeaders || {}});
|
|
};
|
|
}else{
|
|
// TODOC: only works for dojo AMD loader
|
|
if(require.getText){
|
|
getText= require.getText;
|
|
}else{
|
|
console.error("dojo/text plugin failed to load because loader does not support getText");
|
|
}
|
|
}
|
|
|
|
var
|
|
theCache = {},
|
|
|
|
strip= function(text){
|
|
//Strips <?xml ...?> declarations so that external SVG and XML
|
|
//documents can be added to a document without worry. Also, if the string
|
|
//is an HTML document, only the part inside the body tag is returned.
|
|
if(text){
|
|
text= text.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, "");
|
|
var matches= text.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
|
|
if(matches){
|
|
text= matches[1];
|
|
}
|
|
}else{
|
|
text = "";
|
|
}
|
|
return text;
|
|
},
|
|
|
|
notFound = {},
|
|
|
|
pending = {};
|
|
|
|
dojo.cache = function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){
|
|
// summary:
|
|
// A getter and setter for storing the string content associated with the
|
|
// module and url arguments.
|
|
// description:
|
|
// If module is a string that contains slashes, then it is interpretted as a fully
|
|
// resolved path (typically a result returned by require.toUrl), and url should not be
|
|
// provided. This is the preferred signature. If module is a string that does not
|
|
// contain slashes, then url must also be provided and module and url are used to
|
|
// call `dojo.moduleUrl()` to generate a module URL. This signature is deprecated.
|
|
// If value is specified, the cache value for the moduleUrl will be set to
|
|
// that value. Otherwise, dojo.cache will fetch the moduleUrl and store it
|
|
// in its internal cache and return that cached value for the URL. To clear
|
|
// a cache value pass null for value. Since XMLHttpRequest (XHR) is used to fetch the
|
|
// the URL contents, only modules on the same domain of the page can use this capability.
|
|
// The build system can inline the cache values though, to allow for xdomain hosting.
|
|
// module: String||Object
|
|
// If a String with slashes, a fully resolved path; if a String without slashes, the
|
|
// module name to use for the base part of the URL, similar to module argument
|
|
// to `dojo.moduleUrl`. If an Object, something that has a .toString() method that
|
|
// generates a valid path for the cache item. For example, a dojo._Url object.
|
|
// url: String
|
|
// The rest of the path to append to the path derived from the module argument. If
|
|
// module is an object, then this second argument should be the "value" argument instead.
|
|
// value: String||Object?
|
|
// If a String, the value to use in the cache for the module/url combination.
|
|
// If an Object, it can have two properties: value and sanitize. The value property
|
|
// should be the value to use in the cache, and sanitize can be set to true or false,
|
|
// to indicate if XML declarations should be removed from the value and if the HTML
|
|
// inside a body tag in the value should be extracted as the real value. The value argument
|
|
// or the value property on the value argument are usually only used by the build system
|
|
// as it inlines cache content.
|
|
// example:
|
|
// To ask dojo.cache to fetch content and store it in the cache (the dojo["cache"] style
|
|
// of call is used to avoid an issue with the build system erroneously trying to intern
|
|
// this example. To get the build system to intern your dojo.cache calls, use the
|
|
// "dojo.cache" style of call):
|
|
// | //If template.html contains "<h1>Hello</h1>" that will be
|
|
// | //the value for the text variable.
|
|
// | var text = dojo["cache"]("my.module", "template.html");
|
|
// example:
|
|
// To ask dojo.cache to fetch content and store it in the cache, and sanitize the input
|
|
// (the dojo["cache"] style of call is used to avoid an issue with the build system
|
|
// erroneously trying to intern this example. To get the build system to intern your
|
|
// dojo.cache calls, use the "dojo.cache" style of call):
|
|
// | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
|
|
// | //text variable will contain just "<h1>Hello</h1>".
|
|
// | var text = dojo["cache"]("my.module", "template.html", {sanitize: true});
|
|
// example:
|
|
// Same example as previous, but demonstrates how an object can be passed in as
|
|
// the first argument, then the value argument can then be the second argument.
|
|
// | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
|
|
// | //text variable will contain just "<h1>Hello</h1>".
|
|
// | var text = dojo["cache"](new dojo._Url("my/module/template.html"), {sanitize: true});
|
|
|
|
// * (string string [value]) => (module, url, value)
|
|
// * (object [value]) => (module, value), url defaults to ""
|
|
//
|
|
// * if module is an object, then it must be convertable to a string
|
|
// * (module, url) module + (url ? ("/" + url) : "") must be a legal argument to require.toUrl
|
|
// * value may be a string or an object; if an object then may have the properties "value" and/or "sanitize"
|
|
var key;
|
|
if(typeof module=="string"){
|
|
if(/\//.test(module)){
|
|
// module is a version 1.7+ resolved path
|
|
key = module;
|
|
value = url;
|
|
}else{
|
|
// module is a version 1.6- argument to dojo.moduleUrl
|
|
key = require.toUrl(module.replace(/\./g, "/") + (url ? ("/" + url) : ""));
|
|
}
|
|
}else{
|
|
key = module + "";
|
|
value = url;
|
|
}
|
|
var
|
|
val = (value != undefined && typeof value != "string") ? value.value : value,
|
|
sanitize = value && value.sanitize;
|
|
|
|
if(typeof val == "string"){
|
|
//We have a string, set cache value
|
|
theCache[key] = val;
|
|
return sanitize ? strip(val) : val;
|
|
}else if(val === null){
|
|
//Remove cached value
|
|
delete theCache[key];
|
|
return null;
|
|
}else{
|
|
//Allow cache values to be empty strings. If key property does
|
|
//not exist, fetch it.
|
|
if(!(key in theCache)){
|
|
getText(key, true, function(text){
|
|
theCache[key]= text;
|
|
});
|
|
}
|
|
return sanitize ? strip(theCache[key]) : theCache[key];
|
|
}
|
|
};
|
|
|
|
return {
|
|
// summary:
|
|
// This module implements the dojo/text! plugin and the dojo.cache API.
|
|
// description:
|
|
// We choose to include our own plugin to leverage functionality already contained in dojo
|
|
// and thereby reduce the size of the plugin compared to various foreign loader implementations.
|
|
// Also, this allows foreign AMD loaders to be used without their plugins.
|
|
//
|
|
// CAUTION: this module is designed to optionally function synchronously to support the dojo v1.x synchronous
|
|
// loader. This feature is outside the scope of the CommonJS plugins specification.
|
|
|
|
// the dojo/text caches it's own resources because of dojo.cache
|
|
dynamic: true,
|
|
|
|
normalize: function(id, toAbsMid){
|
|
// id is something like (path may be relative):
|
|
//
|
|
// "path/to/text.html"
|
|
// "path/to/text.html!strip"
|
|
var parts= id.split("!"),
|
|
url= parts[0];
|
|
return (/^\./.test(url) ? toAbsMid(url) : url) + (parts[1] ? "!" + parts[1] : "");
|
|
},
|
|
|
|
load: function(id, require, load){
|
|
// id: String
|
|
// Path to the resource.
|
|
// require: Function
|
|
// Object that include the function toUrl with given id returns a valid URL from which to load the text.
|
|
// load: Function
|
|
// Callback function which will be called, when the loading finished.
|
|
|
|
// id is something like (path is always absolute):
|
|
//
|
|
// "path/to/text.html"
|
|
// "path/to/text.html!strip"
|
|
var
|
|
parts= id.split("!"),
|
|
stripFlag= parts.length>1,
|
|
absMid= parts[0],
|
|
url = require.toUrl(parts[0]),
|
|
requireCacheUrl = "url:" + url,
|
|
text = notFound,
|
|
finish = function(text){
|
|
load(stripFlag ? strip(text) : text);
|
|
};
|
|
if(absMid in theCache){
|
|
text = theCache[absMid];
|
|
}else if(requireCacheUrl in require.cache){
|
|
text = require.cache[requireCacheUrl];
|
|
}else if(url in theCache){
|
|
text = theCache[url];
|
|
}
|
|
if(text===notFound){
|
|
if(pending[url]){
|
|
pending[url].push(finish);
|
|
}else{
|
|
var pendingList = pending[url] = [finish];
|
|
getText(url, !require.async, function(text){
|
|
theCache[absMid]= theCache[url]= text;
|
|
for(var i = 0; i<pendingList.length;){
|
|
pendingList[i++](text);
|
|
}
|
|
delete pending[url];
|
|
});
|
|
}
|
|
}else{
|
|
finish(text);
|
|
}
|
|
}
|
|
};
|
|
|
|
});
|
|
|
|
|
|
},
|
|
'url:dijit/form/templates/CheckBox.html':"<div class=\"dijit dijitReset dijitInline\" role=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" role=\"${type}\" aria-checked=\"false\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdata-dojo-attach-point=\"focusNode\"\n\t \tdata-dojo-attach-event=\"onclick:_onClick\"\n/></div>\n",
|
|
'dojo/uacss':function(){
|
|
define("dojo/uacss", ["./dom-geometry", "./_base/lang", "./ready", "./sniff", "./_base/window"],
|
|
function(geometry, lang, ready, has, baseWindow){
|
|
|
|
// module:
|
|
// dojo/uacss
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// Applies pre-set CSS classes to the top-level HTML node, based on:
|
|
//
|
|
// - browser (ex: dj_ie)
|
|
// - browser version (ex: dj_ie6)
|
|
// - box model (ex: dj_contentBox)
|
|
// - text direction (ex: dijitRtl)
|
|
//
|
|
// In addition, browser, browser version, and box model are
|
|
// combined with an RTL flag when browser text is RTL. ex: dj_ie-rtl.
|
|
//
|
|
// Returns the has() method.
|
|
};
|
|
=====*/
|
|
|
|
var
|
|
html = baseWindow.doc.documentElement,
|
|
ie = has("ie"),
|
|
opera = has("opera"),
|
|
maj = Math.floor,
|
|
ff = has("ff"),
|
|
boxModel = geometry.boxModel.replace(/-/,''),
|
|
|
|
classes = {
|
|
"dj_quirks": has("quirks"),
|
|
|
|
// NOTE: Opera not supported by dijit
|
|
"dj_opera": opera,
|
|
|
|
"dj_khtml": has("khtml"),
|
|
|
|
"dj_webkit": has("webkit"),
|
|
"dj_safari": has("safari"),
|
|
"dj_chrome": has("chrome"),
|
|
|
|
"dj_gecko": has("mozilla")
|
|
}; // no dojo unsupported browsers
|
|
|
|
if(ie){
|
|
classes["dj_ie"] = true;
|
|
classes["dj_ie" + maj(ie)] = true;
|
|
classes["dj_iequirks"] = has("quirks");
|
|
}
|
|
if(ff){
|
|
classes["dj_ff" + maj(ff)] = true;
|
|
}
|
|
|
|
classes["dj_" + boxModel] = true;
|
|
|
|
// apply browser, browser version, and box model class names
|
|
var classStr = "";
|
|
for(var clz in classes){
|
|
if(classes[clz]){
|
|
classStr += clz + " ";
|
|
}
|
|
}
|
|
html.className = lang.trim(html.className + " " + classStr);
|
|
|
|
// If RTL mode, then add dj_rtl flag plus repeat existing classes with -rtl extension.
|
|
// We can't run the code below until the <body> tag has loaded (so we can check for dir=rtl).
|
|
// priority is 90 to run ahead of parser priority of 100
|
|
ready(90, function(){
|
|
if(!geometry.isBodyLtr()){
|
|
var rtlClassStr = "dj_rtl dijitRtl " + classStr.replace(/ /g, "-rtl ");
|
|
html.className = lang.trim(html.className + " " + rtlClassStr + "dj_rtl dijitRtl " + classStr.replace(/ /g, "-rtl "));
|
|
}
|
|
});
|
|
return has;
|
|
});
|
|
|
|
},
|
|
'dijit/Tooltip':function(){
|
|
require({cache:{
|
|
'url:dijit/templates/Tooltip.html':"<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\"\n\t><div class=\"dijitTooltipContainer dijitTooltipContents\" data-dojo-attach-point=\"containerNode\" role='alert'></div\n\t><div class=\"dijitTooltipConnector\" data-dojo-attach-point=\"connectorNode\"></div\n></div>\n"}});
|
|
define("dijit/Tooltip", [
|
|
"dojo/_base/array", // array.forEach array.indexOf array.map
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/fx", // fx.fadeIn fx.fadeOut
|
|
"dojo/dom", // dom.byId
|
|
"dojo/dom-class", // domClass.add
|
|
"dojo/dom-geometry", // domGeometry.position
|
|
"dojo/dom-style", // domStyle.set, domStyle.get
|
|
"dojo/_base/lang", // lang.hitch lang.isArrayLike
|
|
"dojo/mouse",
|
|
"dojo/on",
|
|
"dojo/sniff", // has("ie")
|
|
"./_base/manager", // manager.defaultDuration
|
|
"./place",
|
|
"./_Widget",
|
|
"./_TemplatedMixin",
|
|
"./BackgroundIframe",
|
|
"dojo/text!./templates/Tooltip.html",
|
|
"./main" // sets dijit.showTooltip etc. for back-compat
|
|
], function(array, declare, fx, dom, domClass, domGeometry, domStyle, lang, mouse, on, has,
|
|
manager, place, _Widget, _TemplatedMixin, BackgroundIframe, template, dijit){
|
|
|
|
// module:
|
|
// dijit/Tooltip
|
|
|
|
|
|
// TODO: Tooltip should really share more positioning code with TooltipDialog, like:
|
|
// - the orient() method
|
|
// - the connector positioning code in show()
|
|
// - the dijitTooltip[Dialog] class
|
|
//
|
|
// The problem is that Tooltip's implementation supplies it's own <iframe> and interacts directly
|
|
// with dijit/place, rather than going through dijit/popup like TooltipDialog and other popups (ex: Menu).
|
|
|
|
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(){
|
|
this.ownerDocumentBody.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/place.__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;
|
|
}
|
|
|
|
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;
|
|
|
|
if(textDir){
|
|
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";
|
|
}else{
|
|
// Not *-centered, but just above/below/after/before
|
|
this.connectorNode.style.left = "";
|
|
this.connectorNode.style.top = "";
|
|
}
|
|
|
|
// 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
|
|
|
|
var heightAvailable = spaceAvailable.h,
|
|
widthAvailable = spaceAvailable.w;
|
|
|
|
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];
|
|
|
|
// reset width; it may have been set by orient() on a previous tooltip show()
|
|
this.domNode.style.width = "auto";
|
|
|
|
// Reduce tooltip's width to the amount of width available, so that it doesn't overflow screen.
|
|
// Note that sometimes widthAvailable is negative, but we guard against setting style.width to a
|
|
// negative number since that causes an exception on IE.
|
|
var size = domGeometry.position(this.domNode);
|
|
if(has("ie") == 9){
|
|
// workaround strange IE9 bug where setting width to offsetWidth causes words to wrap
|
|
size.w += 2;
|
|
}
|
|
|
|
var width = Math.min((Math.max(widthAvailable,1)), size.w);
|
|
|
|
domGeometry.setMarginBox(this.domNode, {w: width});
|
|
|
|
// Reposition the tooltip connector.
|
|
if(tooltipCorner.charAt(0) == 'B' && aroundCorner.charAt(0) == 'B'){
|
|
var bb = domGeometry.position(node);
|
|
var tooltipConnectorHeight = this.connectorNode.offsetHeight;
|
|
if(bb.h > heightAvailable){
|
|
// The tooltip starts at the top of the page and will extend past the aroundNode
|
|
var aroundNodePlacement = heightAvailable - ((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),
|
|
bb.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 - widthAvailable);
|
|
},
|
|
|
|
_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", 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: place.__Rectangle
|
|
// Specifies that tooltip should be next to this node / area
|
|
// position: String[]?
|
|
// List of positions to try to position tooltip (ex: ["right", "above"])
|
|
// rtl: Boolean?
|
|
// Corresponds to `WidgetBase.dir` attribute, where false means "ltr" and true
|
|
// means "rtl"; specifies GUI direction, not text direction.
|
|
// textDir: String?
|
|
// Corresponds to `WidgetBase.textdir` attribute; specifies direction of text.
|
|
|
|
// After/before don't work, but for back-compat convert them to the working after-centered, before-centered.
|
|
// Possibly remove this in 2.0. Alternately, get before/after to work.
|
|
if(position){
|
|
position = array.map(position, function(val){
|
|
return {after: "after-centered", before: "before-centered"}[val] || val;
|
|
});
|
|
}
|
|
|
|
if(!Tooltip._masterTT){ dijit._masterTT = Tooltip._masterTT = new MasterTooltip(); }
|
|
return Tooltip._masterTT.show(innerHTML, aroundNode, position, rtl, textDir);
|
|
};
|
|
|
|
dijit.hideTooltip = function(aroundNode){
|
|
// summary:
|
|
// Static method to hide the tooltip displayed via showTooltip()
|
|
return Tooltip._masterTT && Tooltip._masterTT.hide(aroundNode);
|
|
};
|
|
|
|
var Tooltip = declare("dijit.Tooltip", _Widget, {
|
|
// summary:
|
|
// Pops up a tooltip (a help message) when you hover over a node.
|
|
// Also provides static show() and hide() methods that can be used without instantiating a dijit/Tooltip.
|
|
|
|
// 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[]|DomNode|DomNode[]
|
|
// Id of domNode(s) to attach the tooltip to.
|
|
// When user hovers over specified dom node(s), the tooltip will appear.
|
|
connectId: [],
|
|
|
|
// position: String[]
|
|
// See description of `dijit/Tooltip.defaultPosition` for details on position parameter.
|
|
position: [],
|
|
|
|
// selector: String?
|
|
// CSS expression to apply this Tooltip to descendants of connectIds, rather than to
|
|
// the nodes specified by connectIds themselves. Useful for applying a Tooltip to
|
|
// a range of rows in a table, tree, etc. Use in conjunction with getContent() parameter.
|
|
// Ex: connectId: myTable, selector: "tr", getContent: function(node){ return ...; }
|
|
//
|
|
// The application must require() an appropriate level of dojo/query to handle the selector.
|
|
selector: "",
|
|
|
|
// TODO: in 2.0 remove support for multiple connectIds. selector gives the same effect.
|
|
// So, change connectId to a "", remove addTarget()/removeTarget(), etc.
|
|
|
|
_setConnectIdAttr: function(/*String|String[]}DomNode|DomNode[]*/ 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, function(handle){ handle.remove(); });
|
|
}, 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, this.ownerDocument); }, this);
|
|
|
|
// Make connections
|
|
this._connections = array.map(this._connectIds, function(id){
|
|
var node = dom.byId(id, this.ownerDocument),
|
|
selector = this.selector,
|
|
delegatedEvent = selector ?
|
|
function(eventType){ return on.selector(selector, eventType); } :
|
|
function(eventType){ return eventType; },
|
|
self = this;
|
|
return [
|
|
on(node, delegatedEvent(mouse.enter), function(){
|
|
self._onHover(this);
|
|
}),
|
|
on(node, delegatedEvent("focusin"), function(){
|
|
self._onHover(this);
|
|
}),
|
|
on(node, delegatedEvent(mouse.leave), lang.hitch(self, "_onUnHover")),
|
|
on(node, delegatedEvent("focusout"), lang.hitch(self, "_onUnHover"))
|
|
];
|
|
}, this);
|
|
|
|
this._set("connectId", newId);
|
|
},
|
|
|
|
addTarget: function(/*OomNode|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);
|
|
},
|
|
|
|
getContent: function(/*DomNode*/ node){
|
|
// summary:
|
|
// User overridable function that return the text to display in the tooltip.
|
|
// tags:
|
|
// extension
|
|
return this.label || this.domNode.innerHTML;
|
|
},
|
|
|
|
_onHover: function(/*DomNode*/ target){
|
|
// 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){
|
|
this._showTimer = this.defer(function(){ this.open(target); }, this.showDelay);
|
|
}
|
|
},
|
|
|
|
_onUnHover: function(){
|
|
// summary:
|
|
// Despite the name of this method, it actually handles both mouseleave and blur
|
|
// events on the target node, hiding the tooltip.
|
|
// tags:
|
|
// private
|
|
|
|
if(this._showTimer){
|
|
this._showTimer.remove();
|
|
delete this._showTimer;
|
|
}
|
|
this.close();
|
|
},
|
|
|
|
open: function(/*DomNode*/ target){
|
|
// summary:
|
|
// Display the tooltip; usually not called directly.
|
|
// tags:
|
|
// private
|
|
|
|
if(this._showTimer){
|
|
this._showTimer.remove();
|
|
delete this._showTimer;
|
|
}
|
|
|
|
var content = this.getContent(target);
|
|
if(!content){
|
|
return;
|
|
}
|
|
Tooltip.show(content, target, this.position, !this.isLeftToRight(), this.textDir);
|
|
|
|
this._connectNode = target; // _connectNode means "tooltip currently displayed for this node"
|
|
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)
|
|
this._showTimer.remove();
|
|
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
|
|
},
|
|
|
|
destroy: function(){
|
|
this.close();
|
|
|
|
// Remove connections manually since they aren't registered to be removed by _WidgetBase
|
|
array.forEach(this._connections || [], function(nested){
|
|
array.forEach(nested, function(handle){ handle.remove(); });
|
|
}, this);
|
|
|
|
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
|
|
|
|
Tooltip.defaultPosition = ["after-centered", "before-centered"];
|
|
|
|
/*=====
|
|
lang.mixin(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.
|
|
});
|
|
=====*/
|
|
return Tooltip;
|
|
});
|
|
|
|
},
|
|
'dojo/string':function(){
|
|
define("dojo/string", [
|
|
"./_base/kernel", // kernel.global
|
|
"./_base/lang"
|
|
], function(kernel, lang){
|
|
|
|
// module:
|
|
// dojo/string
|
|
|
|
var string = {
|
|
// summary:
|
|
// String utilities for Dojo
|
|
};
|
|
lang.setObject("dojo.string", string);
|
|
|
|
string.rep = function(/*String*/str, /*Integer*/num){
|
|
// summary:
|
|
// Efficiently replicate a string `n` times.
|
|
// str:
|
|
// the string to replicate
|
|
// num:
|
|
// number of times to replicate the string
|
|
|
|
if(num <= 0 || !str){ return ""; }
|
|
|
|
var buf = [];
|
|
for(;;){
|
|
if(num & 1){
|
|
buf.push(str);
|
|
}
|
|
if(!(num >>= 1)){ break; }
|
|
str += str;
|
|
}
|
|
return buf.join(""); // String
|
|
};
|
|
|
|
string.pad = function(/*String*/text, /*Integer*/size, /*String?*/ch, /*Boolean?*/end){
|
|
// summary:
|
|
// Pad a string to guarantee that it is at least `size` length by
|
|
// filling with the character `ch` at either the start or end of the
|
|
// string. Pads at the start, by default.
|
|
// text:
|
|
// the string to pad
|
|
// size:
|
|
// length to provide padding
|
|
// ch:
|
|
// character to pad, defaults to '0'
|
|
// end:
|
|
// adds padding at the end if true, otherwise pads at start
|
|
// example:
|
|
// | // Fill the string to length 10 with "+" characters on the right. Yields "Dojo++++++".
|
|
// | string.pad("Dojo", 10, "+", true);
|
|
|
|
if(!ch){
|
|
ch = '0';
|
|
}
|
|
var out = String(text),
|
|
pad = string.rep(ch, Math.ceil((size - out.length) / ch.length));
|
|
return end ? out + pad : pad + out; // String
|
|
};
|
|
|
|
string.substitute = function( /*String*/ template,
|
|
/*Object|Array*/map,
|
|
/*Function?*/ transform,
|
|
/*Object?*/ thisObject){
|
|
// summary:
|
|
// Performs parameterized substitutions on a string. Throws an
|
|
// exception if any parameter is unmatched.
|
|
// template:
|
|
// a string with expressions in the form `${key}` to be replaced or
|
|
// `${key:format}` which specifies a format function. keys are case-sensitive.
|
|
// map:
|
|
// hash to search for substitutions
|
|
// transform:
|
|
// a function to process all parameters before substitution takes
|
|
// place, e.g. mylib.encodeXML
|
|
// thisObject:
|
|
// where to look for optional format function; default to the global
|
|
// namespace
|
|
// example:
|
|
// Substitutes two expressions in a string from an Array or Object
|
|
// | // returns "File 'foo.html' is not found in directory '/temp'."
|
|
// | // by providing substitution data in an Array
|
|
// | string.substitute(
|
|
// | "File '${0}' is not found in directory '${1}'.",
|
|
// | ["foo.html","/temp"]
|
|
// | );
|
|
// |
|
|
// | // also returns "File 'foo.html' is not found in directory '/temp'."
|
|
// | // but provides substitution data in an Object structure. Dotted
|
|
// | // notation may be used to traverse the structure.
|
|
// | string.substitute(
|
|
// | "File '${name}' is not found in directory '${info.dir}'.",
|
|
// | { name: "foo.html", info: { dir: "/temp" } }
|
|
// | );
|
|
// example:
|
|
// Use a transform function to modify the values:
|
|
// | // returns "file 'foo.html' is not found in directory '/temp'."
|
|
// | string.substitute(
|
|
// | "${0} is not found in ${1}.",
|
|
// | ["foo.html","/temp"],
|
|
// | function(str){
|
|
// | // try to figure out the type
|
|
// | var prefix = (str.charAt(0) == "/") ? "directory": "file";
|
|
// | return prefix + " '" + str + "'";
|
|
// | }
|
|
// | );
|
|
// example:
|
|
// Use a formatter
|
|
// | // returns "thinger -- howdy"
|
|
// | string.substitute(
|
|
// | "${0:postfix}", ["thinger"], null, {
|
|
// | postfix: function(value, key){
|
|
// | return value + " -- howdy";
|
|
// | }
|
|
// | }
|
|
// | );
|
|
|
|
thisObject = thisObject || kernel.global;
|
|
transform = transform ?
|
|
lang.hitch(thisObject, transform) : function(v){ return v; };
|
|
|
|
return template.replace(/\$\{([^\s\:\}]+)(?:\:([^\s\:\}]+))?\}/g,
|
|
function(match, key, format){
|
|
var value = lang.getObject(key, false, map);
|
|
if(format){
|
|
value = lang.getObject(format, false, thisObject).call(thisObject, value, key);
|
|
}
|
|
return transform(value, key).toString();
|
|
}); // String
|
|
};
|
|
|
|
string.trim = String.prototype.trim ?
|
|
lang.trim : // aliasing to the native function
|
|
function(str){
|
|
str = str.replace(/^\s+/, '');
|
|
for(var i = str.length - 1; i >= 0; i--){
|
|
if(/\S/.test(str.charAt(i))){
|
|
str = str.substring(0, i + 1);
|
|
break;
|
|
}
|
|
}
|
|
return str;
|
|
};
|
|
|
|
/*=====
|
|
string.trim = function(str){
|
|
// summary:
|
|
// Trims whitespace from both sides of the string
|
|
// str: String
|
|
// String to be trimmed
|
|
// returns: String
|
|
// Returns the trimmed string
|
|
// description:
|
|
// This version of trim() was taken from [Steven Levithan's blog](http://blog.stevenlevithan.com/archives/faster-trim-javascript).
|
|
// The short yet performant version of this function is dojo.trim(),
|
|
// which is part of Dojo base. Uses String.prototype.trim instead, if available.
|
|
return ""; // String
|
|
};
|
|
=====*/
|
|
|
|
return string;
|
|
});
|
|
|
|
},
|
|
'url:dijit/templates/MenuSeparator.html':"<tr class=\"dijitMenuSeparator\">\n\t<td class=\"dijitMenuSeparatorIconCell\">\n\t\t<div class=\"dijitMenuSeparatorTop\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n\t<td colspan=\"3\" class=\"dijitMenuSeparatorLabelCell\">\n\t\t<div class=\"dijitMenuSeparatorTop dijitMenuSeparatorLabel\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n</tr>",
|
|
'dijit/layout/AccordionPane':function(){
|
|
define("dijit/layout/AccordionPane", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"./ContentPane"
|
|
], function(declare, kernel, ContentPane){
|
|
|
|
// module:
|
|
// dijit/layout/AccordionPane
|
|
|
|
return declare("dijit.layout.AccordionPane", ContentPane, {
|
|
// summary:
|
|
// Deprecated widget. Use `dijit/layout/ContentPane` instead.
|
|
// tags:
|
|
// deprecated
|
|
|
|
constructor: function(){
|
|
kernel.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead", "", "2.0");
|
|
},
|
|
|
|
onSelected: function(){
|
|
// summary:
|
|
// called when this pane is selected
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/dijit':function(){
|
|
define("dijit/dijit", [
|
|
"./main",
|
|
"./_base",
|
|
"dojo/parser",
|
|
"./_Widget",
|
|
"./_TemplatedMixin",
|
|
"./_Container",
|
|
"./layout/_LayoutWidget",
|
|
"./form/_FormWidget",
|
|
"./form/_FormValueWidget"
|
|
], function(dijit){
|
|
|
|
// module:
|
|
// dijit/dijit
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// A roll-up for common dijit methods
|
|
// All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require)
|
|
// And some other stuff that we tend to pull in all the time anyway
|
|
};
|
|
=====*/
|
|
|
|
return dijit;
|
|
});
|
|
|
|
},
|
|
'dijit/form/DropDownButton':function(){
|
|
require({cache:{
|
|
'url:dijit/form/templates/DropDownButton.html':"<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdata-dojo-attach-event=\"ondijitclick:_onClick\" data-dojo-attach-point=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdata-dojo-attach-point=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\trole=\"button\" aria-haspopup=\"true\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\"\n\t\t\t\tdata-dojo-attach-point=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdata-dojo-attach-point=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">▼</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdata-dojo-attach-point=\"valueNode\" role=\"presentation\"\n/></span>\n"}});
|
|
define("dijit/form/DropDownButton", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/lang", // hitch
|
|
"dojo/query", // query
|
|
"../registry", // registry.byNode
|
|
"../popup", // dijit.popup2.hide
|
|
"./Button",
|
|
"../_Container",
|
|
"../_HasDropDown",
|
|
"dojo/text!./templates/DropDownButton.html"
|
|
], function(declare, lang, query, registry, popup, Button, _Container, _HasDropDown, template){
|
|
|
|
// module:
|
|
// dijit/form/DropDownButton
|
|
|
|
|
|
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 DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) });
|
|
// | win.body().appendChild(button1);
|
|
//
|
|
|
|
baseClass : "dijitDropDownButton",
|
|
|
|
templateString: template,
|
|
|
|
_fillContent: function(){
|
|
// Overrides Button._fillContent().
|
|
//
|
|
// My inner HTML contains both the button contents and a drop down widget, like
|
|
// <DropDownButton> <span>push me</span> <Menu> ... </Menu> </DropDownButton>
|
|
// The first node is assumed to be the button content. The widget is the popup.
|
|
|
|
if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef
|
|
//FIXME: figure out how to filter out the widget and use all remaining nodes as button
|
|
// content, not just nodes[0]
|
|
var nodes = query("*", this.srcNodeRef);
|
|
this.inherited(arguments, [nodes[0]]);
|
|
|
|
// save pointer to srcNode so we can grab the drop down widget after it's instantiated
|
|
this.dropDownContainer = this.srcNodeRef;
|
|
}
|
|
},
|
|
|
|
startup: function(){
|
|
if(this._started){ return; }
|
|
|
|
// the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM,
|
|
// make it invisible, and store a reference to pass to the popup code.
|
|
if(!this.dropDown && this.dropDownContainer){
|
|
var dropDownNode = query("[widgetId]", this.dropDownContainer)[0];
|
|
this.dropDown = registry.byNode(dropDownNode);
|
|
delete this.dropDownContainer;
|
|
}
|
|
if(this.dropDown){
|
|
popup.hide(this.dropDown);
|
|
}
|
|
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
isLoaded: function(){
|
|
// Returns whether or not we are loaded - if our dropdown has an href,
|
|
// then we want to check that.
|
|
var dropDown = this.dropDown;
|
|
return (!!dropDown && (!dropDown.href || dropDown.isLoaded));
|
|
},
|
|
|
|
loadDropDown: function(/*Function*/ callback){
|
|
// Default implementation assumes that drop down already exists,
|
|
// but hasn't loaded it's data (ex: ContentPane w/href).
|
|
// App must override if the drop down is lazy-created.
|
|
var dropDown = this.dropDown;
|
|
var handler = dropDown.on("load", lang.hitch(this, function(){
|
|
handler.remove();
|
|
callback();
|
|
}));
|
|
dropDown.refresh(); // tell it to load
|
|
},
|
|
|
|
isFocusable: function(){
|
|
// Overridden so that focus is handled by the _HasDropDown mixin, not by
|
|
// the _FormWidget mixin.
|
|
return this.inherited(arguments) && !this._mouseDown;
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/_FormValueMixin':function(){
|
|
define("dijit/form/_FormValueMixin", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/keys", // keys.ESCAPE
|
|
"dojo/sniff", // has("ie"), has("quirks")
|
|
"./_FormWidgetMixin"
|
|
], function(declare, domAttr, keys, has, _FormWidgetMixin){
|
|
|
|
// module:
|
|
// dijit/form/_FormValueMixin
|
|
|
|
return declare("dijit.form._FormValueMixin", _FormWidgetMixin, {
|
|
// summary:
|
|
// Mixin for widgets corresponding to native HTML elements such as `<input>` or `<select>`
|
|
// that have user changeable values.
|
|
// description:
|
|
// Each _FormValueMixin represents a single input value, and has a (possibly hidden) `<input>` element,
|
|
// to which it serializes it's input value, so that form submission (either normal submission or via FormBind?)
|
|
// works as expected.
|
|
|
|
// readOnly: Boolean
|
|
// Should this widget respond to user input?
|
|
// In markup, this is specified as "readOnly".
|
|
// Similar to disabled except readOnly form values are submitted.
|
|
readOnly: false,
|
|
|
|
_setReadOnlyAttr: function(/*Boolean*/ value){
|
|
domAttr.set(this.focusNode, 'readOnly', value);
|
|
this._set("readOnly", value);
|
|
},
|
|
|
|
postCreate: function(){
|
|
this.inherited(arguments);
|
|
|
|
if(has("ie")){ // IE won't stop the event with keypress
|
|
this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown);
|
|
}
|
|
// Update our reset value if it hasn't yet been set (because this.set()
|
|
// is only called when there *is* a value)
|
|
if(this._resetValue === undefined){
|
|
this._lastValueReported = this._resetValue = this.value;
|
|
}
|
|
},
|
|
|
|
_setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
|
|
// summary:
|
|
// Hook so set('value', value) works.
|
|
// description:
|
|
// Sets the value of the widget.
|
|
// If the value has changed, then fire onChange event, unless priorityChange
|
|
// is specified as null (or false?)
|
|
this._handleOnChange(newValue, priorityChange);
|
|
},
|
|
|
|
_handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
|
|
// summary:
|
|
// Called when the value of the widget has changed. Saves the new value in this.value,
|
|
// and calls onChange() if appropriate. See _FormWidget._handleOnChange() for details.
|
|
this._set("value", newValue);
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
undo: function(){
|
|
// summary:
|
|
// Restore the value to the last value passed to onChange
|
|
this._setValueAttr(this._lastValueReported, false);
|
|
},
|
|
|
|
reset: function(){
|
|
// summary:
|
|
// Reset the widget's value to what it was at initialization time
|
|
this._hasBeenBlurred = false;
|
|
this._setValueAttr(this._resetValue, true);
|
|
},
|
|
|
|
_onKeyDown: function(e){
|
|
if(e.keyCode == keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){
|
|
if(has("ie") < 9 || (has("ie") && has("quirks"))){
|
|
e.preventDefault(); // default behavior needs to be stopped here since keypress is too late
|
|
var node = e.srcElement,
|
|
te = node.ownerDocument.createEventObject();
|
|
te.keyCode = keys.ESCAPE;
|
|
te.shiftKey = e.shiftKey;
|
|
node.fireEvent('onkeypress', te);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/form/_FormWidgetMixin':function(){
|
|
define("dijit/form/_FormWidgetMixin", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/dom-style", // domStyle.get
|
|
"dojo/_base/lang", // lang.hitch lang.isArray
|
|
"dojo/mouse", // mouse.isLeft
|
|
"dojo/sniff", // has("webkit")
|
|
"dojo/window", // winUtils.scrollIntoView
|
|
"../a11y" // a11y.hasDefaultTabStop
|
|
], function(array, declare, domAttr, domStyle, lang, mouse, has, winUtils, a11y){
|
|
|
|
// module:
|
|
// dijit/form/_FormWidgetMixin
|
|
|
|
return declare("dijit.form._FormWidgetMixin", null, {
|
|
// summary:
|
|
// Mixin for widgets corresponding to native HTML elements such as `<checkbox>` or `<button>`,
|
|
// which can be children of a `<form>` node or a `dijit/form/Form` widget.
|
|
//
|
|
// description:
|
|
// Represents a single HTML element.
|
|
// All these widgets should have these attributes just like native HTML input elements.
|
|
// You can set them during widget construction or afterwards, via `dijit/_WidgetBase.set()`.
|
|
//
|
|
// They also share some common methods.
|
|
|
|
// name: [const] String
|
|
// Name used when submitting form; same as "name" attribute or plain HTML elements
|
|
name: "",
|
|
|
|
// alt: String
|
|
// Corresponds to the native HTML `<input>` element's attribute.
|
|
alt: "",
|
|
|
|
// value: String
|
|
// Corresponds to the native HTML `<input>` element's attribute.
|
|
value: "",
|
|
|
|
// type: [const] String
|
|
// Corresponds to the native HTML `<input>` element's attribute.
|
|
type: "text",
|
|
|
|
// type: String
|
|
// Apply aria-label in markup to the widget's focusNode
|
|
"aria-label": "focusNode",
|
|
|
|
// tabIndex: String
|
|
// Order fields are traversed when user hits the tab key
|
|
tabIndex: "0",
|
|
_setTabIndexAttr: "focusNode", // force copy even when tabIndex default value, needed since Button is <span>
|
|
|
|
// disabled: Boolean
|
|
// Should this widget respond to user input?
|
|
// In markup, this is specified as "disabled='disabled'", or just "disabled".
|
|
disabled: false,
|
|
|
|
// intermediateChanges: Boolean
|
|
// Fires onChange for each value change or only on demand
|
|
intermediateChanges: false,
|
|
|
|
// scrollOnFocus: Boolean
|
|
// On focus, should this widget scroll into view?
|
|
scrollOnFocus: true,
|
|
|
|
// Override _WidgetBase mapping id to this.domNode, needs to be on focusNode so <label> etc.
|
|
// works with screen reader
|
|
_setIdAttr: "focusNode",
|
|
|
|
_setDisabledAttr: function(/*Boolean*/ value){
|
|
this._set("disabled", value);
|
|
domAttr.set(this.focusNode, 'disabled', value);
|
|
if(this.valueNode){
|
|
domAttr.set(this.valueNode, 'disabled', value);
|
|
}
|
|
this.focusNode.setAttribute("aria-disabled", value ? "true" : "false");
|
|
|
|
if(value){
|
|
// reset these, because after the domNode is disabled, we can no longer receive
|
|
// mouse related events, see #4200
|
|
this._set("hovering", false);
|
|
this._set("active", false);
|
|
|
|
// clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes)
|
|
var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex :
|
|
("_setTabIndexAttr" in this) ? this._setTabIndexAttr : "focusNode";
|
|
array.forEach(lang.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
|
|
var node = this[attachPointName];
|
|
// complex code because tabIndex=-1 on a <div> doesn't work on FF
|
|
if(has("webkit") || a11y.hasDefaultTabStop(node)){ // see #11064 about webkit bug
|
|
node.setAttribute('tabIndex', "-1");
|
|
}else{
|
|
node.removeAttribute('tabIndex');
|
|
}
|
|
}, this);
|
|
}else{
|
|
if(this.tabIndex != ""){
|
|
this.set('tabIndex', this.tabIndex);
|
|
}
|
|
}
|
|
},
|
|
|
|
_onFocus: function(/*String*/ by){
|
|
// If user clicks on the widget, even if the mouse is released outside of it,
|
|
// this widget's focusNode should get focus (to mimic native browser hehavior).
|
|
// Browsers often need help to make sure the focus via mouse actually gets to the focusNode.
|
|
if(by == "mouse" && this.isFocusable()){
|
|
// IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused.
|
|
var focusConnector = this.connect(this.focusNode, "onfocus", function(){
|
|
this.disconnect(mouseUpConnector);
|
|
this.disconnect(focusConnector);
|
|
});
|
|
// Set a global event to handle mouseup, so it fires properly
|
|
// even if the cursor leaves this.domNode before the mouse up event.
|
|
var mouseUpConnector = this.connect(this.ownerDocumentBody, "onmouseup", function(){
|
|
this.disconnect(mouseUpConnector);
|
|
this.disconnect(focusConnector);
|
|
// if here, then the mousedown did not focus the focusNode as the default action
|
|
if(this.focused){
|
|
this.focus();
|
|
}
|
|
});
|
|
}
|
|
if(this.scrollOnFocus){
|
|
this.defer(function(){ winUtils.scrollIntoView(this.domNode); }); // without defer, the input caret position can change on mouse click
|
|
}
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
isFocusable: function(){
|
|
// summary:
|
|
// Tells if this widget is focusable or not. Used internally by dijit.
|
|
// tags:
|
|
// protected
|
|
return !this.disabled && this.focusNode && (domStyle.get(this.domNode, "display") != "none");
|
|
},
|
|
|
|
focus: function(){
|
|
// summary:
|
|
// Put focus on this widget
|
|
if(!this.disabled && this.focusNode.focus){
|
|
try{ this.focusNode.focus(); }catch(e){}/*squelch errors from hidden nodes*/
|
|
}
|
|
},
|
|
|
|
compare: function(/*anything*/ val1, /*anything*/ val2){
|
|
// summary:
|
|
// Compare 2 values (as returned by get('value') for this widget).
|
|
// tags:
|
|
// protected
|
|
if(typeof val1 == "number" && typeof val2 == "number"){
|
|
return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
|
|
}else if(val1 > val2){
|
|
return 1;
|
|
}else if(val1 < val2){
|
|
return -1;
|
|
}else{
|
|
return 0;
|
|
}
|
|
},
|
|
|
|
onChange: function(/*===== newValue =====*/){
|
|
// summary:
|
|
// Callback when this widget's value is changed.
|
|
// tags:
|
|
// callback
|
|
},
|
|
|
|
// _onChangeActive: [private] Boolean
|
|
// Indicates that changes to the value should call onChange() callback.
|
|
// This is false during widget initialization, to avoid calling onChange()
|
|
// when the initial value is set.
|
|
_onChangeActive: false,
|
|
|
|
_handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
|
|
// summary:
|
|
// Called when the value of the widget is set. Calls onChange() if appropriate
|
|
// newValue:
|
|
// the new value
|
|
// priorityChange:
|
|
// For a slider, for example, dragging the slider is priorityChange==false,
|
|
// but on mouse up, it's priorityChange==true. If intermediateChanges==false,
|
|
// onChange is only called form priorityChange=true events.
|
|
// tags:
|
|
// private
|
|
if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
|
|
// this block executes not for a change, but during initialization,
|
|
// and is used to store away the original value (or for ToggleButton, the original checked state)
|
|
this._resetValue = this._lastValueReported = newValue;
|
|
}
|
|
this._pendingOnChange = this._pendingOnChange
|
|
|| (typeof newValue != typeof this._lastValueReported)
|
|
|| (this.compare(newValue, this._lastValueReported) != 0);
|
|
if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
|
|
this._lastValueReported = newValue;
|
|
this._pendingOnChange = false;
|
|
if(this._onChangeActive){
|
|
if(this._onChangeHandle){
|
|
this._onChangeHandle.remove();
|
|
}
|
|
// defer allows hidden value processing to run and
|
|
// also the onChange handler can safely adjust focus, etc
|
|
this._onChangeHandle = this.defer(
|
|
function(){
|
|
this._onChangeHandle = null;
|
|
this.onChange(newValue);
|
|
}); // try to collapse multiple onChange's fired faster than can be processed
|
|
}
|
|
}
|
|
},
|
|
|
|
create: function(){
|
|
// Overrides _Widget.create()
|
|
this.inherited(arguments);
|
|
this._onChangeActive = true;
|
|
},
|
|
|
|
destroy: function(){
|
|
if(this._onChangeHandle){ // destroy called before last onChange has fired
|
|
this._onChangeHandle.remove();
|
|
this.onChange(this._lastValueReported);
|
|
}
|
|
this.inherited(arguments);
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/a11yclick':function(){
|
|
define("dijit/a11yclick", [
|
|
"dojo/on",
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/keys", // keys.ENTER keys.SPACE
|
|
"dojo/_base/declare", // declare
|
|
"dojo/has", // has("dom-addeventlistener")
|
|
"dojo/_base/unload", // unload.addOnWindowUnload
|
|
"dojo/_base/window" // win.doc.addEventListener win.doc.attachEvent win.doc.detachEvent
|
|
], function(on, array, keys, declare, has, unload, win){
|
|
|
|
// module:
|
|
// dijit/a11yclick
|
|
|
|
// Keep track of where the last keydown event was, to help avoid generating
|
|
// spurious ondijitclick events when:
|
|
// 1. focus is on a <button> or <a>
|
|
// 2. user presses then releases the ENTER key
|
|
// 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
|
|
// 4. onkeyup event fires, causing the ondijitclick handler to fire
|
|
var lastKeyDownNode = null;
|
|
if(has("dom-addeventlistener")){
|
|
win.doc.addEventListener('keydown', function(evt){
|
|
lastKeyDownNode = evt.target;
|
|
}, true);
|
|
}else{
|
|
// Fallback path for IE6-8
|
|
(function(){
|
|
var keydownCallback = function(evt){
|
|
lastKeyDownNode = evt.srcElement;
|
|
};
|
|
win.doc.attachEvent('onkeydown', keydownCallback);
|
|
unload.addOnWindowUnload(function(){
|
|
win.doc.detachEvent('onkeydown', keydownCallback);
|
|
});
|
|
})();
|
|
}
|
|
|
|
function clickKey(/*Event*/ e){
|
|
return (e.keyCode === keys.ENTER || e.keyCode === keys.SPACE) &&
|
|
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
|
|
}
|
|
|
|
return function(node, listener){
|
|
// summary:
|
|
// Custom a11yclick (a.k.a. ondijitclick) event
|
|
// which triggers on a mouse click, touch, or space/enter keyup.
|
|
|
|
if(/input|button/i.test(node.nodeName)){
|
|
// pass through, the browser already generates click event on SPACE/ENTER key
|
|
return on(node, "click", listener);
|
|
}else{
|
|
// Don't fire the click event unless both the keydown and keyup occur on this node.
|
|
// Avoids problems where focus shifted to this node or away from the node on keydown,
|
|
// either causing this node to process a stray keyup event, or causing another node
|
|
// to get a stray keyup event.
|
|
|
|
var handles = [
|
|
on(node, "keydown", function(e){
|
|
//console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
|
|
if(clickKey(e)){
|
|
// needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
|
|
lastKeyDownNode = e.target;
|
|
|
|
// Prevent viewport scrolling on space key in IE<9.
|
|
// (Reproducible on test_Button.html on any of the first dijit/form/Button examples)
|
|
e.preventDefault();
|
|
}
|
|
}),
|
|
|
|
on(node, "keyup", function(e){
|
|
//console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
|
|
if(clickKey(e) && e.target == lastKeyDownNode){ // === breaks greasemonkey
|
|
//need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
|
|
lastKeyDownNode = null;
|
|
on.emit(e.target, "click", {
|
|
cancelable: true,
|
|
bubbles: true
|
|
});
|
|
}
|
|
}),
|
|
|
|
on(node, "click", function(e){
|
|
// catch mouse clicks, plus the on.emit() calls from above and below
|
|
listener.call(this, e);
|
|
})
|
|
];
|
|
|
|
if(has("touch")){
|
|
// touchstart-->touchend will automatically generate a click event, but there are problems
|
|
// on iOS after focus has been programatically shifted (#14604, #14918), so setup a failsafe
|
|
// if click doesn't fire naturally.
|
|
|
|
var clickTimer;
|
|
handles.push(
|
|
on(node, "touchend", function(e){
|
|
var target = e.target;
|
|
clickTimer = setTimeout(function(){
|
|
clickTimer = null;
|
|
on.emit(target, "click", {
|
|
cancelable: true,
|
|
bubbles: true
|
|
});
|
|
}, 600);
|
|
}),
|
|
on(node, "click", function(e){
|
|
// If browser generates a click naturally, clear the timer to fire a synthetic click event
|
|
if(clickTimer){
|
|
clearTimeout(clickTimer);
|
|
}
|
|
})
|
|
// TODO: if the touchstart and touchend were <100ms apart, and then there's another touchstart
|
|
// event <300ms after the touchend event, then clear the synthetic click timer, because user
|
|
// is doing a zoom. Alternately monitor screen.deviceXDPI (or something similar) to see if
|
|
// zoom level has changed.
|
|
);
|
|
}
|
|
|
|
return {
|
|
remove: function(){
|
|
array.forEach(handles, function(h){ h.remove(); });
|
|
if(clickTimer){
|
|
clearTimeout(clickTimer);
|
|
clickTimer = null;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
return ret;
|
|
});
|
|
|
|
},
|
|
'url:dijit/templates/ProgressBar.html':"<div class=\"dijitProgressBar dijitProgressBarEmpty\" role=\"progressbar\"\n\t><div data-dojo-attach-point=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\" role=\"presentation\"></div\n\t\t><span style=\"visibility:hidden\"> </span\n\t></div\n\t><div data-dojo-attach-point=\"labelNode\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"></div\n\t><img data-dojo-attach-point=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n",
|
|
'dijit/Destroyable':function(){
|
|
define("dijit/Destroyable", [
|
|
"dojo/_base/array", // array.forEach array.map
|
|
"dojo/aspect",
|
|
"dojo/_base/declare"
|
|
], function(array, aspect, declare){
|
|
|
|
// module:
|
|
// dijit/Destroyable
|
|
|
|
return declare("dijit.Destroyable", null, {
|
|
// summary:
|
|
// Mixin to track handles and release them when instance is destroyed.
|
|
// description:
|
|
// Call this.own(...) on list of handles (returned from dojo/aspect, dojo/on,
|
|
// dojo/Stateful::watch, or any class (including widgets) with a destroyRecursive() or destroy() method.
|
|
// Then call destroy() later to destroy this instance and release the resources.
|
|
|
|
destroy: function(/*Boolean*/ preserveDom){
|
|
// summary:
|
|
// Destroy this class, releasing any resources registered via own().
|
|
this._destroyed = true;
|
|
},
|
|
|
|
own: function(){
|
|
// summary:
|
|
// Track specified handles and remove/destroy them when this instance is destroyed, unless they were
|
|
// already removed/destroyed manually.
|
|
// tags:
|
|
// protected
|
|
// returns:
|
|
// The array of specified handles, so you can do for example:
|
|
// | var handle = this.own(on(...))[0];
|
|
|
|
array.forEach(arguments, function(handle){
|
|
var destroyMethodName =
|
|
"destroyRecursive" in handle ? "destroyRecursive" : // remove "destroyRecursive" for 2.0
|
|
"destroy" in handle ? "destroy" :
|
|
"remove";
|
|
|
|
// When this.destroy() is called, destroy handle. Since I'm using aspect.before(),
|
|
// the handle will be destroyed before a subclass's destroy() method starts running, before it calls
|
|
// this.inherited() or even if it doesn't call this.inherited() at all. If that's an issue, make an
|
|
// onDestroy() method and connect to that instead.
|
|
var odh = aspect.before(this, "destroy", function(preserveDom){
|
|
handle[destroyMethodName](preserveDom);
|
|
});
|
|
|
|
// If handle is destroyed manually before this.destroy() is called, remove the listener set directly above.
|
|
var hdh = aspect.after(handle, destroyMethodName, function(){
|
|
odh.remove();
|
|
hdh.remove();
|
|
}, true);
|
|
}, this);
|
|
|
|
return arguments; // handle
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/layout/_ContentPaneResizeMixin':function(){
|
|
define("dijit/layout/_ContentPaneResizeMixin", [
|
|
"dojo/_base/array", // array.filter array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.contains domClass.toggle
|
|
"dojo/dom-geometry",// domGeometry.contentBox domGeometry.marginBox
|
|
"dojo/dom-style",
|
|
"dojo/_base/lang", // lang.mixin
|
|
"dojo/query", // query
|
|
"dojo/sniff", // has("ie")
|
|
"../registry", // registry.byId
|
|
"../Viewport",
|
|
"./utils" // marginBox2contextBox
|
|
], function(array, declare, domClass, domGeometry, domStyle, lang, query, has,
|
|
registry, Viewport, layoutUtils){
|
|
|
|
// module:
|
|
// dijit/layout/_ContentPaneResizeMixin
|
|
|
|
|
|
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){
|
|
// Since my parent isn't a layout container, and my style *may be* width=height=100%
|
|
// or something similar (either set directly or via a CSS class),
|
|
// monitor when viewport size changes so that I can re-layout.
|
|
// This is more for subclasses of ContentPane than ContentPane itself, although it
|
|
// could be useful for a ContentPane if it has a single child widget inheriting ContentPane's size.
|
|
this.own(Viewport.on("resize", lang.hitch(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 candidateWidgets = [],
|
|
otherVisibleNodes = false;
|
|
|
|
query("> *", this.containerNode).some(function(node){
|
|
var widget = registry.byNode(node);
|
|
if(widget && widget.resize){
|
|
candidateWidgets.push(widget);
|
|
}else if(node.offsetHeight){
|
|
otherVisibleNodes = true;
|
|
}
|
|
});
|
|
|
|
this._singleChild = candidateWidgets.length == 1 && !otherVisibleNodes ?
|
|
candidateWidgets[0] : null;
|
|
|
|
// 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.
|
|
|
|
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 an isLayoutContainer widget, each of my children expects me to
|
|
// call resize() or layout() on it.
|
|
//
|
|
// 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
|
|
|
|
delete this._needLayout;
|
|
|
|
// For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is
|
|
// never called directly, 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();
|
|
}
|
|
|
|
// 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();
|
|
},
|
|
|
|
_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)
|
|
|
|
// Need to keep track of whether ContentPane has been shown (which is different than
|
|
// whether or not it's currently visible).
|
|
this._wasShown = true;
|
|
|
|
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);
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/WidgetSet':function(){
|
|
define("dijit/WidgetSet", [
|
|
"dojo/_base/array", // array.forEach array.map
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/kernel", // kernel.global
|
|
"./registry" // to add functions to dijit.registry
|
|
], function(array, declare, kernel, registry){
|
|
|
|
// module:
|
|
// dijit/WidgetSet
|
|
|
|
var WidgetSet = declare("dijit.WidgetSet", null, {
|
|
// summary:
|
|
// A set of widgets indexed by id.
|
|
// Deprecated, will be removed in 2.0.
|
|
//
|
|
// example:
|
|
// Create a small list of widgets:
|
|
// | require(["dijit/WidgetSet", "dijit/registry"],
|
|
// | function(WidgetSet, registry){
|
|
// | var ws = new WidgetSet();
|
|
// | ws.add(registry.byId("one"));
|
|
// | ws.add(registry.byId("two"));
|
|
// | // destroy both:
|
|
// | ws.forEach(function(w){ w.destroy(); });
|
|
// | });
|
|
|
|
constructor: function(){
|
|
this._hash = {};
|
|
this.length = 0;
|
|
},
|
|
|
|
add: function(/*dijit/_WidgetBase*/ widget){
|
|
// summary:
|
|
// Add a widget to this list. If a duplicate ID is detected, a error is thrown.
|
|
//
|
|
// widget: dijit/_WidgetBase
|
|
// Any dijit/_WidgetBase subclass.
|
|
if(this._hash[widget.id]){
|
|
throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered");
|
|
}
|
|
this._hash[widget.id] = widget;
|
|
this.length++;
|
|
},
|
|
|
|
remove: function(/*String*/ id){
|
|
// summary:
|
|
// Remove a widget from this WidgetSet. Does not destroy the widget; simply
|
|
// removes the reference.
|
|
if(this._hash[id]){
|
|
delete this._hash[id];
|
|
this.length--;
|
|
}
|
|
},
|
|
|
|
forEach: function(/*Function*/ func, /* Object? */thisObj){
|
|
// summary:
|
|
// Call specified function for each widget in this set.
|
|
//
|
|
// func:
|
|
// A callback function to run for each item. Is passed the widget, the index
|
|
// in the iteration, and the full hash, similar to `array.forEach`.
|
|
//
|
|
// thisObj:
|
|
// An optional scope parameter
|
|
//
|
|
// example:
|
|
// Using the default `dijit.registry` instance:
|
|
// | require(["dijit/WidgetSet", "dijit/registry"],
|
|
// | function(WidgetSet, registry){
|
|
// | registry.forEach(function(widget){
|
|
// | console.log(widget.declaredClass);
|
|
// | });
|
|
// | });
|
|
//
|
|
// returns:
|
|
// Returns self, in order to allow for further chaining.
|
|
|
|
thisObj = thisObj || kernel.global;
|
|
var i = 0, id;
|
|
for(id in this._hash){
|
|
func.call(thisObj, this._hash[id], i++, this._hash);
|
|
}
|
|
return this; // dijit/WidgetSet
|
|
},
|
|
|
|
filter: function(/*Function*/ filter, /* Object? */thisObj){
|
|
// summary:
|
|
// Filter down this WidgetSet to a smaller new WidgetSet
|
|
// Works the same as `array.filter` and `NodeList.filter`
|
|
//
|
|
// filter:
|
|
// Callback function to test truthiness. Is passed the widget
|
|
// reference and the pseudo-index in the object.
|
|
//
|
|
// thisObj: Object?
|
|
// Option scope to use for the filter function.
|
|
//
|
|
// example:
|
|
// Arbitrary: select the odd widgets in this list
|
|
// |
|
|
// |
|
|
// |
|
|
// | require(["dijit/WidgetSet", "dijit/registry"],
|
|
// | function(WidgetSet, registry){
|
|
// | registry.filter(function(w, i){
|
|
// | return i % 2 == 0;
|
|
// | }).forEach(function(w){ /* odd ones */ });
|
|
// | });
|
|
|
|
thisObj = thisObj || kernel.global;
|
|
var res = new WidgetSet(), i = 0, id;
|
|
for(id in this._hash){
|
|
var w = this._hash[id];
|
|
if(filter.call(thisObj, w, i++, this._hash)){
|
|
res.add(w);
|
|
}
|
|
}
|
|
return res; // dijit/WidgetSet
|
|
},
|
|
|
|
byId: function(/*String*/ id){
|
|
// summary:
|
|
// Find a widget in this list by it's id.
|
|
// example:
|
|
// Test if an id is in a particular WidgetSet
|
|
// | require(["dijit/WidgetSet", "dijit/registry"],
|
|
// | function(WidgetSet, registry){
|
|
// | var ws = new WidgetSet();
|
|
// | ws.add(registry.byId("bar"));
|
|
// | var t = ws.byId("bar") // returns a widget
|
|
// | var x = ws.byId("foo"); // returns undefined
|
|
// | });
|
|
|
|
return this._hash[id]; // dijit/_WidgetBase
|
|
},
|
|
|
|
byClass: function(/*String*/ cls){
|
|
// summary:
|
|
// Reduce this widgetset to a new WidgetSet of a particular `declaredClass`
|
|
//
|
|
// cls: String
|
|
// The Class to scan for. Full dot-notated string.
|
|
//
|
|
// example:
|
|
// Find all `dijit.TitlePane`s in a page:
|
|
// | require(["dijit/WidgetSet", "dijit/registry"],
|
|
// | function(WidgetSet, registry){
|
|
// | registry.byClass("dijit.TitlePane").forEach(function(tp){ tp.close(); });
|
|
// | });
|
|
|
|
var res = new WidgetSet(), id, widget;
|
|
for(id in this._hash){
|
|
widget = this._hash[id];
|
|
if(widget.declaredClass == cls){
|
|
res.add(widget);
|
|
}
|
|
}
|
|
return res; // dijit/WidgetSet
|
|
},
|
|
|
|
toArray: function(){
|
|
// summary:
|
|
// Convert this WidgetSet into a true Array
|
|
//
|
|
// example:
|
|
// Work with the widget .domNodes in a real Array
|
|
// | require(["dijit/WidgetSet", "dijit/registry"],
|
|
// | function(WidgetSet, registry){
|
|
// | array.map(registry.toArray(), function(w){ return w.domNode; });
|
|
// | });
|
|
|
|
|
|
var ar = [];
|
|
for(var id in this._hash){
|
|
ar.push(this._hash[id]);
|
|
}
|
|
return ar; // dijit/_WidgetBase[]
|
|
},
|
|
|
|
map: function(/* Function */func, /* Object? */thisObj){
|
|
// summary:
|
|
// Create a new Array from this WidgetSet, following the same rules as `array.map`
|
|
// example:
|
|
// | require(["dijit/WidgetSet", "dijit/registry"],
|
|
// | function(WidgetSet, registry){
|
|
// | var nodes = registry.map(function(w){ return w.domNode; });
|
|
// | });
|
|
//
|
|
// returns:
|
|
// A new array of the returned values.
|
|
return array.map(this.toArray(), func, thisObj); // Array
|
|
},
|
|
|
|
every: function(func, thisObj){
|
|
// summary:
|
|
// A synthetic clone of `array.every` acting explicitly on this WidgetSet
|
|
//
|
|
// func: Function
|
|
// A callback function run for every widget in this list. Exits loop
|
|
// when the first false return is encountered.
|
|
//
|
|
// thisObj: Object?
|
|
// Optional scope parameter to use for the callback
|
|
|
|
thisObj = thisObj || kernel.global;
|
|
var x = 0, i;
|
|
for(i in this._hash){
|
|
if(!func.call(thisObj, this._hash[i], x++, this._hash)){
|
|
return false; // Boolean
|
|
}
|
|
}
|
|
return true; // Boolean
|
|
},
|
|
|
|
some: function(func, thisObj){
|
|
// summary:
|
|
// A synthetic clone of `array.some` acting explicitly on this WidgetSet
|
|
//
|
|
// func: Function
|
|
// A callback function run for every widget in this list. Exits loop
|
|
// when the first true return is encountered.
|
|
//
|
|
// thisObj: Object?
|
|
// Optional scope parameter to use for the callback
|
|
|
|
thisObj = thisObj || kernel.global;
|
|
var x = 0, i;
|
|
for(i in this._hash){
|
|
if(func.call(thisObj, this._hash[i], x++, this._hash)){
|
|
return true; // Boolean
|
|
}
|
|
}
|
|
return false; // Boolean
|
|
}
|
|
|
|
});
|
|
|
|
// Add in 1.x compatibility methods to dijit/registry.
|
|
// These functions won't show up in the API doc but since they are deprecated anyway,
|
|
// that's probably for the best.
|
|
array.forEach(["forEach", "filter", "byClass", "map", "every", "some"], function(func){
|
|
registry[func] = WidgetSet.prototype[func];
|
|
});
|
|
|
|
|
|
return WidgetSet;
|
|
});
|
|
|
|
},
|
|
'dojo/dnd/Moveable':function(){
|
|
define("dojo/dnd/Moveable", [
|
|
"../_base/array", "../_base/declare", "../_base/event", "../_base/lang",
|
|
"../dom", "../dom-class", "../Evented", "../on", "../topic", "../touch", "./common", "./Mover", "../_base/window"
|
|
], function(array, declare, event, lang, dom, domClass, Evented, on, topic, touch, dnd, Mover, win){
|
|
|
|
// module:
|
|
// dojo/dnd/Moveable
|
|
|
|
|
|
var Moveable = declare("dojo.dnd.Moveable", [Evented], {
|
|
// summary:
|
|
// an object, which makes a node movable
|
|
|
|
// object attributes (for markup)
|
|
handle: "",
|
|
delay: 0,
|
|
skip: false,
|
|
|
|
constructor: function(node, params){
|
|
// node: Node
|
|
// a node (or node's id) to be moved
|
|
// params: Moveable.__MoveableArgs?
|
|
// optional parameters
|
|
this.node = dom.byId(node);
|
|
if(!params){ params = {}; }
|
|
this.handle = params.handle ? dom.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 : Mover;
|
|
this.events = [
|
|
on(this.handle, touch.press, lang.hitch(this, "onMouseDown")),
|
|
// cancel text selection and text dragging
|
|
on(this.handle, "dragstart", lang.hitch(this, "onSelectStart")),
|
|
on(this.handle, "selectstart", lang.hitch(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
|
|
array.forEach(this.events, function(handle){ handle.remove(); });
|
|
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 && dnd.isFormElement(e)){ return; }
|
|
if(this.delay){
|
|
this.events.push(
|
|
on(this.handle, touch.move, lang.hitch(this, "onMouseMove")),
|
|
on(this.handle, touch.release, lang.hitch(this, "onMouseUp"))
|
|
);
|
|
this._lastX = e.pageX;
|
|
this._lastY = e.pageY;
|
|
}else{
|
|
this.onDragDetected(e);
|
|
}
|
|
event.stop(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);
|
|
}
|
|
event.stop(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){
|
|
this.events.pop().remove();
|
|
}
|
|
event.stop(e);
|
|
},
|
|
onSelectStart: function(e){
|
|
// summary:
|
|
// event processor for onselectevent and ondragevent
|
|
// e: Event
|
|
// mouse event
|
|
if(!this.skip || !dnd.isFormElement(e)){
|
|
event.stop(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(/*Mover*/ mover){
|
|
// summary:
|
|
// called before every move operation
|
|
topic.publish("/dnd/move/start", mover);
|
|
domClass.add(win.body(), "dojoMove");
|
|
domClass.add(this.node, "dojoMoveItem");
|
|
},
|
|
onMoveStop: function(/*Mover*/ mover){
|
|
// summary:
|
|
// called after every move operation
|
|
topic.publish("/dnd/move/stop", mover);
|
|
domClass.remove(win.body(), "dojoMove");
|
|
domClass.remove(this.node, "dojoMoveItem");
|
|
},
|
|
onFirstMove: function(/*===== mover, e =====*/){
|
|
// summary:
|
|
// called during the very first move notification;
|
|
// can be used to initialize coordinates, can be overwritten.
|
|
// mover: Mover
|
|
// e: Event
|
|
|
|
// default implementation does nothing
|
|
},
|
|
onMove: function(mover, leftTop /*=====, e =====*/){
|
|
// summary:
|
|
// called during every move notification;
|
|
// should actually move the node; can be overwritten.
|
|
// mover: Mover
|
|
// leftTop: Object
|
|
// e: Event
|
|
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(/*===== mover, leftTop =====*/){
|
|
// summary:
|
|
// called before every incremental move; can be overwritten.
|
|
// mover: Mover
|
|
// leftTop: Object
|
|
|
|
// default implementation does nothing
|
|
},
|
|
onMoved: function(/*===== mover, leftTop =====*/){
|
|
// summary:
|
|
// called after every incremental move; can be overwritten.
|
|
// mover: Mover
|
|
// leftTop: Object
|
|
|
|
// default implementation does nothing
|
|
}
|
|
});
|
|
|
|
/*=====
|
|
Moveable.__MoveableArgs = declare([], {
|
|
// 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: dnd.Mover
|
|
});
|
|
=====*/
|
|
|
|
return Moveable;
|
|
});
|
|
|
|
},
|
|
'dijit/TooltipDialog':function(){
|
|
require({cache:{
|
|
'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\" data-dojo-attach-point=\"connectorNode\"></div>\n</div>\n"}});
|
|
define("dijit/TooltipDialog", [
|
|
"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",
|
|
"./main" // exports methods to dijit global
|
|
], function(declare, domClass, event, keys, lang,
|
|
focus, ContentPane, _DialogMixin, _FormMixin, _TemplatedMixin, template, dijit){
|
|
|
|
// module:
|
|
// dijit/TooltipDialog
|
|
|
|
|
|
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*/ tooltipCorner){
|
|
// 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
|
|
|
|
// Note: intentionally not using dijitTooltip class since that sets position:absolute, which
|
|
// confuses dijit/popup trying to get the size of the tooltip.
|
|
var newC = {
|
|
"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];
|
|
|
|
domClass.replace(this.domNode, newC, this._currentOrientClass || "");
|
|
this._currentOrientClass = newC;
|
|
|
|
// Tooltip.orient() has code to reposition connector for when Tooltip is before/after anchor.
|
|
// Not putting here to avoid code bloat, and since TooltipDialogs are generally above/below.
|
|
// Should combine code from Tooltip and TooltipDialog.
|
|
},
|
|
|
|
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);
|
|
|
|
// 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";
|
|
}
|
|
|
|
this._onShow(); // lazy load trigger (TODO: shouldn't we load before positioning?)
|
|
},
|
|
|
|
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 defer to avoid crash on IE, see #10396.
|
|
this.defer("onCancel");
|
|
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/store/util/SimpleQueryEngine':function(){
|
|
define("dojo/store/util/SimpleQueryEngine", ["../../_base/array" /*=====, "../api/Store" =====*/], function(arrayUtil /*=====, Store =====*/){
|
|
|
|
// module:
|
|
// dojo/store/util/SimpleQueryEngine
|
|
|
|
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/api/Store.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 = SimpleQueryEngine;
|
|
// | // define our query method
|
|
// | this.query = function(query, options){
|
|
// | return 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){
|
|
// an object can provide a test method, which makes it work with regex
|
|
if(!required.test(object[key], object)){
|
|
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
|
|
var sortSet = options && options.sort;
|
|
if(sortSet){
|
|
results.sort(typeof sortSet == "function" ? sortSet : function(a, b){
|
|
for(var sort, i=0; sort = sortSet[i]; i++){
|
|
var aValue = a[sort.attribute];
|
|
var bValue = b[sort.attribute];
|
|
if (aValue != bValue){
|
|
return !!sort.descending == (aValue == null || aValue > bValue) ? -1 : 1;
|
|
}
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
// now we paginate
|
|
if(options && (options.start || options.count)){
|
|
var total = results.length;
|
|
results = results.slice(options.start || 0, (options.start || 0) + (options.count || Infinity));
|
|
results.total = total;
|
|
}
|
|
return results;
|
|
}
|
|
execute.matches = query;
|
|
return execute;
|
|
};
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/typematic':function(){
|
|
define("dijit/typematic", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/connect", // connect.connect
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/_base/lang", // lang.mixin, lang.hitch
|
|
"dojo/on",
|
|
"dojo/sniff", // has("ie")
|
|
"./main" // setting dijit.typematic global
|
|
], function(array, connect, event, kernel, lang, on, has, dijit){
|
|
|
|
// module:
|
|
// dijit/typematic
|
|
|
|
var typematic = (dijit.typematic = {
|
|
// summary:
|
|
// These functions are used to repetitively call a user specified callback
|
|
// method when a specific key or mouse click over a specific DOM node is
|
|
// held down for a specific amount of time.
|
|
// Only 1 such event is allowed to occur on the browser page at 1 time.
|
|
|
|
_fireEventAndReload: function(){
|
|
this._timer = null;
|
|
this._callback(++this._count, this._node, this._evt);
|
|
|
|
// Schedule next event, timer is at most minDelay (default 10ms) to avoid
|
|
// browser overload (particularly avoiding starving DOH robot so it never gets to send a mouseup)
|
|
this._currentTimeout = Math.max(
|
|
this._currentTimeout < 0 ? this._initialDelay :
|
|
(this._subsequentDelay > 1 ? this._subsequentDelay : Math.round(this._currentTimeout * this._subsequentDelay)),
|
|
this._minDelay);
|
|
this._timer = setTimeout(lang.hitch(this, "_fireEventAndReload"), this._currentTimeout);
|
|
},
|
|
|
|
trigger: function(/*Event*/ evt, /*Object*/ _this, /*DOMNode*/ node, /*Function*/ callback, /*Object*/ obj, /*Number?*/ subsequentDelay, /*Number?*/ initialDelay, /*Number?*/ minDelay){
|
|
// summary:
|
|
// Start a timed, repeating callback sequence.
|
|
// If already started, the function call is ignored.
|
|
// This method is not normally called by the user but can be
|
|
// when the normal listener code is insufficient.
|
|
// evt:
|
|
// key or mouse event object to pass to the user callback
|
|
// _this:
|
|
// pointer to the user's widget space.
|
|
// node:
|
|
// the DOM node object to pass the the callback function
|
|
// callback:
|
|
// function to call until the sequence is stopped called with 3 parameters:
|
|
// count:
|
|
// integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped
|
|
// node:
|
|
// the DOM node object passed in
|
|
// evt:
|
|
// key or mouse event object
|
|
// obj:
|
|
// user space object used to uniquely identify each typematic sequence
|
|
// subsequentDelay:
|
|
// if > 1, the number of milliseconds until the 3->n events occur
|
|
// or else the fractional time multiplier for the next event's delay, default=0.9
|
|
// initialDelay:
|
|
// the number of milliseconds until the 2nd event occurs, default=500ms
|
|
// minDelay:
|
|
// the maximum delay in milliseconds for event to fire, default=10ms
|
|
if(obj != this._obj){
|
|
this.stop();
|
|
this._initialDelay = initialDelay || 500;
|
|
this._subsequentDelay = subsequentDelay || 0.90;
|
|
this._minDelay = minDelay || 10;
|
|
this._obj = obj;
|
|
this._node = node;
|
|
this._currentTimeout = -1;
|
|
this._count = -1;
|
|
this._callback = lang.hitch(_this, callback);
|
|
this._evt = { faux: true };
|
|
for(var attr in evt){
|
|
if(attr != "layerX" && attr != "layerY"){ // prevent WebKit warnings
|
|
var v = evt[attr];
|
|
if(typeof v != "function" && typeof v != "undefined"){ this._evt[attr] = v }
|
|
}
|
|
}
|
|
this._fireEventAndReload();
|
|
}
|
|
},
|
|
|
|
stop: function(){
|
|
// summary:
|
|
// Stop an ongoing timed, repeating callback sequence.
|
|
if(this._timer){
|
|
clearTimeout(this._timer);
|
|
this._timer = null;
|
|
}
|
|
if(this._obj){
|
|
this._callback(-1, this._node, this._evt);
|
|
this._obj = null;
|
|
}
|
|
},
|
|
|
|
addKeyListener: function(/*DOMNode*/ node, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
|
|
// summary:
|
|
// Start listening for a specific typematic key.
|
|
// See also the trigger method for other parameters.
|
|
// keyObject:
|
|
// an object defining the key to listen for:
|
|
//
|
|
// - charOrCode: the printable character (string) or keyCode (number) to listen for.
|
|
// - keyCode: (deprecated - use charOrCode) the keyCode (number) to listen for (implies charCode = 0).
|
|
// - charCode: (deprecated - use charOrCode) the charCode (number) to listen for.
|
|
// - ctrlKey: desired ctrl key state to initiate the callback sequence:
|
|
// - pressed (true)
|
|
// - released (false)
|
|
// - either (unspecified)
|
|
// - altKey: same as ctrlKey but for the alt key
|
|
// - shiftKey: same as ctrlKey but for the shift key
|
|
// returns:
|
|
// a connection handle
|
|
|
|
if(keyObject.keyCode){
|
|
keyObject.charOrCode = keyObject.keyCode;
|
|
kernel.deprecated("keyCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0");
|
|
}else if(keyObject.charCode){
|
|
keyObject.charOrCode = String.fromCharCode(keyObject.charCode);
|
|
kernel.deprecated("charCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0");
|
|
}
|
|
var handles = [
|
|
on(node, connect._keypress, lang.hitch(this, function(evt){
|
|
if(evt.charOrCode == keyObject.charOrCode &&
|
|
(keyObject.ctrlKey === undefined || keyObject.ctrlKey == evt.ctrlKey) &&
|
|
(keyObject.altKey === undefined || keyObject.altKey == evt.altKey) &&
|
|
(keyObject.metaKey === undefined || keyObject.metaKey == (evt.metaKey || false)) && // IE doesn't even set metaKey
|
|
(keyObject.shiftKey === undefined || keyObject.shiftKey == evt.shiftKey)){
|
|
event.stop(evt);
|
|
typematic.trigger(evt, _this, node, callback, keyObject, subsequentDelay, initialDelay, minDelay);
|
|
}else if(typematic._obj == keyObject){
|
|
typematic.stop();
|
|
}
|
|
})),
|
|
on(node, "keyup", lang.hitch(this, function(){
|
|
if(typematic._obj == keyObject){
|
|
typematic.stop();
|
|
}
|
|
}))
|
|
];
|
|
return { remove: function(){ array.forEach(handles, function(h){ h.remove(); }); } };
|
|
},
|
|
|
|
addMouseListener: function(/*DOMNode*/ node, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
|
|
// summary:
|
|
// Start listening for a typematic mouse click.
|
|
// See the trigger method for other parameters.
|
|
// returns:
|
|
// a connection handle
|
|
var handles = [
|
|
on(node, "mousedown", lang.hitch(this, function(evt){
|
|
evt.preventDefault();
|
|
typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
|
|
})),
|
|
on(node, "mouseup", lang.hitch(this, function(evt){
|
|
if(this._obj){
|
|
evt.preventDefault();
|
|
}
|
|
typematic.stop();
|
|
})),
|
|
on(node, "mouseout", lang.hitch(this, function(evt){
|
|
if(this._obj){
|
|
evt.preventDefault();
|
|
}
|
|
typematic.stop();
|
|
})),
|
|
on(node, "dblclick", lang.hitch(this, function(evt){
|
|
evt.preventDefault();
|
|
if(has("ie") < 9){
|
|
typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
|
|
setTimeout(lang.hitch(this, typematic.stop), 50);
|
|
}
|
|
}))
|
|
];
|
|
return { remove: function(){ array.forEach(handles, function(h){ h.remove(); }); } };
|
|
},
|
|
|
|
addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
|
|
// summary:
|
|
// Start listening for a specific typematic key and mouseclick.
|
|
// This is a thin wrapper to addKeyListener and addMouseListener.
|
|
// See the addMouseListener and addKeyListener methods for other parameters.
|
|
// mouseNode:
|
|
// the DOM node object to listen on for mouse events.
|
|
// keyNode:
|
|
// the DOM node object to listen on for key events.
|
|
// returns:
|
|
// a connection handle
|
|
var handles = [
|
|
this.addKeyListener(keyNode, keyObject, _this, callback, subsequentDelay, initialDelay, minDelay),
|
|
this.addMouseListener(mouseNode, _this, callback, subsequentDelay, initialDelay, minDelay)
|
|
];
|
|
return { remove: function(){ array.forEach(handles, function(h){ h.remove(); }); } };
|
|
}
|
|
});
|
|
|
|
return typematic;
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/MenuItem':function(){
|
|
require({cache:{
|
|
'url:dijit/templates/MenuItem.html':"<tr class=\"dijitReset dijitMenuItem\" data-dojo-attach-point=\"focusNode\" role=\"menuitem\" tabIndex=\"-1\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" role=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitMenuItemIcon\" data-dojo-attach-point=\"iconNode\"/>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" data-dojo-attach-point=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" data-dojo-attach-point=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\">\n\t\t<div data-dojo-attach-point=\"arrowWrapper\" style=\"visibility: hidden\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuExpand\"/>\n\t\t\t<span class=\"dijitMenuExpandA11y\">+</span>\n\t\t</div>\n\t</td>\n</tr>\n"}});
|
|
define("dijit/MenuItem", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.setSelectable
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/dom-class", // domClass.toggle
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/sniff", // has("ie")
|
|
"./_Widget",
|
|
"./_TemplatedMixin",
|
|
"./_Contained",
|
|
"./_CssStateMixin",
|
|
"dojo/text!./templates/MenuItem.html"
|
|
], function(declare, dom, domAttr, domClass, kernel, has,
|
|
_Widget, _TemplatedMixin, _Contained, _CssStateMixin, template){
|
|
|
|
// module:
|
|
// dijit/MenuItem
|
|
|
|
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: function(val){
|
|
this.containerNode.innerHTML = val;
|
|
this._set("label", val);
|
|
if(this.textDir === "auto"){
|
|
this.applyTextDir(this.focusNode, this.label);
|
|
}
|
|
},
|
|
|
|
// 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);
|
|
},
|
|
|
|
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);
|
|
},
|
|
_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);
|
|
this.applyTextDir(this.focusNode, this.label);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/layout/TabController':function(){
|
|
require({cache:{
|
|
'url:dijit/layout/templates/_TabButton.html':"<div role=\"presentation\" data-dojo-attach-point=\"titleNode,innerDiv,tabContent\" class=\"dijitTabInner dijitTabContent\">\n\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitTabButtonIcon\" data-dojo-attach-point='iconNode'/>\n\t<span data-dojo-attach-point='containerNode,focusNode' class='tabLabel'></span>\n\t<span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" data-dojo-attach-point='closeNode'\n\t\t role=\"presentation\">\n\t\t<span data-dojo-attach-point='closeText' class='dijitTabCloseText'>[x]</span\n\t\t\t\t></span>\n</div>\n"}});
|
|
define("dijit/layout/TabController", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.setSelectable
|
|
"dojo/dom-attr", // domAttr.attr
|
|
"dojo/dom-class", // domClass.toggle
|
|
"dojo/i18n", // i18n.getLocalization
|
|
"dojo/_base/lang", // lang.hitch lang.trim
|
|
"./StackController",
|
|
"../registry",
|
|
"../Menu",
|
|
"../MenuItem",
|
|
"dojo/text!./templates/_TabButton.html",
|
|
"dojo/i18n!../nls/common"
|
|
], function(declare, dom, domAttr, domClass, i18n, lang, StackController, registry, Menu, MenuItem, template){
|
|
|
|
// module:
|
|
// dijit/layout/TabController
|
|
|
|
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.
|
|
this.defer(function(){
|
|
n.className = n.className;
|
|
}, 1);
|
|
},
|
|
|
|
_setCloseButtonAttr: function(/*Boolean*/ disp){
|
|
// summary:
|
|
// Hide/show close button
|
|
this._set("closeButton", disp);
|
|
domClass.toggle(this.domNode, "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);
|
|
}
|
|
}
|
|
},
|
|
|
|
_setDisabledAttr: function(/*Boolean*/ disabled){
|
|
// summary:
|
|
// Make tab selected/unselectable
|
|
|
|
this.inherited(arguments);
|
|
|
|
// Don't show tooltip for close button when tab is disabled
|
|
if(this.closeNode){
|
|
if(disabled){
|
|
domAttr.remove(this.closeNode, "title");
|
|
}else{
|
|
var _nlsResources = i18n.getLocalization("dijit", "common");
|
|
domAttr.set(this.closeNode, "title", _nlsResources.itemClose);
|
|
}
|
|
}
|
|
},
|
|
|
|
_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 || '');
|
|
}
|
|
}
|
|
});
|
|
|
|
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,
|
|
|
|
// buttonWidgetCloseClass: String
|
|
// Class of [x] close icon, used by event delegation code to tell when close button was clicked
|
|
buttonWidgetCloseClass: "dijitTabCloseButton",
|
|
|
|
postCreate: function(){
|
|
this.inherited(arguments);
|
|
|
|
// Setup a close menu to be shared between all the closable tabs (excluding disabled tabs)
|
|
var closeMenu = new Menu({
|
|
id: this.id+"_Menu",
|
|
ownerDocument: this.ownerDocument,
|
|
dir: this.dir,
|
|
lang: this.lang,
|
|
textDir: this.textDir,
|
|
targetNodeIds: [this.domNode],
|
|
selector: function(node){
|
|
return domClass.contains(node, "dijitClosable") && !domClass.contains(node, "dijitTabDisabled");
|
|
}
|
|
});
|
|
this.own(closeMenu);
|
|
|
|
var _nlsResources = i18n.getLocalization("dijit", "common"),
|
|
controller = this;
|
|
closeMenu.addChild(new MenuItem({
|
|
label: _nlsResources.itemClose,
|
|
ownerDocument: this.ownerDocument,
|
|
dir: this.dir,
|
|
lang: this.lang,
|
|
textDir: this.textDir,
|
|
onClick: function(evt){
|
|
var button = registry.byNode(this.getParent().currentTarget);
|
|
controller.onCloseButtonClick(button.page);
|
|
}
|
|
}));
|
|
}
|
|
});
|
|
|
|
TabController.TabButton = TabButton; // for monkey patching
|
|
|
|
return TabController;
|
|
});
|
|
|
|
},
|
|
'dijit/ToolbarSeparator':function(){
|
|
define("dijit/ToolbarSeparator", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.setSelectable
|
|
"./_Widget",
|
|
"./_TemplatedMixin"
|
|
], function(declare, dom, _Widget, _TemplatedMixin){
|
|
|
|
// module:
|
|
// dijit/ToolbarSeparator
|
|
|
|
|
|
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/_LayoutWidget':function(){
|
|
define("dijit/layout/_LayoutWidget", [
|
|
"dojo/_base/lang", // lang.mixin
|
|
"../_Widget",
|
|
"../_Container",
|
|
"../_Contained",
|
|
"../Viewport",
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.add domClass.remove
|
|
"dojo/dom-geometry", // domGeometry.marginBox
|
|
"dojo/dom-style" // domStyle.getComputedStyle
|
|
], function(lang, _Widget, _Container, _Contained, Viewport,
|
|
declare, domClass, domGeometry, domStyle){
|
|
|
|
// module:
|
|
// dijit/layout/_LayoutWidget
|
|
|
|
|
|
return declare("dijit.layout._LayoutWidget", [_Widget, _Container, _Contained], {
|
|
// summary:
|
|
// Base class for a _Container widget which is responsible for laying out its children.
|
|
// Widgets which mixin this code must define layout() to manage placement and sizing of the children.
|
|
|
|
// baseClass: [protected extension] String
|
|
// This class name is applied to the widget's domNode
|
|
// and also may be used to generate names for sub nodes,
|
|
// for example dijitTabContainer-content.
|
|
baseClass: "dijitLayoutContainer",
|
|
|
|
// isLayoutContainer: [protected] Boolean
|
|
// Indicates that this widget is going to call resize() on its
|
|
// children widgets, setting their size, when they become visible.
|
|
isLayoutContainer: true,
|
|
|
|
buildRendering: function(){
|
|
this.inherited(arguments);
|
|
domClass.add(this.domNode, "dijitContainer");
|
|
},
|
|
|
|
startup: function(){
|
|
// summary:
|
|
// Called after all the widgets have been instantiated and their
|
|
// dom nodes have been inserted somewhere under win.doc.body.
|
|
//
|
|
// Widgets should override this method to do any initialization
|
|
// dependent on other widgets existing, and then call
|
|
// this superclass method to finish things off.
|
|
//
|
|
// startup() in subclasses shouldn't do anything
|
|
// size related because the size of the widget hasn't been set yet.
|
|
|
|
if(this._started){ return; }
|
|
|
|
// Need to call inherited first - so that child widgets get started
|
|
// up correctly
|
|
this.inherited(arguments);
|
|
|
|
// If I am a not being controlled by a parent layout widget...
|
|
var parent = this.getParent && this.getParent();
|
|
if(!(parent && parent.isLayoutContainer)){
|
|
// Do recursive sizing and layout of all my descendants
|
|
// (passing in no argument to resize means that it has to glean the size itself)
|
|
this.resize();
|
|
|
|
// Since my parent isn't a layout container, and my style *may be* width=height=100%
|
|
// or something similar (either set directly or via a CSS class),
|
|
// monitor when viewport size changes so that I can re-layout.
|
|
this.own(Viewport.on("resize", lang.hitch(this, "resize")));
|
|
}
|
|
},
|
|
|
|
resize: function(changeSize, resultSize){
|
|
// summary:
|
|
// Call this to resize a widget, or after its size has changed.
|
|
// description:
|
|
// ####Change size mode:
|
|
//
|
|
// When changeSize is specified, changes the marginBox of this widget
|
|
// and forces it to re-layout its contents accordingly.
|
|
// changeSize may specify height, width, or both.
|
|
//
|
|
// If resultSize is specified it indicates the size the widget will
|
|
// become after changeSize has been applied.
|
|
//
|
|
// ####Notification mode:
|
|
//
|
|
// When changeSize is null, indicates that the caller has already changed
|
|
// the size of the widget, or perhaps it changed because the browser
|
|
// window was resized. Tells widget to re-layout its contents accordingly.
|
|
//
|
|
// If resultSize is also specified it indicates the size the widget has
|
|
// become.
|
|
//
|
|
// In either mode, this method also:
|
|
//
|
|
// 1. Sets this._borderBox and this._contentBox to the new size of
|
|
// the widget. Queries the current domNode size if necessary.
|
|
// 2. Calls layout() to resize contents (and maybe adjust child widgets).
|
|
// changeSize: Object?
|
|
// Sets the widget to this margin-box size and position.
|
|
// May include any/all of the following properties:
|
|
// | {w: int, h: int, l: int, t: int}
|
|
// resultSize: Object?
|
|
// The margin-box size of this widget after applying changeSize (if
|
|
// changeSize is specified). If caller knows this size and
|
|
// passes it in, we don't need to query the browser to get the size.
|
|
// | {w: int, h: int}
|
|
|
|
var node = this.domNode;
|
|
|
|
// set margin box size, unless it wasn't specified, in which case use current size
|
|
if(changeSize){
|
|
domGeometry.setMarginBox(node, changeSize);
|
|
}
|
|
|
|
// If either height or width wasn't specified by the user, then query node for it.
|
|
// But note that setting the margin box and then immediately querying dimensions may return
|
|
// inaccurate results, so try not to depend on it.
|
|
var mb = resultSize || {};
|
|
lang.mixin(mb, changeSize || {}); // changeSize overrides resultSize
|
|
if( !("h" in mb) || !("w" in mb) ){
|
|
mb = lang.mixin(domGeometry.getMarginBox(node), mb); // just use domGeometry.marginBox() to fill in missing values
|
|
}
|
|
|
|
// Compute and save the size of my border box and content box
|
|
// (w/out calling domGeometry.getContentBox() since that may fail if size was recently set)
|
|
var cs = domStyle.getComputedStyle(node);
|
|
var me = domGeometry.getMarginExtents(node, cs);
|
|
var be = domGeometry.getBorderExtents(node, cs);
|
|
var bb = (this._borderBox = {
|
|
w: mb.w - (me.w + be.w),
|
|
h: mb.h - (me.h + be.h)
|
|
});
|
|
var pe = domGeometry.getPadExtents(node, cs);
|
|
this._contentBox = {
|
|
l: domStyle.toPixelValue(node, cs.paddingLeft),
|
|
t: domStyle.toPixelValue(node, cs.paddingTop),
|
|
w: bb.w - pe.w,
|
|
h: bb.h - pe.h
|
|
};
|
|
|
|
// Callback for widget to adjust size of its children
|
|
this.layout();
|
|
},
|
|
|
|
layout: function(){
|
|
// summary:
|
|
// Widgets override this method to size and position their contents/children.
|
|
// When this is called this._contentBox is guaranteed to be set (see resize()).
|
|
//
|
|
// This is called after startup(), and also when the widget's size has been
|
|
// changed.
|
|
// tags:
|
|
// protected extension
|
|
},
|
|
|
|
_setupChild: function(/*dijit/_WidgetBase*/child){
|
|
// summary:
|
|
// Common setup for initial children and children which are added after startup
|
|
// tags:
|
|
// protected extension
|
|
|
|
var cls = this.baseClass + "-child "
|
|
+ (child.baseClass ? this.baseClass + "-" + child.baseClass : "");
|
|
domClass.add(child.domNode, cls);
|
|
},
|
|
|
|
addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex){
|
|
// Overrides _Container.addChild() to call _setupChild()
|
|
this.inherited(arguments);
|
|
if(this._started){
|
|
this._setupChild(child);
|
|
}
|
|
},
|
|
|
|
removeChild: function(/*dijit/_WidgetBase*/ child){
|
|
// Overrides _Container.removeChild() to remove class added by _setupChild()
|
|
var cls = this.baseClass + "-child"
|
|
+ (child.baseClass ?
|
|
" " + this.baseClass + "-" + child.baseClass : "");
|
|
domClass.remove(child.domNode, cls);
|
|
|
|
this.inherited(arguments);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/popup':function(){
|
|
define("dijit/popup", [
|
|
"dojo/_base/array", // array.forEach array.some
|
|
"dojo/aspect",
|
|
"dojo/_base/connect", // connect._keypress
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.isDescendant
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/dom-construct", // domConstruct.create domConstruct.destroy
|
|
"dojo/dom-geometry", // domGeometry.isBodyLtr
|
|
"dojo/dom-style", // domStyle.set
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/keys",
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/on",
|
|
"dojo/sniff", // has("ie") has("mozilla")
|
|
"./place",
|
|
"./BackgroundIframe",
|
|
"./main" // dijit (defining dijit.popup to match API doc)
|
|
], function(array, aspect, connect, declare, dom, domAttr, domConstruct, domGeometry, domStyle, event, keys, lang, on, has,
|
|
place, BackgroundIframe, dijit){
|
|
|
|
// module:
|
|
// dijit/popup
|
|
|
|
/*=====
|
|
var __OpenArgs = {
|
|
// popup: Widget
|
|
// widget to display
|
|
// parent: Widget
|
|
// the button etc. that is displaying this popup
|
|
// around: DomNode
|
|
// DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.)
|
|
// x: Integer
|
|
// Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.)
|
|
// y: Integer
|
|
// Absolute vertical position (in pixels) to place node at. (Specify this *or* "around" parameter.)
|
|
// orient: Object|String
|
|
// When the around parameter is specified, orient should be a list of positions to try, ex:
|
|
// | [ "below", "above" ]
|
|
// For backwards compatibility it can also be an (ordered) hash of tuples of the form
|
|
// (around-node-corner, popup-node-corner), ex:
|
|
// | { "BL": "TL", "TL": "BL" }
|
|
// where BL means "bottom left" and "TL" means "top left", etc.
|
|
//
|
|
// dijit/popup.open() tries to position the popup according to each specified position, in order,
|
|
// until the popup appears fully within the viewport.
|
|
//
|
|
// The default value is ["below", "above"]
|
|
//
|
|
// When an (x,y) position is specified rather than an around node, orient is either
|
|
// "R" or "L". R (for right) means that it tries to put the popup to the right of the mouse,
|
|
// specifically positioning the popup's top-right corner at the mouse position, and if that doesn't
|
|
// fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner,
|
|
// and the top-right corner.
|
|
// onCancel: Function
|
|
// callback when user has canceled the popup by:
|
|
//
|
|
// 1. hitting ESC or
|
|
// 2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog);
|
|
// i.e. whenever popupWidget.onCancel() is called, args.onCancel is called
|
|
// onClose: Function
|
|
// callback whenever this popup is closed
|
|
// onExecute: Function
|
|
// callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only)
|
|
// padding: place.__Position
|
|
// adding a buffer around the opening position. This is only useful when around is not set.
|
|
};
|
|
=====*/
|
|
|
|
function destroyWrapper(){
|
|
// summary:
|
|
// Function to destroy wrapper when popup widget is destroyed.
|
|
// Left in this scope to avoid memory leak on IE8 on refresh page, see #15206.
|
|
if(this._popupWrapper){
|
|
domConstruct.destroy(this._popupWrapper);
|
|
delete this._popupWrapper;
|
|
}
|
|
}
|
|
|
|
var PopupManager = declare(null, {
|
|
// summary:
|
|
// Used to show drop downs (ex: the select list of a ComboBox)
|
|
// or popups (ex: right-click context menus).
|
|
|
|
// _stack: dijit/_WidgetBase[]
|
|
// Stack of currently popped up widgets.
|
|
// (someone opened _stack[0], and then it opened _stack[1], etc.)
|
|
_stack: [],
|
|
|
|
// _beginZIndex: Number
|
|
// Z-index of the first popup. (If first popup opens other
|
|
// popups they get a higher z-index.)
|
|
_beginZIndex: 1000,
|
|
|
|
_idGen: 1,
|
|
|
|
_createWrapper: function(/*Widget*/ widget){
|
|
// summary:
|
|
// Initialization for widgets that will be used as popups.
|
|
// Puts widget inside a wrapper DIV (if not already in one),
|
|
// and returns pointer to that wrapper DIV.
|
|
|
|
var wrapper = widget._popupWrapper,
|
|
node = widget.domNode;
|
|
|
|
if(!wrapper){
|
|
// Create wrapper <div> for when this widget [in the future] will be used as a popup.
|
|
// This is done early because of IE bugs where creating/moving DOM nodes causes focus
|
|
// to go wonky, see tests/robot/Toolbar.html to reproduce
|
|
wrapper = domConstruct.create("div", {
|
|
"class":"dijitPopup",
|
|
style:{ display: "none"},
|
|
role: "presentation"
|
|
}, widget.ownerDocumentBody);
|
|
wrapper.appendChild(node);
|
|
|
|
var s = node.style;
|
|
s.display = "";
|
|
s.visibility = "";
|
|
s.position = "";
|
|
s.top = "0px";
|
|
|
|
widget._popupWrapper = wrapper;
|
|
aspect.after(widget, "destroy", destroyWrapper, true);
|
|
}
|
|
|
|
return wrapper;
|
|
},
|
|
|
|
moveOffScreen: function(/*Widget*/ widget){
|
|
// summary:
|
|
// Moves the popup widget off-screen.
|
|
// Do not use this method to hide popups when not in use, because
|
|
// that will create an accessibility issue: the offscreen popup is
|
|
// still in the tabbing order.
|
|
|
|
// Create wrapper if not already there
|
|
var wrapper = this._createWrapper(widget);
|
|
|
|
domStyle.set(wrapper, {
|
|
visibility: "hidden",
|
|
top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
|
|
display: ""
|
|
});
|
|
},
|
|
|
|
hide: function(/*Widget*/ widget){
|
|
// summary:
|
|
// Hide this popup widget (until it is ready to be shown).
|
|
// Initialization for widgets that will be used as popups
|
|
//
|
|
// Also puts widget inside a wrapper DIV (if not already in one)
|
|
//
|
|
// If popup widget needs to layout it should
|
|
// do so when it is made visible, and popup._onShow() is called.
|
|
|
|
// Create wrapper if not already there
|
|
var wrapper = this._createWrapper(widget);
|
|
|
|
domStyle.set(wrapper, "display", "none");
|
|
},
|
|
|
|
getTopPopup: function(){
|
|
// summary:
|
|
// Compute the closest ancestor popup that's *not* a child of another popup.
|
|
// Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button.
|
|
var stack = this._stack;
|
|
for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){
|
|
/* do nothing, just trying to get right value for pi */
|
|
}
|
|
return stack[pi];
|
|
},
|
|
|
|
open: function(/*__OpenArgs*/ args){
|
|
// summary:
|
|
// Popup the widget at the specified position
|
|
//
|
|
// example:
|
|
// opening at the mouse position
|
|
// | popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
|
|
//
|
|
// example:
|
|
// opening the widget as a dropdown
|
|
// | popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
|
|
//
|
|
// Note that whatever widget called dijit/popup.open() should also listen to its own _onBlur callback
|
|
// (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.
|
|
|
|
var stack = this._stack,
|
|
widget = args.popup,
|
|
orient = args.orient || ["below", "below-alt", "above", "above-alt"],
|
|
ltr = args.parent ? args.parent.isLeftToRight() : domGeometry.isBodyLtr(widget.ownerDocument),
|
|
around = args.around,
|
|
id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++);
|
|
|
|
// If we are opening a new popup that isn't a child of a currently opened popup, then
|
|
// close currently opened popup(s). This should happen automatically when the old popups
|
|
// gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198].
|
|
while(stack.length && (!args.parent || !dom.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){
|
|
this.close(stack[stack.length-1].widget);
|
|
}
|
|
|
|
// Get pointer to popup wrapper, and create wrapper if it doesn't exist
|
|
var wrapper = this._createWrapper(widget);
|
|
|
|
|
|
domAttr.set(wrapper, {
|
|
id: id,
|
|
style: {
|
|
zIndex: this._beginZIndex + stack.length
|
|
},
|
|
"class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup",
|
|
dijitPopupParent: args.parent ? args.parent.id : ""
|
|
});
|
|
|
|
if(has("ie") || has("mozilla")){
|
|
if(!widget.bgIframe){
|
|
// setting widget.bgIframe triggers cleanup in _Widget.destroy()
|
|
widget.bgIframe = new BackgroundIframe(wrapper);
|
|
}
|
|
}
|
|
|
|
// position the wrapper node and make it visible
|
|
var best = around ?
|
|
place.around(wrapper, around, orient, ltr, widget.orient ? lang.hitch(widget, "orient") : null) :
|
|
place.at(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding);
|
|
|
|
wrapper.style.display = "";
|
|
wrapper.style.visibility = "visible";
|
|
widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown
|
|
|
|
var handlers = [];
|
|
|
|
// provide default escape and tab key handling
|
|
// (this will work for any widget, not just menu)
|
|
handlers.push(on(wrapper, connect._keypress, lang.hitch(this, function(evt){
|
|
if(evt.charOrCode == keys.ESCAPE && args.onCancel){
|
|
event.stop(evt);
|
|
args.onCancel();
|
|
}else if(evt.charOrCode === keys.TAB){
|
|
event.stop(evt);
|
|
var topPopup = this.getTopPopup();
|
|
if(topPopup && topPopup.onCancel){
|
|
topPopup.onCancel();
|
|
}
|
|
}
|
|
})));
|
|
|
|
// watch for cancel/execute events on the popup and notify the caller
|
|
// (for a menu, "execute" means clicking an item)
|
|
if(widget.onCancel && args.onCancel){
|
|
handlers.push(widget.on("cancel", args.onCancel));
|
|
}
|
|
|
|
handlers.push(widget.on(widget.onExecute ? "execute" : "change", lang.hitch(this, function(){
|
|
var topPopup = this.getTopPopup();
|
|
if(topPopup && topPopup.onExecute){
|
|
topPopup.onExecute();
|
|
}
|
|
})));
|
|
|
|
stack.push({
|
|
widget: widget,
|
|
parent: args.parent,
|
|
onExecute: args.onExecute,
|
|
onCancel: args.onCancel,
|
|
onClose: args.onClose,
|
|
handlers: handlers
|
|
});
|
|
|
|
if(widget.onOpen){
|
|
// TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here)
|
|
widget.onOpen(best);
|
|
}
|
|
|
|
return best;
|
|
},
|
|
|
|
close: function(/*Widget?*/ popup){
|
|
// summary:
|
|
// Close specified popup and any popups that it parented.
|
|
// If no popup is specified, closes all popups.
|
|
|
|
var stack = this._stack;
|
|
|
|
// Basically work backwards from the top of the stack closing popups
|
|
// until we hit the specified popup, but IIRC there was some issue where closing
|
|
// a popup would cause others to close too. Thus if we are trying to close B in [A,B,C]
|
|
// closing C might close B indirectly and then the while() condition will run where stack==[A]...
|
|
// so the while condition is constructed defensively.
|
|
while((popup && array.some(stack, function(elem){return elem.widget == popup;})) ||
|
|
(!popup && stack.length)){
|
|
var top = stack.pop(),
|
|
widget = top.widget,
|
|
onClose = top.onClose;
|
|
|
|
if(widget.onClose){
|
|
// TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here)
|
|
widget.onClose();
|
|
}
|
|
|
|
var h;
|
|
while(h = top.handlers.pop()){ h.remove(); }
|
|
|
|
// Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc.
|
|
if(widget && widget.domNode){
|
|
this.hide(widget);
|
|
}
|
|
|
|
if(onClose){
|
|
onClose();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return (dijit.popup = new PopupManager());
|
|
});
|
|
|
|
},
|
|
'dijit/_base/manager':function(){
|
|
define("dijit/_base/manager", [
|
|
"dojo/_base/array",
|
|
"dojo/_base/config", // defaultDuration
|
|
"dojo/_base/lang",
|
|
"../registry",
|
|
"../main" // for setting exports to dijit namespace
|
|
], function(array, config, lang, registry, dijit){
|
|
|
|
// module:
|
|
// dijit/_base/manager
|
|
|
|
var exports = {
|
|
// summary:
|
|
// Deprecated. Shim to methods on registry, plus a few other declarations.
|
|
// New code should access dijit/registry directly when possible.
|
|
};
|
|
|
|
array.forEach(["byId", "getUniqueId", "findWidgets", "_destroyAll", "byNode", "getEnclosingWidget"], function(name){
|
|
exports[name] = registry[name];
|
|
});
|
|
|
|
lang.mixin(exports, {
|
|
// defaultDuration: Integer
|
|
// The default fx.animation speed (in ms) to use for all Dijit
|
|
// transitional fx.animations, unless otherwise specified
|
|
// on a per-instance basis. Defaults to 200, overrided by
|
|
// `djConfig.defaultDuration`
|
|
defaultDuration: config["defaultDuration"] || 200
|
|
});
|
|
|
|
lang.mixin(dijit, exports);
|
|
|
|
/*===== return exports; =====*/
|
|
return dijit; // for back compat :-(
|
|
});
|
|
|
|
},
|
|
'dijit/layout/StackController':function(){
|
|
define("dijit/layout/StackController", [
|
|
"dojo/_base/array", // array.forEach array.indexOf array.map
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class",
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/keys", // keys
|
|
"dojo/_base/lang", // lang.getObject
|
|
"dojo/on",
|
|
"../focus", // focus.focus()
|
|
"../registry", // registry.byId
|
|
"../_Widget",
|
|
"../_TemplatedMixin",
|
|
"../_Container",
|
|
"../form/ToggleButton",
|
|
"dojo/i18n!../nls/common"
|
|
], function(array, declare, domClass, event, keys, lang, on,
|
|
focus, registry, _Widget, _TemplatedMixin, _Container, ToggleButton){
|
|
|
|
// module:
|
|
// dijit/layout/StackController
|
|
|
|
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,
|
|
|
|
_aria_attr: "aria-selected",
|
|
|
|
buildRendering: function(/*Event*/ evt){
|
|
this.inherited(arguments);
|
|
(this.focusNode || this.domNode).setAttribute("role", "tab");
|
|
}
|
|
});
|
|
|
|
|
|
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,
|
|
|
|
// buttonWidgetCloseClass: String
|
|
// CSS class of [x] close icon, used by event delegation code to tell when close button was clicked
|
|
buttonWidgetCloseClass: "dijitStackCloseButton",
|
|
|
|
constructor: function(params /*===== , srcNodeRef =====*/){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree
|
|
|
|
this.pane2button = {}; // mapping from pane id to buttons
|
|
},
|
|
|
|
postCreate: function(){
|
|
this.inherited(arguments);
|
|
|
|
// Listen to notifications from StackContainer.
|
|
// TODO: do this through bubbled events instead of topics
|
|
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");
|
|
|
|
// Listen for click events to select or close tabs.
|
|
// No need to worry about ENTER/SPACE key handling: tabs are selected via left/right arrow keys,
|
|
// and closed via shift-F10 (to show the close menu).
|
|
this.connect(this.containerNode, 'click', function(evt){
|
|
var button = registry.getEnclosingWidget(evt.target);
|
|
if(button != this.containerNode && !button.disabled && button.page){
|
|
for(var target = evt.target; target !== this.containerNode; target = target.parentNode){
|
|
if(domClass.contains(target, this.buttonWidgetCloseClass)){
|
|
this.onCloseButtonClick(button.page);
|
|
break;
|
|
}else if(target == button.domNode){
|
|
this.onButtonClick(button.page);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
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);
|
|
}
|
|
|
|
// Reflect events like page title changes to tab buttons
|
|
var containerNode = registry.byId(this.containerId).containerNode,
|
|
pane2button = this.pane2button,
|
|
paneToButtonAttr = {
|
|
"title": "label",
|
|
"showtitle": "showLabel",
|
|
"iconclass": "iconClass",
|
|
"closable": "closeButton",
|
|
"tooltip": "title",
|
|
"disabled": "disabled"
|
|
},
|
|
connectFunc = function(attr, buttonAttr){
|
|
return on(containerNode, "attrmodified-" + attr, function(evt){
|
|
var button = pane2button[evt.detail && evt.detail.widget && evt.detail.widget.id];
|
|
if(button){
|
|
button.set(buttonAttr, evt.detail.newValue);
|
|
}
|
|
});
|
|
};
|
|
for(var attr in paneToButtonAttr){
|
|
this.own(connectFunc(attr, paneToButtonAttr[attr]));
|
|
}
|
|
},
|
|
|
|
destroy: function(){
|
|
// Since the buttons are internal to the StackController widget, destroy() should remove them, which is
|
|
// done by calling onRemoveChild().
|
|
for(var pane in this.pane2button){
|
|
this.onRemoveChild(registry.byId(pane));
|
|
}
|
|
|
|
// TODO: destroyRecursive() will call destroy() on each child button twice. Once from the above code,
|
|
// and once because _WidgetBase.destroyDescendants() deletes anything inside of this.containerNode.
|
|
// Probably shouldn't attach that DOMNode as this.containerNode.
|
|
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
onAddChild: function(/*dijit/_WidgetBase*/ 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,
|
|
name: this.id + "_" + page.id,
|
|
label: page.title,
|
|
disabled: page.disabled,
|
|
ownerDocument: this.ownerDocument,
|
|
dir: page.dir,
|
|
lang: page.lang,
|
|
textDir: page.textDir,
|
|
showLabel: page.showTitle,
|
|
iconClass: page.iconClass,
|
|
closeButton: page.closable,
|
|
title: page.tooltip,
|
|
page: 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){
|
|
// If this is the first child then StackContainer will soon publish that it's selected,
|
|
// but before that StackContainer calls layout(), and before layout() is called the
|
|
// StackController needs to have the proper height... which means that the button needs
|
|
// to be marked as selected now. See test_TabContainer_CSS.html for test.
|
|
this.onSelectChild(page);
|
|
}
|
|
},
|
|
|
|
onRemoveChild: function(/*dijit/_WidgetBase*/ 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; }
|
|
|
|
var button = this.pane2button[page.id];
|
|
if(button){
|
|
this.removeChild(button);
|
|
delete this.pane2button[page.id];
|
|
button.destroy();
|
|
}
|
|
delete page.controlButton;
|
|
},
|
|
|
|
onSelectChild: function(/*dijit/_WidgetBase*/ 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("tabIndex", "-1");
|
|
}
|
|
|
|
var newButton=this.pane2button[page.id];
|
|
newButton.set('checked', 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/_WidgetBase*/ page){
|
|
// summary:
|
|
// Called whenever one of my child buttons is pressed in an attempt to select a page
|
|
// tags:
|
|
// private
|
|
|
|
var button = this.pane2button[page.id];
|
|
|
|
// For TabContainer where the tabs are <span>, need to set focus explicitly when left/right arrow
|
|
focus.focus(button.focusNode);
|
|
|
|
if(this._currentChild && 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.
|
|
button.set('checked', true);
|
|
}
|
|
var container = registry.byId(this.containerId);
|
|
container.selectChild(page);
|
|
},
|
|
|
|
onCloseButtonClick: function(/*dijit/_WidgetBase*/ 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 idx = array.indexOf(children, this.pane2button[this._currentChild.id]),
|
|
current = children[idx];
|
|
|
|
// Pick next/previous non-disabled button to focus on. If we get back to the original button it means
|
|
// that all buttons must be disabled, so return current child to avoid an infinite loop.
|
|
var child;
|
|
do{
|
|
idx = (idx + (forward ? 1 : children.length - 1)) % children.length;
|
|
child = children[idx];
|
|
}while(child.disabled && child != current);
|
|
|
|
return child; // dijit/_WidgetBase
|
|
},
|
|
|
|
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:
|
|
// Navigate to first non-disabled child
|
|
var children = this.getChildren();
|
|
for(var idx = 0; idx < children.length; idx++){
|
|
var child = children[idx];
|
|
if(!child.disabled){
|
|
this.onButtonClick(child.page);
|
|
break;
|
|
}
|
|
}
|
|
event.stop(e);
|
|
break;
|
|
case keys.END:
|
|
// Navigate to last non-disabled child
|
|
var children = this.getChildren();
|
|
for(var idx = children.length-1; idx >= 0; idx--){
|
|
var child = children[idx];
|
|
if(!child.disabled){
|
|
this.onButtonClick(child.page);
|
|
break;
|
|
}
|
|
}
|
|
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.onButtonClick(this.adjacent(!e.shiftKey).page);
|
|
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.onButtonClick(this.adjacent(forward).page);
|
|
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\" data-dojo-attach-point=\"connectorNode\"></div>\n</div>\n",
|
|
'dojo/dnd/Mover':function(){
|
|
define("dojo/dnd/Mover", [
|
|
"../_base/array", "../_base/declare", "../_base/event", "../_base/lang", "../sniff", "../_base/window",
|
|
"../dom", "../dom-geometry", "../dom-style", "../Evented", "../on", "../touch", "./common", "./autoscroll"
|
|
], function(array, declare, event, lang, has, win, dom, domGeom, domStyle, Evented, on, touch, dnd, autoscroll){
|
|
|
|
// module:
|
|
// dojo/dnd/Mover
|
|
|
|
return declare("dojo.dnd.Mover", [Evented], {
|
|
// 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.
|
|
|
|
constructor: function(node, e, host){
|
|
// 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 = dom.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
|
|
// listener is disconnected.
|
|
on(d, touch.move, lang.hitch(this, "onFirstMove")),
|
|
|
|
// These are called continually during the drag
|
|
on(d, touch.move, lang.hitch(this, "onMouseMove")),
|
|
|
|
// And these are called at the end of the drag
|
|
on(d, touch.release, lang.hitch(this, "onMouseUp")),
|
|
|
|
// cancel text selection and text dragging
|
|
on(d, "dragstart", event.stop),
|
|
on(d.body, "selectstart", event.stop)
|
|
];
|
|
|
|
// Tell autoscroll that a drag is starting
|
|
autoscroll.autoScrollStart(d);
|
|
|
|
// 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
|
|
autoscroll.autoScroll(e);
|
|
var m = this.marginBox;
|
|
this.host.onMove(this, {l: m.l + e.pageX, t: m.t + e.pageY}, e);
|
|
event.stop(e);
|
|
},
|
|
onMouseUp: function(e){
|
|
if(has("webkit") && has("mac") && this.mouseButton == 2 ?
|
|
e.button == 0 : this.mouseButton == e.button){ // TODO Should condition be met for touch devices, too?
|
|
this.destroy();
|
|
}
|
|
event.stop(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 = domGeom.getMarginBox(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 domGeom.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 = win.doc.body;
|
|
var bs = domStyle.getComputedStyle(b);
|
|
var bm = domGeom.getMarginBox(b, bs);
|
|
var bc = domGeom.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 touch.move that call this function
|
|
this.events.shift().remove();
|
|
},
|
|
destroy: function(){
|
|
// summary:
|
|
// stops the move, deletes all references, so the object can be garbage-collected
|
|
array.forEach(this.events, function(handle){ handle.remove(); });
|
|
// undo global settings
|
|
var h = this.host;
|
|
if(h && h.onMoveStop){
|
|
h.onMoveStop(this);
|
|
}
|
|
// destroy objects
|
|
this.events = this.node = this.host = null;
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/layout/TabContainer':function(){
|
|
define("dijit/layout/TabContainer", [
|
|
"dojo/_base/lang", // lang.getObject
|
|
"dojo/_base/declare", // declare
|
|
"./_TabContainerBase",
|
|
"./TabController",
|
|
"./ScrollingTabController"
|
|
], function(lang, declare, _TabContainerBase, TabController, ScrollingTabController){
|
|
|
|
// module:
|
|
// dijit/layout/TabContainer
|
|
|
|
|
|
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.
|
|
//
|
|
// See `StackContainer.ChildWidgetProperties` for details on the properties that can be set on
|
|
// children of a `TabContainer`.
|
|
|
|
// 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: Class
|
|
// 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
|
|
|
|
// "string" branch for back-compat, remove for 2.0
|
|
var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"),
|
|
TabController = typeof this.controllerWidget == "string" ? lang.getObject(this.controllerWidget) :
|
|
this.controllerWidget;
|
|
|
|
return new TabController({
|
|
id: this.id + "_tablist",
|
|
ownerDocument: this.ownerDocument,
|
|
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 ?
|
|
ScrollingTabController : TabController;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/BackgroundIframe':function(){
|
|
define("dijit/BackgroundIframe", [
|
|
"require", // require.toUrl
|
|
"./main", // to export dijit.BackgroundIframe
|
|
"dojo/_base/config",
|
|
"dojo/dom-construct", // domConstruct.create
|
|
"dojo/dom-style", // domStyle.set
|
|
"dojo/_base/lang", // lang.extend lang.hitch
|
|
"dojo/on",
|
|
"dojo/sniff", // has("ie"), has("mozilla"), has("quirks")
|
|
"dojo/_base/window" // win.doc.createElement
|
|
], function(require, dijit, config, domConstruct, domStyle, lang, on, has, win){
|
|
|
|
// module:
|
|
// dijit/BackgroundIFrame
|
|
|
|
// TODO: remove _frames, it isn't being used much, since popups never release their
|
|
// iframes (see [22236])
|
|
var _frames = new function(){
|
|
// summary:
|
|
// cache of iframes
|
|
|
|
var queue = [];
|
|
|
|
this.pop = function(){
|
|
var iframe;
|
|
if(queue.length){
|
|
iframe = queue.pop();
|
|
iframe.style.display="";
|
|
}else{
|
|
if(has("ie") < 9){
|
|
var burl = config["dojoBlankHtmlUrl"] || require.toUrl("dojo/resources/blank.html") || "javascript:\"\"";
|
|
var html="<iframe src='" + burl + "' role='presentation'"
|
|
+ " style='position: absolute; left: 0px; top: 0px;"
|
|
+ "z-index: -1; filter:Alpha(Opacity=\"0\");'>";
|
|
iframe = win.doc.createElement(html);
|
|
}else{
|
|
iframe = domConstruct.create("iframe");
|
|
iframe.src = 'javascript:""';
|
|
iframe.className = "dijitBackgroundIframe";
|
|
iframe.setAttribute("role", "presentation");
|
|
domStyle.set(iframe, "opacity", 0.1);
|
|
}
|
|
iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didn't work.
|
|
}
|
|
return iframe;
|
|
};
|
|
|
|
this.push = function(iframe){
|
|
iframe.style.display="none";
|
|
queue.push(iframe);
|
|
}
|
|
}();
|
|
|
|
|
|
dijit.BackgroundIframe = function(/*DomNode*/ node){
|
|
// summary:
|
|
// For IE/FF z-index schenanigans. id attribute is required.
|
|
//
|
|
// description:
|
|
// new dijit.BackgroundIframe(node).
|
|
//
|
|
// Makes a background iframe as a child of node, that fills
|
|
// area (and position) of node
|
|
|
|
if(!node.id){ throw new Error("no id"); }
|
|
if(has("ie") || has("mozilla")){
|
|
var iframe = (this.iframe = _frames.pop());
|
|
node.appendChild(iframe);
|
|
if(has("ie")<7 || has("quirks")){
|
|
this.resize(node);
|
|
this._conn = on(node, 'resize', lang.hitch(this, function(){
|
|
this.resize(node);
|
|
}));
|
|
}else{
|
|
domStyle.set(iframe, {
|
|
width: '100%',
|
|
height: '100%'
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
lang.extend(dijit.BackgroundIframe, {
|
|
resize: function(node){
|
|
// summary:
|
|
// Resize the iframe so it's the same size as node.
|
|
// Needed on IE6 and IE/quirks because height:100% doesn't work right.
|
|
if(this.iframe){
|
|
domStyle.set(this.iframe, {
|
|
width: node.offsetWidth + 'px',
|
|
height: node.offsetHeight + 'px'
|
|
});
|
|
}
|
|
},
|
|
destroy: function(){
|
|
// summary:
|
|
// destroy the iframe
|
|
if(this._conn){
|
|
this._conn.remove();
|
|
this._conn = null;
|
|
}
|
|
if(this.iframe){
|
|
_frames.push(this.iframe);
|
|
delete this.iframe;
|
|
}
|
|
}
|
|
});
|
|
|
|
return dijit.BackgroundIframe;
|
|
});
|
|
|
|
},
|
|
'url:dijit/templates/Menu.html':"<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" role=\"menu\" tabIndex=\"${tabIndex}\"\n\t data-dojo-attach-event=\"onkeypress:_onKeyPress\" cellspacing=\"0\">\n\t<tbody class=\"dijitReset\" data-dojo-attach-point=\"containerNode\"></tbody>\n</table>\n",
|
|
'dojo/dnd/Avatar':function(){
|
|
define("dojo/dnd/Avatar", [
|
|
"../_base/declare",
|
|
"../_base/window",
|
|
"../dom",
|
|
"../dom-attr",
|
|
"../dom-class",
|
|
"../dom-construct",
|
|
"../hccss",
|
|
"../query"
|
|
], function(declare, win, dom, domAttr, domClass, domConstruct, has, query){
|
|
|
|
// module:
|
|
// dojo/dnd/Avatar
|
|
|
|
return declare("dojo.dnd.Avatar", null, {
|
|
// summary:
|
|
// Object that represents transferred DnD items visually
|
|
// manager: Object
|
|
// a DnD manager object
|
|
|
|
constructor: function(manager){
|
|
this.manager = manager;
|
|
this.construct();
|
|
},
|
|
|
|
// methods
|
|
construct: function(){
|
|
// summary:
|
|
// constructor function;
|
|
// it is separate so it can be (dynamically) overwritten in case of need
|
|
|
|
var a = domConstruct.create("table", {
|
|
"class": "dojoDndAvatar",
|
|
style: {
|
|
position: "absolute",
|
|
zIndex: "1999",
|
|
margin: "0px"
|
|
}
|
|
}),
|
|
source = this.manager.source, node,
|
|
b = domConstruct.create("tbody", null, a),
|
|
tr = domConstruct.create("tr", null, b),
|
|
td = domConstruct.create("td", null, tr),
|
|
k = Math.min(5, this.manager.nodes.length), i = 0;
|
|
|
|
if(has("highcontrast")){
|
|
domConstruct.create("span", {
|
|
id : "a11yIcon",
|
|
innerHTML : this.manager.copy ? '+' : "<"
|
|
}, td)
|
|
}
|
|
domConstruct.create("span", {
|
|
innerHTML: source.generateText ? this._generateText() : ""
|
|
}, td);
|
|
|
|
// we have to set the opacity on IE only after the node is live
|
|
domAttr.set(tr, {
|
|
"class": "dojoDndAvatarHeader",
|
|
style: {opacity: 0.9}
|
|
});
|
|
for(; i < k; ++i){
|
|
if(source.creator){
|
|
// create an avatar representation of the node
|
|
node = source._normalizedCreator(source.getItem(this.manager.nodes[i].id).data, "avatar").node;
|
|
}else{
|
|
// or just clone the node and hope it works
|
|
node = this.manager.nodes[i].cloneNode(true);
|
|
if(node.tagName.toLowerCase() == "tr"){
|
|
// insert extra table nodes
|
|
var table = domConstruct.create("table"),
|
|
tbody = domConstruct.create("tbody", null, table);
|
|
tbody.appendChild(node);
|
|
node = table;
|
|
}
|
|
}
|
|
node.id = "";
|
|
tr = domConstruct.create("tr", null, b);
|
|
td = domConstruct.create("td", null, tr);
|
|
td.appendChild(node);
|
|
domAttr.set(tr, {
|
|
"class": "dojoDndAvatarItem",
|
|
style: {opacity: (9 - i) / 10}
|
|
});
|
|
}
|
|
this.node = a;
|
|
},
|
|
destroy: function(){
|
|
// summary:
|
|
// destructor for the avatar; called to remove all references so it can be garbage-collected
|
|
domConstruct.destroy(this.node);
|
|
this.node = false;
|
|
},
|
|
update: function(){
|
|
// summary:
|
|
// updates the avatar to reflect the current DnD state
|
|
domClass.toggle(this.node, "dojoDndAvatarCanDrop", this.manager.canDropFlag);
|
|
if(has("highcontrast")){
|
|
var icon = dom.byId("a11yIcon");
|
|
var text = '+'; // assume canDrop && copy
|
|
if (this.manager.canDropFlag && !this.manager.copy){
|
|
text = '< '; // canDrop && move
|
|
}else if (!this.manager.canDropFlag && !this.manager.copy){
|
|
text = "o"; //!canDrop && move
|
|
}else if(!this.manager.canDropFlag){
|
|
text = 'x'; // !canDrop && copy
|
|
}
|
|
icon.innerHTML=text;
|
|
}
|
|
// replace text
|
|
query(("tr.dojoDndAvatarHeader td span" +(has("highcontrast") ? " span" : "")), this.node).forEach(
|
|
function(node){
|
|
node.innerHTML = this.manager.source.generateText ? this._generateText() : "";
|
|
}, this);
|
|
},
|
|
_generateText: function(){
|
|
// summary:
|
|
// generates a proper text to reflect copying or moving of items
|
|
return this.manager.nodes.length.toString();
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/Button':function(){
|
|
require({cache:{
|
|
'url:dijit/form/templates/Button.html':"<span class=\"dijit dijitReset dijitInline\" role=\"presentation\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdata-dojo-attach-event=\"ondijitclick:_onClick\" role=\"presentation\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdata-dojo-attach-point=\"titleNode,focusNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" data-dojo-attach-point=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">●</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdata-dojo-attach-point=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\ttabIndex=\"-1\" role=\"presentation\" data-dojo-attach-point=\"valueNode\"\n/></span>\n"}});
|
|
define("dijit/form/Button", [
|
|
"require",
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.toggle
|
|
"dojo/has", // has("dijit-legacy-requires")
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/_base/lang", // lang.trim
|
|
"dojo/ready",
|
|
"./_FormWidget",
|
|
"./_ButtonMixin",
|
|
"dojo/text!./templates/Button.html"
|
|
], function(require, declare, domClass, has, kernel, lang, ready, _FormWidget, _ButtonMixin, template){
|
|
|
|
// module:
|
|
// dijit/form/Button
|
|
|
|
// Back compat w/1.6, remove for 2.0
|
|
if(has("dijit-legacy-requires")){
|
|
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 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
|
|
e.stopPropagation(); // avoid two events bubbling from Button widget
|
|
// leave ok = true so that subclasses can do what they need to do
|
|
}
|
|
}
|
|
return ok;
|
|
},
|
|
|
|
_fillContent: function(/*DomNode*/ source){
|
|
// Overrides _Templated._fillContent().
|
|
// If button label is specified as srcNodeRef.innerHTML rather than
|
|
// this.params.label, handle it here.
|
|
// TODO: remove the method in 2.0, parser will do it all for me
|
|
if(source && (!this.params || !("label" in this.params))){
|
|
var sourceLabel = lang.trim(source.innerHTML);
|
|
if(sourceLabel){
|
|
this.label = sourceLabel; // _applyAttributes will be called after buildRendering completes to update the DOM
|
|
}
|
|
}
|
|
},
|
|
|
|
_setShowLabelAttr: function(val){
|
|
if(this.containerNode){
|
|
domClass.toggle(this.containerNode, "dijitDisplayNone", !val);
|
|
}
|
|
this._set("showLabel", val);
|
|
},
|
|
|
|
setLabel: function(/*String*/ content){
|
|
// summary:
|
|
// Deprecated. Use set('label', ...) instead.
|
|
kernel.deprecated("dijit.form.Button.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0");
|
|
this.set("label", content);
|
|
},
|
|
|
|
_setLabelAttr: function(/*String*/ content){
|
|
// summary:
|
|
// Hook for set('label', ...) to work.
|
|
// description:
|
|
// Set the label (text) of the button; takes an HTML string.
|
|
// If the label is hidden (showLabel=false) then and no title has
|
|
// been specified, then label is also set as title attribute of icon.
|
|
this.inherited(arguments);
|
|
if(!this.showLabel && !("title" in this.params)){
|
|
this.titleNode.title = lang.trim(this.containerNode.innerText || this.containerNode.textContent || '');
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
'url:dijit/layout/templates/TabContainer.html':"<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" data-dojo-attach-point=\"tablistNode\"></div>\n\t<div data-dojo-attach-point=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" data-dojo-attach-point=\"containerNode\"></div>\n</div>\n",
|
|
'dojo/dnd/move':function(){
|
|
define("dojo/dnd/move", [
|
|
"../_base/declare",
|
|
"../dom-geometry", "../dom-style",
|
|
"./common", "./Mover", "./Moveable"
|
|
], function(declare, domGeom, domStyle, dnd, Mover, Moveable){
|
|
|
|
// module:
|
|
// dojo/dnd/move
|
|
|
|
/*=====
|
|
var __constrainedMoveableArgs = declare([Moveable.__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
|
|
});
|
|
=====*/
|
|
|
|
var constrainedMoveable = declare("dojo.dnd.move.constrainedMoveable", 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: __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(/*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 = domGeom.getMarginSize(mover.node);
|
|
c.r -= mb.w;
|
|
c.b -= mb.h;
|
|
}
|
|
},
|
|
onMove: function(/*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);
|
|
}
|
|
});
|
|
|
|
/*=====
|
|
var __boxConstrainedMoveableArgs = declare([__constrainedMoveableArgs], {
|
|
// box: Object
|
|
// a constraint box
|
|
box: {}
|
|
});
|
|
=====*/
|
|
|
|
var boxConstrainedMoveable = declare("dojo.dnd.move.boxConstrainedMoveable", 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: __boxConstrainedMoveableArgs?
|
|
// an optional object with parameters
|
|
var box = params && params.box;
|
|
this.constraints = function(){ return box; };
|
|
}
|
|
});
|
|
|
|
/*=====
|
|
var __parentConstrainedMoveableArgs = declare( [__constrainedMoveableArgs], {
|
|
// area: String
|
|
// A parent's area to restrict the move.
|
|
// Can be "margin", "border", "padding", or "content".
|
|
area: ""
|
|
});
|
|
=====*/
|
|
|
|
var parentConstrainedMoveable = declare("dojo.dnd.move.parentConstrainedMoveable", 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: __parentConstrainedMoveableArgs?
|
|
// an optional object with parameters
|
|
var area = params && params.area;
|
|
this.constraints = function(){
|
|
var n = this.node.parentNode,
|
|
s = domStyle.getComputedStyle(n),
|
|
mb = domGeom.getMarginBox(n, s);
|
|
if(area == "margin"){
|
|
return mb; // Object
|
|
}
|
|
var t = domGeom.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 = domGeom.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 = domGeom.getPadExtents(n, s);
|
|
mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
|
|
return mb; // Object
|
|
};
|
|
}
|
|
});
|
|
|
|
|
|
return {
|
|
// summary:
|
|
// TODOC
|
|
constrainedMoveable: constrainedMoveable,
|
|
boxConstrainedMoveable: boxConstrainedMoveable,
|
|
parentConstrainedMoveable: parentConstrainedMoveable
|
|
};
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/_WidgetBase':function(){
|
|
define("dijit/_WidgetBase", [
|
|
"require", // require.toUrl
|
|
"dojo/_base/array", // array.forEach array.map
|
|
"dojo/aspect",
|
|
"dojo/_base/config", // config.blankGif
|
|
"dojo/_base/connect", // connect.connect
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.byId
|
|
"dojo/dom-attr", // domAttr.set domAttr.remove
|
|
"dojo/dom-class", // domClass.add domClass.replace
|
|
"dojo/dom-construct", // domConstruct.destroy domConstruct.place
|
|
"dojo/dom-geometry", // isBodyLtr
|
|
"dojo/dom-style", // domStyle.set, domStyle.get
|
|
"dojo/has",
|
|
"dojo/_base/kernel",
|
|
"dojo/_base/lang", // mixin(), isArray(), etc.
|
|
"dojo/on",
|
|
"dojo/ready",
|
|
"dojo/Stateful", // Stateful
|
|
"dojo/topic",
|
|
"dojo/_base/window", // win.doc, win.body()
|
|
"./Destroyable",
|
|
"./registry" // registry.getUniqueId(), registry.findWidgets()
|
|
], function(require, array, aspect, config, connect, declare,
|
|
dom, domAttr, domClass, domConstruct, domGeometry, domStyle, has, kernel,
|
|
lang, on, ready, Stateful, topic, win, Destroyable, registry){
|
|
|
|
// module:
|
|
// dijit/_WidgetBase
|
|
|
|
// Flag to make dijit load modules the app didn't explicitly request, for backwards compatibility
|
|
has.add("dijit-legacy-requires", !kernel.isAsync);
|
|
|
|
// For back-compat, remove in 2.0.
|
|
if(has("dijit-legacy-requires")){
|
|
ready(0, function(){
|
|
var requires = ["dijit/_base/manager"];
|
|
require(requires); // use indirection so modules not rolled into a build
|
|
});
|
|
}
|
|
|
|
// Nested hash listing attributes for each tag, all strings in lowercase.
|
|
// ex: {"div": {"style": true, "tabindex" true}, "form": { ...
|
|
var tagAttrs = {};
|
|
function getAttrs(obj){
|
|
var ret = {};
|
|
for(var attr in obj){
|
|
ret[attr.toLowerCase()] = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function nonEmptyAttrToDom(attr){
|
|
// summary:
|
|
// Returns a setter function that copies the attribute to this.domNode,
|
|
// or removes the attribute from this.domNode, depending on whether the
|
|
// value is defined or not.
|
|
return function(val){
|
|
domAttr[val ? "set" : "remove"](this.domNode, attr, val);
|
|
this._set(attr, val);
|
|
};
|
|
}
|
|
|
|
return declare("dijit._WidgetBase", [Stateful, Destroyable], {
|
|
// summary:
|
|
// Future base class for all Dijit widgets.
|
|
// description:
|
|
// Future base class for all Dijit widgets.
|
|
// _Widget extends this class adding support for various features needed by desktop.
|
|
//
|
|
// Provides stubs for widget lifecycle methods for subclasses to extend, like postMixInProperties(), buildRendering(),
|
|
// postCreate(), startup(), and destroy(), and also public API methods like set(), get(), and watch().
|
|
//
|
|
// Widgets can provide custom setters/getters for widget attributes, which are called automatically by set(name, value).
|
|
// For an attribute XXX, define methods _setXXXAttr() and/or _getXXXAttr().
|
|
//
|
|
// _setXXXAttr can also be a string/hash/array mapping from a widget attribute XXX to the widget's DOMNodes:
|
|
//
|
|
// - DOM node attribute
|
|
// | _setFocusAttr: {node: "focusNode", type: "attribute"}
|
|
// | _setFocusAttr: "focusNode" (shorthand)
|
|
// | _setFocusAttr: "" (shorthand, maps to this.domNode)
|
|
// Maps this.focus to this.focusNode.focus, or (last example) this.domNode.focus
|
|
//
|
|
// - DOM node innerHTML
|
|
// | _setTitleAttr: { node: "titleNode", type: "innerHTML" }
|
|
// Maps this.title to this.titleNode.innerHTML
|
|
//
|
|
// - DOM node innerText
|
|
// | _setTitleAttr: { node: "titleNode", type: "innerText" }
|
|
// Maps this.title to this.titleNode.innerText
|
|
//
|
|
// - DOM node CSS class
|
|
// | _setMyClassAttr: { node: "domNode", type: "class" }
|
|
// Maps this.myClass to this.domNode.className
|
|
//
|
|
// If the value of _setXXXAttr is an array, then each element in the array matches one of the
|
|
// formats of the above list.
|
|
//
|
|
// If the custom setter is null, no action is performed other than saving the new value
|
|
// in the widget (in this).
|
|
//
|
|
// If no custom setter is defined for an attribute, then it will be copied
|
|
// to this.focusNode (if the widget defines a focusNode), or this.domNode otherwise.
|
|
// That's only done though for attributes that match DOMNode attributes (title,
|
|
// alt, aria-labelledby, etc.)
|
|
|
|
// id: [const] String
|
|
// A unique, opaque ID string that can be assigned by users or by the
|
|
// system. If the developer passes an ID which is known not to be
|
|
// unique, the specified ID is ignored and the system-generated ID is
|
|
// used instead.
|
|
id: "",
|
|
_setIdAttr: "domNode", // to copy to this.domNode even for auto-generated id's
|
|
|
|
// lang: [const] String
|
|
// Rarely used. Overrides the default Dojo locale used to render this widget,
|
|
// as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute.
|
|
// Value must be among the list of locales specified during by the Dojo bootstrap,
|
|
// formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us).
|
|
lang: "",
|
|
// set on domNode even when there's a focus node. but don't set lang="", since that's invalid.
|
|
_setLangAttr: nonEmptyAttrToDom("lang"),
|
|
|
|
// dir: [const] String
|
|
// Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir)
|
|
// attribute. Either left-to-right "ltr" or right-to-left "rtl". If undefined, widgets renders in page's
|
|
// default direction.
|
|
dir: "",
|
|
// set on domNode even when there's a focus node. but don't set dir="", since that's invalid.
|
|
_setDirAttr: nonEmptyAttrToDom("dir"), // to set on domNode even when there's a focus node
|
|
|
|
// textDir: String
|
|
// Bi-directional support, the main variable which is responsible for the direction of the text.
|
|
// The text direction can be different than the GUI direction by using this parameter in creation
|
|
// of a widget.
|
|
//
|
|
// Allowed values:
|
|
//
|
|
// 1. "ltr"
|
|
// 2. "rtl"
|
|
// 3. "auto" - contextual the direction of a text defined by first strong letter.
|
|
//
|
|
// By default is as the page direction.
|
|
textDir: "",
|
|
|
|
// class: String
|
|
// HTML class attribute
|
|
"class": "",
|
|
_setClassAttr: { node: "domNode", type: "class" },
|
|
|
|
// style: String||Object
|
|
// HTML style attributes as cssText string or name/value hash
|
|
style: "",
|
|
|
|
// title: String
|
|
// HTML title attribute.
|
|
//
|
|
// For form widgets this specifies a tooltip to display when hovering over
|
|
// the widget (just like the native HTML title attribute).
|
|
//
|
|
// For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer,
|
|
// etc., it's used to specify the tab label, accordion pane title, etc.
|
|
title: "",
|
|
|
|
// tooltip: String
|
|
// When this widget's title attribute is used to for a tab label, accordion pane title, etc.,
|
|
// this specifies the tooltip to appear when the mouse is hovered over that text.
|
|
tooltip: "",
|
|
|
|
// baseClass: [protected] String
|
|
// Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate
|
|
// widget state.
|
|
baseClass: "",
|
|
|
|
// srcNodeRef: [readonly] DomNode
|
|
// pointer to original DOM node
|
|
srcNodeRef: null,
|
|
|
|
// domNode: [readonly] DomNode
|
|
// This is our visible representation of the widget! Other DOM
|
|
// Nodes may by assigned to other properties, usually through the
|
|
// template system's data-dojo-attach-point syntax, but the domNode
|
|
// property is the canonical "top level" node in widget UI.
|
|
domNode: null,
|
|
|
|
// containerNode: [readonly] DomNode
|
|
// Designates where children of the source DOM node will be placed.
|
|
// "Children" in this case refers to both DOM nodes and widgets.
|
|
// For example, for myWidget:
|
|
//
|
|
// | <div data-dojo-type=myWidget>
|
|
// | <b> here's a plain DOM node
|
|
// | <span data-dojo-type=subWidget>and a widget</span>
|
|
// | <i> and another plain DOM node </i>
|
|
// | </div>
|
|
//
|
|
// containerNode would point to:
|
|
//
|
|
// | <b> here's a plain DOM node
|
|
// | <span data-dojo-type=subWidget>and a widget</span>
|
|
// | <i> and another plain DOM node </i>
|
|
//
|
|
// In templated widgets, "containerNode" is set via a
|
|
// data-dojo-attach-point assignment.
|
|
//
|
|
// containerNode must be defined for any widget that accepts innerHTML
|
|
// (like ContentPane or BorderContainer or even Button), and conversely
|
|
// is null for widgets that don't, like TextBox.
|
|
containerNode: null,
|
|
|
|
// ownerDocument: [const] Document?
|
|
// The document this widget belongs to. If not specified to constructor, will default to
|
|
// srcNodeRef.ownerDocument, or if no sourceRef specified, then to dojo/_base/window::doc
|
|
ownerDocument: null,
|
|
_setOwnerDocumentAttr: function(val){
|
|
// this setter is merely to avoid automatically trying to set this.domNode.ownerDocument
|
|
this._set("ownerDocument", val);
|
|
},
|
|
|
|
/*=====
|
|
// _started: [readonly] Boolean
|
|
// startup() has completed.
|
|
_started: false,
|
|
=====*/
|
|
|
|
// attributeMap: [protected] Object
|
|
// Deprecated. Instead of attributeMap, widget should have a _setXXXAttr attribute
|
|
// for each XXX attribute to be mapped to the DOM.
|
|
//
|
|
// attributeMap sets up a "binding" between attributes (aka properties)
|
|
// of the widget and the widget's DOM.
|
|
// Changes to widget attributes listed in attributeMap will be
|
|
// reflected into the DOM.
|
|
//
|
|
// For example, calling set('title', 'hello')
|
|
// on a TitlePane will automatically cause the TitlePane's DOM to update
|
|
// with the new title.
|
|
//
|
|
// attributeMap is a hash where the key is an attribute of the widget,
|
|
// and the value reflects a binding to a:
|
|
//
|
|
// - DOM node attribute
|
|
// | focus: {node: "focusNode", type: "attribute"}
|
|
// Maps this.focus to this.focusNode.focus
|
|
//
|
|
// - DOM node innerHTML
|
|
// | title: { node: "titleNode", type: "innerHTML" }
|
|
// Maps this.title to this.titleNode.innerHTML
|
|
//
|
|
// - DOM node innerText
|
|
// | title: { node: "titleNode", type: "innerText" }
|
|
// Maps this.title to this.titleNode.innerText
|
|
//
|
|
// - DOM node CSS class
|
|
// | myClass: { node: "domNode", type: "class" }
|
|
// Maps this.myClass to this.domNode.className
|
|
//
|
|
// If the value is an array, then each element in the array matches one of the
|
|
// formats of the above list.
|
|
//
|
|
// There are also some shorthands for backwards compatibility:
|
|
//
|
|
// - string --> { node: string, type: "attribute" }, for example:
|
|
//
|
|
// | "focusNode" ---> { node: "focusNode", type: "attribute" }
|
|
//
|
|
// - "" --> { node: "domNode", type: "attribute" }
|
|
attributeMap: {},
|
|
|
|
// _blankGif: [protected] String
|
|
// Path to a blank 1x1 image.
|
|
// Used by `<img>` nodes in templates that really get their image via CSS background-image.
|
|
_blankGif: config.blankGif || require.toUrl("dojo/resources/blank.gif"),
|
|
|
|
//////////// INITIALIZATION METHODS ///////////////////////////////////////
|
|
|
|
/*=====
|
|
constructor: function(params, srcNodeRef){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified:
|
|
//
|
|
// - use srcNodeRef.innerHTML as my contents
|
|
// - if this is a behavioral widget then apply behavior to that srcNodeRef
|
|
// - otherwise, replace srcNodeRef with my generated DOM tree
|
|
},
|
|
=====*/
|
|
|
|
postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
|
|
// summary:
|
|
// Kicks off widget instantiation. See create() for details.
|
|
// tags:
|
|
// private
|
|
this.create(params, srcNodeRef);
|
|
},
|
|
|
|
create: function(params, srcNodeRef){
|
|
// summary:
|
|
// Kick off the life-cycle of a widget
|
|
// description:
|
|
// Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
|
|
// etc.), some of which of you'll want to override. See http://dojotoolkit.org/reference-guide/dijit/_WidgetBase.html
|
|
// for a discussion of the widget creation lifecycle.
|
|
//
|
|
// Of course, adventurous developers could override create entirely, but this should
|
|
// only be done as a last resort.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified:
|
|
//
|
|
// - use srcNodeRef.innerHTML as my contents
|
|
// - if this is a behavioral widget then apply behavior to that srcNodeRef
|
|
// - otherwise, replace srcNodeRef with my generated DOM tree
|
|
// tags:
|
|
// private
|
|
|
|
// store pointer to original DOM tree
|
|
this.srcNodeRef = dom.byId(srcNodeRef);
|
|
|
|
// No longer used, remove for 2.0.
|
|
this._connects = [];
|
|
this._supportingWidgets = [];
|
|
|
|
// this is here for back-compat, remove in 2.0 (but check NodeList-instantiate.html test)
|
|
if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; }
|
|
|
|
// mix in our passed parameters
|
|
if(params){
|
|
this.params = params;
|
|
lang.mixin(this, params);
|
|
}
|
|
this.postMixInProperties();
|
|
|
|
// Generate an id for the widget if one wasn't specified, or it was specified as id: undefined.
|
|
// Do this before buildRendering() because it might expect the id to be there.
|
|
if(!this.id){
|
|
this.id = registry.getUniqueId(this.declaredClass.replace(/\./g,"_"));
|
|
if(this.params){
|
|
// if params contains {id: undefined}, prevent _applyAttributes() from processing it
|
|
delete this.params.id;
|
|
}
|
|
}
|
|
|
|
// The document and <body> node this widget is associated with
|
|
this.ownerDocument = this.ownerDocument || (this.srcNodeRef ? this.srcNodeRef.ownerDocument : win.doc);
|
|
this.ownerDocumentBody = win.body(this.ownerDocument);
|
|
|
|
registry.add(this);
|
|
|
|
this.buildRendering();
|
|
|
|
var deleteSrcNodeRef;
|
|
|
|
if(this.domNode){
|
|
// Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
|
|
// Also calls custom setters for all attributes with custom setters.
|
|
this._applyAttributes();
|
|
|
|
// If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree.
|
|
// For 2.0, move this after postCreate(). postCreate() shouldn't depend on the
|
|
// widget being attached to the DOM since it isn't when a widget is created programmatically like
|
|
// new MyWidget({}). See #11635.
|
|
var source = this.srcNodeRef;
|
|
if(source && source.parentNode && this.domNode !== source){
|
|
source.parentNode.replaceChild(this.domNode, source);
|
|
deleteSrcNodeRef = true;
|
|
}
|
|
|
|
// Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId",
|
|
// assuming that dojo._scopeName even exists in 2.0
|
|
this.domNode.setAttribute("widgetId", this.id);
|
|
}
|
|
this.postCreate();
|
|
|
|
// If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
|
|
// I think for back-compatibility it isn't deleting srcNodeRef until after postCreate() has run.
|
|
if(deleteSrcNodeRef){
|
|
delete this.srcNodeRef;
|
|
}
|
|
|
|
this._created = true;
|
|
},
|
|
|
|
_applyAttributes: function(){
|
|
// summary:
|
|
// Step during widget creation to copy widget attributes to the
|
|
// DOM according to attributeMap and _setXXXAttr objects, and also to call
|
|
// custom _setXXXAttr() methods.
|
|
//
|
|
// Skips over blank/false attribute values, unless they were explicitly specified
|
|
// as parameters to the widget, since those are the default anyway,
|
|
// and setting tabIndex="" is different than not setting tabIndex at all.
|
|
//
|
|
// For backwards-compatibility reasons attributeMap overrides _setXXXAttr when
|
|
// _setXXXAttr is a hash/string/array, but _setXXXAttr as a functions override attributeMap.
|
|
// tags:
|
|
// private
|
|
|
|
// Get list of attributes where this.set(name, value) will do something beyond
|
|
// setting this[name] = value. Specifically, attributes that have:
|
|
// - associated _setXXXAttr() method/hash/string/array
|
|
// - entries in attributeMap (remove this for 2.0);
|
|
var ctor = this.constructor,
|
|
list = ctor._setterAttrs;
|
|
if(!list){
|
|
list = (ctor._setterAttrs = []);
|
|
for(var attr in this.attributeMap){
|
|
list.push(attr);
|
|
}
|
|
|
|
var proto = ctor.prototype;
|
|
for(var fxName in proto){
|
|
if(fxName in this.attributeMap){ continue; }
|
|
var setterName = "_set" + fxName.replace(/^[a-z]|-[a-zA-Z]/g, function(c){ return c.charAt(c.length-1).toUpperCase(); }) + "Attr";
|
|
if(setterName in proto){
|
|
list.push(fxName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call this.set() for each property that was either specified as parameter to constructor,
|
|
// or is in the list found above. For correlated properties like value and displayedValue, the one
|
|
// specified as a parameter should take precedence.
|
|
// Particularly important for new DateTextBox({displayedValue: ...}) since DateTextBox's default value is
|
|
// NaN and thus is not ignored like a default value of "".
|
|
|
|
// Step 1: Save the current values of the widget properties that were specified as parameters to the constructor.
|
|
// Generally this.foo == this.params.foo, except if postMixInProperties() changed the value of this.foo.
|
|
var params = {};
|
|
for(var key in this.params || {}){
|
|
params[key] = this[key];
|
|
}
|
|
|
|
// Step 2: Call set() for each property that wasn't passed as a parameter to the constructor
|
|
array.forEach(list, function(attr){
|
|
if(attr in params){
|
|
// skip this one, do it below
|
|
}else if(this[attr]){
|
|
this.set(attr, this[attr]);
|
|
}
|
|
}, this);
|
|
|
|
// Step 3: Call set() for each property that was specified as parameter to constructor.
|
|
// Use params hash created above to ignore side effects from step #2 above.
|
|
for(key in params){
|
|
this.set(key, params[key]);
|
|
}
|
|
},
|
|
|
|
postMixInProperties: function(){
|
|
// summary:
|
|
// Called after the parameters to the widget have been read-in,
|
|
// but before the widget template is instantiated. Especially
|
|
// useful to set properties that are referenced in the widget
|
|
// template.
|
|
// tags:
|
|
// protected
|
|
},
|
|
|
|
buildRendering: function(){
|
|
// summary:
|
|
// Construct the UI for this widget, setting this.domNode.
|
|
// Most widgets will mixin `dijit._TemplatedMixin`, which implements this method.
|
|
// tags:
|
|
// protected
|
|
|
|
if(!this.domNode){
|
|
// Create root node if it wasn't created by _Templated
|
|
this.domNode = this.srcNodeRef || this.ownerDocument.createElement("div");
|
|
}
|
|
|
|
// baseClass is a single class name or occasionally a space-separated list of names.
|
|
// Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix.
|
|
// TODO: make baseClass custom setter
|
|
if(this.baseClass){
|
|
var classes = this.baseClass.split(" ");
|
|
if(!this.isLeftToRight()){
|
|
classes = classes.concat( array.map(classes, function(name){ return name+"Rtl"; }));
|
|
}
|
|
domClass.add(this.domNode, classes);
|
|
}
|
|
},
|
|
|
|
postCreate: function(){
|
|
// summary:
|
|
// Processing after the DOM fragment is created
|
|
// description:
|
|
// Called after the DOM fragment has been created, but not necessarily
|
|
// added to the document. Do not include any operations which rely on
|
|
// node dimensions or placement.
|
|
// tags:
|
|
// protected
|
|
},
|
|
|
|
startup: function(){
|
|
// summary:
|
|
// Processing after the DOM fragment is added to the document
|
|
// description:
|
|
// Called after a widget and its children have been created and added to the page,
|
|
// and all related widgets have finished their create() cycle, up through postCreate().
|
|
// This is useful for composite widgets that need to control or layout sub-widgets.
|
|
// Many layout widgets can use this as a wiring phase.
|
|
if(this._started){ return; }
|
|
this._started = true;
|
|
array.forEach(this.getChildren(), function(obj){
|
|
if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){
|
|
obj.startup();
|
|
obj._started = true;
|
|
}
|
|
});
|
|
},
|
|
|
|
//////////// DESTROY FUNCTIONS ////////////////////////////////
|
|
|
|
destroyRecursive: function(/*Boolean?*/ preserveDom){
|
|
// summary:
|
|
// Destroy this widget and its descendants
|
|
// description:
|
|
// This is the generic "destructor" function that all widget users
|
|
// should call to cleanly discard with a widget. Once a widget is
|
|
// destroyed, it is removed from the manager object.
|
|
// preserveDom:
|
|
// If true, this method will leave the original DOM structure
|
|
// alone of descendant Widgets. Note: This will NOT work with
|
|
// dijit._Templated widgets.
|
|
|
|
this._beingDestroyed = true;
|
|
this.destroyDescendants(preserveDom);
|
|
this.destroy(preserveDom);
|
|
},
|
|
|
|
destroy: function(/*Boolean*/ preserveDom){
|
|
// summary:
|
|
// Destroy this widget, but not its descendants.
|
|
// This method will, however, destroy internal widgets such as those used within a template.
|
|
// preserveDom: Boolean
|
|
// If true, this method will leave the original DOM structure alone.
|
|
// Note: This will not yet work with _Templated widgets
|
|
|
|
this._beingDestroyed = true;
|
|
this.uninitialize();
|
|
|
|
function destroy(w){
|
|
if(w.destroyRecursive){
|
|
w.destroyRecursive(preserveDom);
|
|
}else if(w.destroy){
|
|
w.destroy(preserveDom);
|
|
}
|
|
}
|
|
|
|
// Back-compat, remove for 2.0
|
|
array.forEach(this._connects, lang.hitch(this, "disconnect"));
|
|
array.forEach(this._supportingWidgets, destroy);
|
|
|
|
// Destroy supporting widgets, but not child widgets under this.containerNode (for 2.0, destroy child widgets
|
|
// here too). if() statement is to guard against exception if destroy() called multiple times (see #15815).
|
|
if(this.domNode){
|
|
array.forEach(registry.findWidgets(this.domNode, this.containerNode), destroy);
|
|
}
|
|
|
|
this.destroyRendering(preserveDom);
|
|
registry.remove(this.id);
|
|
this._destroyed = true;
|
|
},
|
|
|
|
destroyRendering: function(/*Boolean?*/ preserveDom){
|
|
// summary:
|
|
// Destroys the DOM nodes associated with this widget
|
|
// preserveDom:
|
|
// If true, this method will leave the original DOM structure alone
|
|
// during tear-down. Note: this will not work with _Templated
|
|
// widgets yet.
|
|
// tags:
|
|
// protected
|
|
|
|
if(this.bgIframe){
|
|
this.bgIframe.destroy(preserveDom);
|
|
delete this.bgIframe;
|
|
}
|
|
|
|
if(this.domNode){
|
|
if(preserveDom){
|
|
domAttr.remove(this.domNode, "widgetId");
|
|
}else{
|
|
domConstruct.destroy(this.domNode);
|
|
}
|
|
delete this.domNode;
|
|
}
|
|
|
|
if(this.srcNodeRef){
|
|
if(!preserveDom){
|
|
domConstruct.destroy(this.srcNodeRef);
|
|
}
|
|
delete this.srcNodeRef;
|
|
}
|
|
},
|
|
|
|
destroyDescendants: function(/*Boolean?*/ preserveDom){
|
|
// summary:
|
|
// Recursively destroy the children of this widget and their
|
|
// descendants.
|
|
// preserveDom:
|
|
// If true, the preserveDom attribute is passed to all descendant
|
|
// widget's .destroy() method. Not for use with _Templated
|
|
// widgets.
|
|
|
|
// get all direct descendants and destroy them recursively
|
|
array.forEach(this.getChildren(), function(widget){
|
|
if(widget.destroyRecursive){
|
|
widget.destroyRecursive(preserveDom);
|
|
}
|
|
});
|
|
},
|
|
|
|
uninitialize: function(){
|
|
// summary:
|
|
// Deprecated. Override destroy() instead to implement custom widget tear-down
|
|
// behavior.
|
|
// tags:
|
|
// protected
|
|
return false;
|
|
},
|
|
|
|
////////////////// GET/SET, CUSTOM SETTERS, ETC. ///////////////////
|
|
|
|
_setStyleAttr: function(/*String||Object*/ value){
|
|
// summary:
|
|
// Sets the style attribute of the widget according to value,
|
|
// which is either a hash like {height: "5px", width: "3px"}
|
|
// or a plain string
|
|
// description:
|
|
// Determines which node to set the style on based on style setting
|
|
// in attributeMap.
|
|
// tags:
|
|
// protected
|
|
|
|
var mapNode = this.domNode;
|
|
|
|
// Note: technically we should revert any style setting made in a previous call
|
|
// to his method, but that's difficult to keep track of.
|
|
|
|
if(lang.isObject(value)){
|
|
domStyle.set(mapNode, value);
|
|
}else{
|
|
if(mapNode.style.cssText){
|
|
mapNode.style.cssText += "; " + value;
|
|
}else{
|
|
mapNode.style.cssText = value;
|
|
}
|
|
}
|
|
|
|
this._set("style", value);
|
|
},
|
|
|
|
_attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){
|
|
// summary:
|
|
// Reflect a widget attribute (title, tabIndex, duration etc.) to
|
|
// the widget DOM, as specified by commands parameter.
|
|
// If commands isn't specified then it's looked up from attributeMap.
|
|
// Note some attributes like "type"
|
|
// cannot be processed this way as they are not mutable.
|
|
// attr:
|
|
// Name of member variable (ex: "focusNode" maps to this.focusNode) pointing
|
|
// to DOMNode inside the widget, or alternately pointing to a subwidget
|
|
// tags:
|
|
// private
|
|
|
|
commands = arguments.length >= 3 ? commands : this.attributeMap[attr];
|
|
|
|
array.forEach(lang.isArray(commands) ? commands : [commands], function(command){
|
|
|
|
// Get target node and what we are doing to that node
|
|
var mapNode = this[command.node || command || "domNode"]; // DOM node
|
|
var type = command.type || "attribute"; // class, innerHTML, innerText, or attribute
|
|
|
|
switch(type){
|
|
case "attribute":
|
|
if(lang.isFunction(value)){ // functions execute in the context of the widget
|
|
value = lang.hitch(this, value);
|
|
}
|
|
|
|
// Get the name of the DOM node attribute; usually it's the same
|
|
// as the name of the attribute in the widget (attr), but can be overridden.
|
|
// Also maps handler names to lowercase, like onSubmit --> onsubmit
|
|
var attrName = command.attribute ? command.attribute :
|
|
(/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr);
|
|
|
|
if(mapNode.tagName){
|
|
// Normal case, mapping to a DOMNode. Note that modern browsers will have a mapNode.set()
|
|
// method, but for consistency we still call domAttr
|
|
domAttr.set(mapNode, attrName, value);
|
|
}else{
|
|
// mapping to a sub-widget
|
|
mapNode.set(attrName, value);
|
|
}
|
|
break;
|
|
case "innerText":
|
|
mapNode.innerHTML = "";
|
|
mapNode.appendChild(this.ownerDocument.createTextNode(value));
|
|
break;
|
|
case "innerHTML":
|
|
mapNode.innerHTML = value;
|
|
break;
|
|
case "class":
|
|
domClass.replace(mapNode, value, this[attr]);
|
|
break;
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
get: function(name){
|
|
// summary:
|
|
// Get a property from a widget.
|
|
// name:
|
|
// The property to get.
|
|
// description:
|
|
// Get a named property from a widget. The property may
|
|
// potentially be retrieved via a getter method. If no getter is defined, this
|
|
// just retrieves the object's property.
|
|
//
|
|
// For example, if the widget has properties `foo` and `bar`
|
|
// and a method named `_getFooAttr()`, calling:
|
|
// `myWidget.get("foo")` would be equivalent to calling
|
|
// `widget._getFooAttr()` and `myWidget.get("bar")`
|
|
// would be equivalent to the expression
|
|
// `widget.bar2`
|
|
var names = this._getAttrNames(name);
|
|
return this[names.g] ? this[names.g]() : this[name];
|
|
},
|
|
|
|
set: function(name, value){
|
|
// summary:
|
|
// Set a property on a widget
|
|
// name:
|
|
// The property to set.
|
|
// value:
|
|
// The value to set in the property.
|
|
// description:
|
|
// Sets named properties on a widget which may potentially be handled by a
|
|
// setter in the widget.
|
|
//
|
|
// For example, if the widget has properties `foo` and `bar`
|
|
// and a method named `_setFooAttr()`, calling
|
|
// `myWidget.set("foo", "Howdy!")` would be equivalent to calling
|
|
// `widget._setFooAttr("Howdy!")` and `myWidget.set("bar", 3)`
|
|
// would be equivalent to the statement `widget.bar = 3;`
|
|
//
|
|
// set() may also be called with a hash of name/value pairs, ex:
|
|
//
|
|
// | myWidget.set({
|
|
// | foo: "Howdy",
|
|
// | bar: 3
|
|
// | });
|
|
//
|
|
// This is equivalent to calling `set(foo, "Howdy")` and `set(bar, 3)`
|
|
|
|
if(typeof name === "object"){
|
|
for(var x in name){
|
|
this.set(x, name[x]);
|
|
}
|
|
return this;
|
|
}
|
|
var names = this._getAttrNames(name),
|
|
setter = this[names.s];
|
|
if(lang.isFunction(setter)){
|
|
// use the explicit setter
|
|
var result = setter.apply(this, Array.prototype.slice.call(arguments, 1));
|
|
}else{
|
|
// Mapping from widget attribute to DOMNode/subwidget attribute/value/etc.
|
|
// Map according to:
|
|
// 1. attributeMap setting, if one exists (TODO: attributeMap deprecated, remove in 2.0)
|
|
// 2. _setFooAttr: {...} type attribute in the widget (if one exists)
|
|
// 3. apply to focusNode or domNode if standard attribute name, excluding funcs like onClick.
|
|
// Checks if an attribute is a "standard attribute" by whether the DOMNode JS object has a similar
|
|
// attribute name (ex: accept-charset attribute matches jsObject.acceptCharset).
|
|
// Note also that Tree.focusNode() is a function not a DOMNode, so test for that.
|
|
var defaultNode = this.focusNode && !lang.isFunction(this.focusNode) ? "focusNode" : "domNode",
|
|
tag = this[defaultNode].tagName,
|
|
attrsForTag = tagAttrs[tag] || (tagAttrs[tag] = getAttrs(this[defaultNode])),
|
|
map = name in this.attributeMap ? this.attributeMap[name] :
|
|
names.s in this ? this[names.s] :
|
|
((names.l in attrsForTag && typeof value != "function") ||
|
|
/^aria-|^data-|^role$/.test(name)) ? defaultNode : null;
|
|
if(map != null){
|
|
this._attrToDom(name, value, map);
|
|
}
|
|
this._set(name, value);
|
|
}
|
|
return result || this;
|
|
},
|
|
|
|
_attrPairNames: {}, // shared between all widgets
|
|
_getAttrNames: function(name){
|
|
// summary:
|
|
// Helper function for get() and set().
|
|
// Caches attribute name values so we don't do the string ops every time.
|
|
// tags:
|
|
// private
|
|
|
|
var apn = this._attrPairNames;
|
|
if(apn[name]){ return apn[name]; }
|
|
var uc = name.replace(/^[a-z]|-[a-zA-Z]/g, function(c){ return c.charAt(c.length-1).toUpperCase(); });
|
|
return (apn[name] = {
|
|
n: name+"Node",
|
|
s: "_set"+uc+"Attr", // converts dashes to camel case, ex: accept-charset --> _setAcceptCharsetAttr
|
|
g: "_get"+uc+"Attr",
|
|
l: uc.toLowerCase() // lowercase name w/out dashes, ex: acceptcharset
|
|
});
|
|
},
|
|
|
|
_set: function(/*String*/ name, /*anything*/ value){
|
|
// summary:
|
|
// Helper function to set new value for specified attribute, and call handlers
|
|
// registered with watch() if the value has changed.
|
|
var oldValue = this[name];
|
|
this[name] = value;
|
|
if(this._created && value !== oldValue){
|
|
if(this._watchCallbacks){
|
|
this._watchCallbacks(name, oldValue, value);
|
|
}
|
|
this.emit("attrmodified-" + name, {
|
|
detail: {
|
|
prevValue: oldValue,
|
|
newValue: value
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
emit: function(/*String*/ type, /*Object?*/ eventObj, /*Array?*/ callbackArgs){
|
|
// summary:
|
|
// Used by widgets to signal that a synthetic event occurred, ex:
|
|
// | myWidget.emit("attrmodified-selectedChildWidget", {}).
|
|
//
|
|
// Emits an event on this.domNode named type.toLowerCase(), based on eventObj.
|
|
// Also calls onType() method, if present, and returns value from that method.
|
|
// By default passes eventObj to callback, but will pass callbackArgs instead, if specified.
|
|
// Modifies eventObj by adding missing parameters (bubbles, cancelable, widget).
|
|
// tags:
|
|
// protected
|
|
|
|
// Specify fallback values for bubbles, cancelable in case they are not set in eventObj.
|
|
// Also set pointer to widget, although since we can't add a pointer to the widget for native events
|
|
// (see #14729), maybe we shouldn't do it here?
|
|
eventObj = eventObj || {};
|
|
if(eventObj.bubbles === undefined){ eventObj.bubbles = true; }
|
|
if(eventObj.cancelable === undefined){ eventObj.cancelable = true; }
|
|
if(!eventObj.detail){ eventObj.detail = {}; }
|
|
eventObj.detail.widget = this;
|
|
|
|
var ret, callback = this["on"+type];
|
|
if(callback){
|
|
ret = callback.apply(this, callbackArgs ? callbackArgs : [eventObj]);
|
|
}
|
|
|
|
// Emit event, but avoid spurious emit()'s as parent sets properties on child during startup/destroy
|
|
if(this._started && !this._beingDestroyed){
|
|
on.emit(this.domNode, type.toLowerCase(), eventObj);
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
on: function(/*String|Function*/ type, /*Function*/ func){
|
|
// summary:
|
|
// Call specified function when event occurs, ex: myWidget.on("click", function(){ ... }).
|
|
// type:
|
|
// Name of event (ex: "click") or extension event like touch.press.
|
|
// description:
|
|
// Call specified function when event `type` occurs, ex: `myWidget.on("click", function(){ ... })`.
|
|
// Note that the function is not run in any particular scope, so if (for example) you want it to run in the
|
|
// widget's scope you must do `myWidget.on("click", lang.hitch(myWidget, func))`.
|
|
|
|
// For backwards compatibility, if there's an onType() method in the widget then connect to that.
|
|
// Remove in 2.0.
|
|
var widgetMethod = this._onMap(type);
|
|
if(widgetMethod){
|
|
return aspect.after(this, widgetMethod, func, true);
|
|
}
|
|
|
|
// Otherwise, just listen for the event on this.domNode.
|
|
return this.own(on(this.domNode, type, func))[0];
|
|
},
|
|
|
|
_onMap: function(/*String|Function*/ type){
|
|
// summary:
|
|
// Maps on() type parameter (ex: "mousemove") to method name (ex: "onMouseMove").
|
|
// If type is a synthetic event like touch.press then returns undefined.
|
|
var ctor = this.constructor, map = ctor._onMap;
|
|
if(!map){
|
|
map = (ctor._onMap = {});
|
|
for(var attr in ctor.prototype){
|
|
if(/^on/.test(attr)){
|
|
map[attr.replace(/^on/, "").toLowerCase()] = attr;
|
|
}
|
|
}
|
|
}
|
|
return map[typeof type == "string" && type.toLowerCase()]; // String
|
|
},
|
|
|
|
toString: function(){
|
|
// summary:
|
|
// Returns a string that represents the widget
|
|
// description:
|
|
// When a widget is cast to a string, this method will be used to generate the
|
|
// output. Currently, it does not implement any sort of reversible
|
|
// serialization.
|
|
return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String
|
|
},
|
|
|
|
getChildren: function(){
|
|
// summary:
|
|
// Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
|
|
// Does not return nested widgets, nor widgets that are part of this widget's template.
|
|
return this.containerNode ? registry.findWidgets(this.containerNode) : []; // dijit/_WidgetBase[]
|
|
},
|
|
|
|
getParent: function(){
|
|
// summary:
|
|
// Returns the parent widget of this widget
|
|
return registry.getEnclosingWidget(this.domNode.parentNode);
|
|
},
|
|
|
|
connect: function(
|
|
/*Object|null*/ obj,
|
|
/*String|Function*/ event,
|
|
/*String|Function*/ method){
|
|
// summary:
|
|
// Deprecated, will be removed in 2.0, use this.own(on(...)) or this.own(aspect.after(...)) instead.
|
|
//
|
|
// Connects specified obj/event to specified method of this object
|
|
// and registers for disconnect() on widget destroy.
|
|
//
|
|
// Provide widget-specific analog to dojo.connect, except with the
|
|
// implicit use of this widget as the target object.
|
|
// Events connected with `this.connect` are disconnected upon
|
|
// destruction.
|
|
// returns:
|
|
// A handle that can be passed to `disconnect` in order to disconnect before
|
|
// the widget is destroyed.
|
|
// example:
|
|
// | var btn = new Button();
|
|
// | // when foo.bar() is called, call the listener we're going to
|
|
// | // provide in the scope of btn
|
|
// | btn.connect(foo, "bar", function(){
|
|
// | console.debug(this.toString());
|
|
// | });
|
|
// tags:
|
|
// protected
|
|
|
|
return this.own(connect.connect(obj, event, this, method))[0]; // handle
|
|
},
|
|
|
|
disconnect: function(handle){
|
|
// summary:
|
|
// Deprecated, will be removed in 2.0, use handle.remove() instead.
|
|
//
|
|
// Disconnects handle created by `connect`.
|
|
// tags:
|
|
// protected
|
|
|
|
handle.remove();
|
|
},
|
|
|
|
subscribe: function(t, method){
|
|
// summary:
|
|
// Deprecated, will be removed in 2.0, use this.own(topic.subscribe()) instead.
|
|
//
|
|
// Subscribes to the specified topic and calls the specified method
|
|
// of this object and registers for unsubscribe() on widget destroy.
|
|
//
|
|
// Provide widget-specific analog to dojo.subscribe, except with the
|
|
// implicit use of this widget as the target object.
|
|
// t: String
|
|
// The topic
|
|
// method: Function
|
|
// The callback
|
|
// example:
|
|
// | var btn = new Button();
|
|
// | // when /my/topic is published, this button changes its label to
|
|
// | // be the parameter of the topic.
|
|
// | btn.subscribe("/my/topic", function(v){
|
|
// | this.set("label", v);
|
|
// | });
|
|
// tags:
|
|
// protected
|
|
return this.own(topic.subscribe(t, lang.hitch(this, method)))[0]; // handle
|
|
},
|
|
|
|
unsubscribe: function(/*Object*/ handle){
|
|
// summary:
|
|
// Deprecated, will be removed in 2.0, use handle.remove() instead.
|
|
//
|
|
// Unsubscribes handle created by this.subscribe.
|
|
// Also removes handle from this widget's list of subscriptions
|
|
// tags:
|
|
// protected
|
|
|
|
handle.remove();
|
|
},
|
|
|
|
isLeftToRight: function(){
|
|
// summary:
|
|
// Return this widget's explicit or implicit orientation (true for LTR, false for RTL)
|
|
// tags:
|
|
// protected
|
|
return this.dir ? (this.dir == "ltr") : domGeometry.isBodyLtr(this.ownerDocument); //Boolean
|
|
},
|
|
|
|
isFocusable: function(){
|
|
// summary:
|
|
// Return true if this widget can currently be focused
|
|
// and false if not
|
|
return this.focus && (domStyle.get(this.domNode, "display") != "none");
|
|
},
|
|
|
|
placeAt: function(/* String|DomNode|_Widget */ reference, /* String|Int? */ position){
|
|
// summary:
|
|
// Place this widget somewhere in the DOM based
|
|
// on standard domConstruct.place() conventions.
|
|
// description:
|
|
// A convenience function provided in all _Widgets, providing a simple
|
|
// shorthand mechanism to put an existing (or newly created) Widget
|
|
// somewhere in the dom, and allow chaining.
|
|
// reference:
|
|
// Widget, DOMNode, or id of widget or DOMNode
|
|
// position:
|
|
// If reference is a widget (or id of widget), and that widget has an ".addChild" method,
|
|
// it will be called passing this widget instance into that method, supplying the optional
|
|
// position index passed. In this case position (if specified) should be an integer.
|
|
//
|
|
// If reference is a DOMNode (or id matching a DOMNode but not a widget),
|
|
// the position argument can be a numeric index or a string
|
|
// "first", "last", "before", or "after", same as dojo/dom-construct::place().
|
|
// returns: dijit/_WidgetBase
|
|
// Provides a useful return of the newly created dijit._Widget instance so you
|
|
// can "chain" this function by instantiating, placing, then saving the return value
|
|
// to a variable.
|
|
// example:
|
|
// | // create a Button with no srcNodeRef, and place it in the body:
|
|
// | var button = new Button({ label:"click" }).placeAt(win.body());
|
|
// | // now, 'button' is still the widget reference to the newly created button
|
|
// | button.on("click", function(e){ console.log('click'); }));
|
|
// example:
|
|
// | // create a button out of a node with id="src" and append it to id="wrapper":
|
|
// | var button = new Button({},"src").placeAt("wrapper");
|
|
// example:
|
|
// | // place a new button as the first element of some div
|
|
// | var button = new Button({ label:"click" }).placeAt("wrapper","first");
|
|
// example:
|
|
// | // create a contentpane and add it to a TabContainer
|
|
// | var tc = dijit.byId("myTabs");
|
|
// | new ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
|
|
|
|
var refWidget = !reference.tagName && registry.byId(reference);
|
|
if(refWidget && refWidget.addChild && (!position || typeof position === "number")){
|
|
// Adding this to refWidget and can use refWidget.addChild() to handle everything.
|
|
refWidget.addChild(this, position);
|
|
}else{
|
|
// "reference" is a plain DOMNode, or we can't use refWidget.addChild(). Use domConstruct.place() and
|
|
// target refWidget.containerNode for nested placement (position==number, "first", "last", "only"), and
|
|
// refWidget.domNode otherwise ("after"/"before"/"replace"). (But not supported officially, see #14946.)
|
|
var ref = refWidget ?
|
|
(refWidget.containerNode && !/after|before|replace/.test(position||"") ?
|
|
refWidget.containerNode : refWidget.domNode) : dom.byId(reference, this.ownerDocument);
|
|
domConstruct.place(this.domNode, ref, position);
|
|
|
|
// Start this iff it has a parent widget that's already started.
|
|
if(!this._started && (this.getParent() || {})._started){
|
|
this.startup();
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
getTextDir: function(/*String*/ text,/*String*/ originalDir){
|
|
// summary:
|
|
// Return direction of the text.
|
|
// The function overridden in the _BidiSupport module,
|
|
// its main purpose is to calculate the direction of the
|
|
// text, if was defined by the programmer through textDir.
|
|
// tags:
|
|
// protected.
|
|
return originalDir;
|
|
},
|
|
|
|
applyTextDir: function(/*===== element, text =====*/){
|
|
// summary:
|
|
// The function overridden in the _BidiSupport module,
|
|
// originally used for setting element.dir according to this.textDir.
|
|
// In this case does nothing.
|
|
// element: DOMNode
|
|
// text: String
|
|
// tags:
|
|
// protected.
|
|
},
|
|
|
|
defer: function(fcn, delay){
|
|
// summary:
|
|
// Wrapper to setTimeout to avoid deferred functions executing
|
|
// after the originating widget has been destroyed.
|
|
// Returns an object handle with a remove method (that returns null) (replaces clearTimeout).
|
|
// fcn: function reference
|
|
// delay: Optional number (defaults to 0)
|
|
// tags:
|
|
// protected.
|
|
var timer = setTimeout(lang.hitch(this,
|
|
function(){
|
|
timer = null;
|
|
if(!this._destroyed){
|
|
lang.hitch(this, fcn)();
|
|
}
|
|
}),
|
|
delay || 0
|
|
);
|
|
return {
|
|
remove: function(){
|
|
if(timer){
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
return null; // so this works well: handle = handle.remove();
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/layout/_TabContainerBase':function(){
|
|
require({cache:{
|
|
'url:dijit/layout/templates/TabContainer.html':"<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" data-dojo-attach-point=\"tablistNode\"></div>\n\t<div data-dojo-attach-point=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" data-dojo-attach-point=\"containerNode\"></div>\n</div>\n"}});
|
|
define("dijit/layout/_TabContainerBase", [
|
|
"dojo/text!./templates/TabContainer.html",
|
|
"./StackContainer",
|
|
"./utils", // marginBox2contextBox, layoutChildren
|
|
"../_TemplatedMixin",
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.add
|
|
"dojo/dom-geometry", // domGeometry.contentBox
|
|
"dojo/dom-style" // domStyle.style
|
|
], function(template, StackContainer, layoutUtils, _TemplatedMixin, declare, domClass, domGeometry, domStyle){
|
|
|
|
// module:
|
|
// dijit/layout/_TabContainerBase
|
|
|
|
|
|
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/_WidgetBase*/ 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);
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/Form':function(){
|
|
define("dijit/form/Form", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/sniff", // has("ie")
|
|
"../_Widget",
|
|
"../_TemplatedMixin",
|
|
"./_FormMixin",
|
|
"../layout/_ContentPaneResizeMixin"
|
|
], function(declare, domAttr, event, kernel, has, _Widget, _TemplatedMixin, _FormMixin, _ContentPaneResizeMixin){
|
|
|
|
// module:
|
|
// dijit/form/Form
|
|
|
|
|
|
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();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/store/Memory':function(){
|
|
define("dojo/store/Memory", ["../_base/declare", "./util/QueryResults", "./util/SimpleQueryEngine" /*=====, "./api/Store" =====*/],
|
|
function(declare, QueryResults, SimpleQueryEngine /*=====, Store =====*/){
|
|
|
|
// module:
|
|
// dojo/store/Memory
|
|
|
|
// No base class, but for purposes of documentation, the base class is dojo/store/api/Store
|
|
var base = null;
|
|
/*===== base = Store; =====*/
|
|
|
|
return declare("dojo.store.Memory", base, {
|
|
// summary:
|
|
// This is a basic in-memory object store. It implements dojo/store/api/Store.
|
|
constructor: function(options){
|
|
// summary:
|
|
// Creates a memory object store.
|
|
// options: dojo/store/Memory
|
|
// 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 = object[idProperty] = (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 Memory({
|
|
// | data: [
|
|
// | {id: 1, name: "one", prime: false },
|
|
// | {id: 2, name: "two", even: true, prime: true},
|
|
// | {id: 3, name: "three", prime: true},
|
|
// | {id: 4, name: "four", even: true, prime: false},
|
|
// | {id: 5, name: "five", prime: true}
|
|
// | ]
|
|
// | });
|
|
//
|
|
// ...find all items where "prime" is true:
|
|
//
|
|
// | var results = store.query({ prime: true });
|
|
//
|
|
// ...or find all items where "even" is true:
|
|
//
|
|
// | var results = store.query({ even: true });
|
|
return QueryResults(this.queryEngine(query, options)(this.data));
|
|
},
|
|
setData: function(data){
|
|
// summary:
|
|
// Sets the given data as the source for this store, and indexes it
|
|
// data: Object[]
|
|
// An array of objects to use as the source of data.
|
|
if(data.items){
|
|
// just for convenience with the data format IFRS expects
|
|
this.idProperty = data.identifier;
|
|
data = this.data = data.items;
|
|
}else{
|
|
this.data = data;
|
|
}
|
|
this.index = {};
|
|
for(var i = 0, l = data.length; i < l; i++){
|
|
this.index[data[i][this.idProperty]] = i;
|
|
}
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'url:dijit/templates/Tooltip.html':"<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\"\n\t><div class=\"dijitTooltipContainer dijitTooltipContents\" data-dojo-attach-point=\"containerNode\" role='alert'></div\n\t><div class=\"dijitTooltipConnector\" data-dojo-attach-point=\"connectorNode\"></div\n></div>\n",
|
|
'dijit/_base/sniff':function(){
|
|
define("dijit/_base/sniff", [ "dojo/uacss" ], function(){
|
|
|
|
// module:
|
|
// dijit/_base/sniff
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// Deprecated, back compatibility module, new code should require dojo/uacss directly instead of this module.
|
|
};
|
|
=====*/
|
|
});
|
|
|
|
},
|
|
'dijit/Toolbar':function(){
|
|
define("dijit/Toolbar", [
|
|
"require",
|
|
"dojo/_base/declare", // declare
|
|
"dojo/has",
|
|
"dojo/keys", // keys.LEFT_ARROW keys.RIGHT_ARROW
|
|
"dojo/ready",
|
|
"./_Widget",
|
|
"./_KeyNavContainer",
|
|
"./_TemplatedMixin"
|
|
], function(require, declare, has, keys, ready, _Widget, _KeyNavContainer, _TemplatedMixin){
|
|
|
|
// module:
|
|
// dijit/Toolbar
|
|
|
|
|
|
// Back compat w/1.6, remove for 2.0
|
|
if(has("dijit-legacy-requires")){
|
|
ready(0, function(){
|
|
var requires = ["dijit/ToolbarSeparator"];
|
|
require(requires); // use indirection so modules not rolled into a build
|
|
});
|
|
}
|
|
|
|
return declare("dijit.Toolbar", [_Widget, _TemplatedMixin, _KeyNavContainer], {
|
|
// summary:
|
|
// A Toolbar widget, used to hold things like `dijit.Editor` buttons
|
|
|
|
templateString:
|
|
'<div class="dijit" role="toolbar" tabIndex="${tabIndex}" data-dojo-attach-point="containerNode">' +
|
|
'</div>',
|
|
|
|
baseClass: "dijitToolbar",
|
|
|
|
postCreate: function(){
|
|
this.inherited(arguments);
|
|
|
|
this.connectKeyNavHandlers(
|
|
this.isLeftToRight() ? [keys.LEFT_ARROW] : [keys.RIGHT_ARROW],
|
|
this.isLeftToRight() ? [keys.RIGHT_ARROW] : [keys.LEFT_ARROW]
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/layout/StackContainer':function(){
|
|
define("dijit/layout/StackContainer", [
|
|
"dojo/_base/array", // array.forEach array.indexOf array.some
|
|
"dojo/cookie", // cookie
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.add domClass.replace
|
|
"dojo/has", // has("dijit-legacy-requires")
|
|
"dojo/_base/lang", // lang.extend
|
|
"dojo/ready",
|
|
"dojo/topic", // publish
|
|
"../registry", // registry.byId
|
|
"../_WidgetBase",
|
|
"./_LayoutWidget",
|
|
"dojo/i18n!../nls/common"
|
|
], function(array, cookie, declare, domClass, has, lang, ready, topic,
|
|
registry, _WidgetBase, _LayoutWidget){
|
|
|
|
// module:
|
|
// dijit/layout/StackContainer
|
|
|
|
// Back compat w/1.6, remove for 2.0
|
|
if(has("dijit-legacy-requires")){
|
|
ready(0, function(){
|
|
var requires = ["dijit/layout/StackController"];
|
|
require(requires); // use indirection so modules not rolled into a build
|
|
});
|
|
}
|
|
|
|
var StackContainer = 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.
|
|
//
|
|
// See `StackContainer.ChildWidgetProperties` for details on the properties that can be set on
|
|
// children of a `StackContainer`.
|
|
|
|
// 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(){
|
|
// Overrides _LayoutWidget.resize()
|
|
// 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/_WidgetBase*/ 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/_WidgetBase*/ 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/_WidgetBase*/ 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/_WidgetBase|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/_WidgetBase
|
|
// The newly selected widget.
|
|
// oldWidget: dijit/_WidgetBase
|
|
// 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.
|
|
|
|
// TODO: remove for 2.0 if this isn't being used. Otherwise, fix to skip disabled tabs.
|
|
|
|
var children = this.getChildren();
|
|
var index = array.indexOf(children, this.selectedChildWidget);
|
|
index += forward ? 1 : children.length - 1;
|
|
return children[ index % children.length ]; // dijit/_WidgetBase
|
|
},
|
|
|
|
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/_WidgetBase*/ 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/_WidgetBase*/ 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/_WidgetBase*/ 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;
|
|
}
|
|
});
|
|
|
|
StackContainer.ChildWidgetProperties = {
|
|
// summary:
|
|
// These properties can be specified for the children of a StackContainer.
|
|
|
|
// selected: Boolean
|
|
// Specifies that this widget should be the initially displayed pane.
|
|
// Note: to change the selected child use `dijit/layout/StackContainer.selectChild`
|
|
selected: false,
|
|
|
|
// disabled: Boolean
|
|
// Specifies that the button to select this pane should be disabled.
|
|
// Doesn't affect programmatic selection of the pane, nor does it deselect the pane if it is currently selected.
|
|
disabled: false,
|
|
|
|
// closable: Boolean
|
|
// True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
|
|
closable: false,
|
|
|
|
// iconClass: String
|
|
// CSS Class specifying icon to use in label associated with this pane.
|
|
iconClass: "dijitNoIcon",
|
|
|
|
// showTitle: Boolean
|
|
// When true, display title of this widget as tab label etc., rather than just using
|
|
// icon specified in iconClass
|
|
showTitle: true
|
|
};
|
|
|
|
// 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.)
|
|
// This is for the benefit of the parser. Remove for 2.0. Also, hide from doc viewer.
|
|
lang.extend(_WidgetBase, /*===== {} || =====*/ StackContainer.ChildWidgetProperties);
|
|
|
|
return StackContainer;
|
|
});
|
|
|
|
},
|
|
'dojo/regexp':function(){
|
|
define("dojo/regexp", ["./_base/kernel", "./_base/lang"], function(dojo, lang){
|
|
|
|
// module:
|
|
// dojo/regexp
|
|
|
|
var regexp = {
|
|
// summary:
|
|
// Regular expressions and Builder resources
|
|
};
|
|
lang.setObject("dojo.regexp", regexp);
|
|
|
|
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
|
|
};
|
|
|
|
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 regexp.group(b.join("|"), nonCapture); // String
|
|
};
|
|
|
|
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 regexp;
|
|
});
|
|
|
|
},
|
|
'dijit/DropDownMenu':function(){
|
|
require({cache:{
|
|
'url:dijit/templates/Menu.html':"<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" role=\"menu\" tabIndex=\"${tabIndex}\"\n\t data-dojo-attach-event=\"onkeypress:_onKeyPress\" cellspacing=\"0\">\n\t<tbody class=\"dijitReset\" data-dojo-attach-point=\"containerNode\"></tbody>\n</table>\n"}});
|
|
define("dijit/DropDownMenu", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/keys", // keys
|
|
"dojo/text!./templates/Menu.html",
|
|
"./_OnDijitClickMixin",
|
|
"./_MenuBase"
|
|
], function(declare, event, keys, template, _OnDijitClickMixin, _MenuBase){
|
|
|
|
// module:
|
|
// dijit/DropDownMenu
|
|
|
|
return declare("dijit.DropDownMenu", [_MenuBase, _OnDijitClickMixin], {
|
|
// summary:
|
|
// A menu, without features for context menu (Meaning, drop down menu)
|
|
|
|
templateString: template,
|
|
|
|
baseClass: "dijitMenu",
|
|
|
|
postCreate: function(){
|
|
this.inherited(arguments);
|
|
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/form/_FormMixin':function(){
|
|
define("dijit/form/_FormMixin", [
|
|
"dojo/_base/array", // array.every array.filter array.forEach array.indexOf array.map
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/_base/lang", // lang.hitch lang.isArray
|
|
"dojo/on",
|
|
"dojo/window" // winUtils.scrollIntoView
|
|
], function(array, declare, kernel, lang, on, winUtils){
|
|
|
|
// module:
|
|
// dijit/form/_FormMixin
|
|
|
|
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 defer(...) 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:
|
|
// Deprecated method. Applications no longer need to call this. Remove for 2.0.
|
|
},
|
|
|
|
connectChildren: function(/*Boolean*/ inStartup){
|
|
// summary:
|
|
// 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.
|
|
|
|
// TODO: rename for 2.0
|
|
|
|
this._descendants = this._getDescendantFormWidgets();
|
|
|
|
// To get notifications from children they need to be started. Children didn't used to need to be started,
|
|
// so for back-compat, start them here
|
|
array.forEach(this._descendants, function(child){
|
|
if(!child._started){ child.startup(); }
|
|
});
|
|
|
|
if(!inStartup){
|
|
this._onChildChange();
|
|
}
|
|
},
|
|
|
|
_onChildChange: function(/*String*/ attr){
|
|
// summary:
|
|
// Called when child's value or disabled state changes
|
|
|
|
// The unit tests expect state update to be synchronous, so update it immediately.
|
|
if(!attr || attr == "state" || attr == "disabled"){
|
|
this._set("state", this._getState());
|
|
}
|
|
|
|
// Use defer() 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(!attr || attr == "value" || attr == "disabled" || attr == "checked"){
|
|
if(this._onChangeDelayTimer){
|
|
this._onChangeDelayTimer.remove();
|
|
}
|
|
this._onChangeDelayTimer = this.defer(function(){
|
|
delete this._onChangeDelayTimer;
|
|
this._set("value", this.get("value"));
|
|
}, 10);
|
|
}
|
|
},
|
|
|
|
startup: function(){
|
|
this.inherited(arguments);
|
|
|
|
// Set initial this.value and this.state. Don't emit watch() notifications.
|
|
this._descendants = this._getDescendantFormWidgets();
|
|
this.value = this.get("value");
|
|
this.state = this._getState();
|
|
|
|
// Initialize value and valid/invalid state tracking.
|
|
var self = this;
|
|
this.own(
|
|
on(
|
|
this.containerNode,
|
|
"attrmodified-state, attrmodified-disabled, attrmodified-value, attrmodified-checked",
|
|
function(evt){
|
|
if(evt.target == self.domNode){
|
|
return; // ignore events that I fire on myself because my children changed
|
|
}
|
|
self._onChildChange(evt.type.replace("attrmodified-", ""));
|
|
}
|
|
)
|
|
);
|
|
|
|
// Make state change call onValidStateChange(), will be removed in 2.0
|
|
this.watch("state", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); });
|
|
},
|
|
|
|
destroy: function(){
|
|
this.inherited(arguments);
|
|
}
|
|
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/data/util/simpleFetch':function(){
|
|
define("dojo/data/util/simpleFetch", ["../../_base/lang", "../../_base/kernel", "./sorter"],
|
|
function(lang, kernel, sorter){
|
|
// module:
|
|
// dojo/data/util/simpleFetch
|
|
// summary:
|
|
// The simpleFetch mixin is designed to serve as a set of function(s) that can
|
|
// be mixed into other datastore implementations to accelerate their development.
|
|
|
|
var simpleFetch = {};
|
|
lang.setObject("dojo.data.util.simpleFetch", simpleFetch);
|
|
|
|
simpleFetch.errorHandler = function(/*Object*/ errorData, /*Object*/ requestObject){
|
|
// summary:
|
|
// The error handler when there is an error fetching items. This function should not be called
|
|
// directly and is used by simpleFetch.fetch().
|
|
if(requestObject.onError){
|
|
var scope = requestObject.scope || kernel.global;
|
|
requestObject.onError.call(scope, errorData, requestObject);
|
|
}
|
|
};
|
|
|
|
simpleFetch.fetchHandler = function(/*Array*/ items, /*Object*/ requestObject){
|
|
// summary:
|
|
// The handler when items are sucessfully fetched. This function should not be called directly
|
|
// and is used by simpleFetch.fetch().
|
|
var oldAbortFunction = requestObject.abort || null,
|
|
aborted = false,
|
|
|
|
startIndex = requestObject.start?requestObject.start: 0,
|
|
endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length;
|
|
|
|
requestObject.abort = function(){
|
|
aborted = true;
|
|
if(oldAbortFunction){
|
|
oldAbortFunction.call(requestObject);
|
|
}
|
|
};
|
|
|
|
var scope = requestObject.scope || kernel.global;
|
|
if(!requestObject.store){
|
|
requestObject.store = this;
|
|
}
|
|
if(requestObject.onBegin){
|
|
requestObject.onBegin.call(scope, items.length, requestObject);
|
|
}
|
|
if(requestObject.sort){
|
|
items.sort(sorter.createSortFunction(requestObject.sort, this));
|
|
}
|
|
if(requestObject.onItem){
|
|
for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){
|
|
var item = items[i];
|
|
if(!aborted){
|
|
requestObject.onItem.call(scope, item, requestObject);
|
|
}
|
|
}
|
|
}
|
|
if(requestObject.onComplete && !aborted){
|
|
var subset = null;
|
|
if(!requestObject.onItem){
|
|
subset = items.slice(startIndex, endIndex);
|
|
}
|
|
requestObject.onComplete.call(scope, subset, requestObject);
|
|
}
|
|
};
|
|
|
|
simpleFetch.fetch = function(/* Object? */ request){
|
|
// summary:
|
|
// The simpleFetch mixin is designed to serve as a set of function(s) that can
|
|
// be mixed into other datastore implementations to accelerate their development.
|
|
// description:
|
|
// The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems()
|
|
// call by returning an array of all the found items that matched the query. The simpleFetch mixin
|
|
// is not designed to work for datastores that respond to a fetch() call by incrementally
|
|
// loading items, or sequentially loading partial batches of the result
|
|
// set. For datastores that mixin simpleFetch, simpleFetch
|
|
// implements a fetch method that automatically handles eight of the fetch()
|
|
// arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope
|
|
// The class mixing in simpleFetch should not implement fetch(),
|
|
// but should instead implement a _fetchItems() method. The _fetchItems()
|
|
// method takes three arguments, the keywordArgs object that was passed
|
|
// to fetch(), a callback function to be called when the result array is
|
|
// available, and an error callback to be called if something goes wrong.
|
|
// The _fetchItems() method should ignore any keywordArgs parameters for
|
|
// start, count, onBegin, onItem, onComplete, onError, sort, and scope.
|
|
// The _fetchItems() method needs to correctly handle any other keywordArgs
|
|
// parameters, including the query parameter and any optional parameters
|
|
// (such as includeChildren). The _fetchItems() method should create an array of
|
|
// result items and pass it to the fetchHandler along with the original request object --
|
|
// or, the _fetchItems() method may, if it wants to, create an new request object
|
|
// with other specifics about the request that are specific to the datastore and pass
|
|
// that as the request object to the handler.
|
|
//
|
|
// For more information on this specific function, see dojo/data/api/Read.fetch()
|
|
//
|
|
// request:
|
|
// The keywordArgs parameter may either be an instance of
|
|
// conforming to dojo/data/api/Request or may be a simple anonymous object
|
|
// that may contain any of the following:
|
|
// | {
|
|
// | query: query-object or query-string,
|
|
// | queryOptions: object,
|
|
// | onBegin: Function,
|
|
// | onItem: Function,
|
|
// | onComplete: Function,
|
|
// | onError: Function,
|
|
// | scope: object,
|
|
// | start: int
|
|
// | count: int
|
|
// | sort: array
|
|
// | }
|
|
// All implementations should accept keywordArgs objects with any of
|
|
// the 9 standard properties: query, onBegin, onItem, onComplete, onError
|
|
// scope, sort, start, and count. Some implementations may accept additional
|
|
// properties in the keywordArgs object as valid parameters, such as
|
|
// {includeOutliers:true}.
|
|
//
|
|
// ####The *query* parameter
|
|
//
|
|
// The query may be optional in some data store implementations.
|
|
// The dojo/data/api/Read API does not specify the syntax or semantics
|
|
// of the query itself -- each different data store implementation
|
|
// may have its own notion of what a query should look like.
|
|
// However, as of dojo 0.9, 1.0, and 1.1, all the provided datastores in dojo.data
|
|
// and dojox.data support an object structure query, where the object is a set of
|
|
// name/value parameters such as { attrFoo: valueBar, attrFoo1: valueBar1}. Most of the
|
|
// dijit widgets, such as ComboBox assume this to be the case when working with a datastore
|
|
// when they dynamically update the query. Therefore, for maximum compatibility with dijit
|
|
// widgets the recommended query parameter is a key/value object. That does not mean that the
|
|
// the datastore may not take alternative query forms, such as a simple string, a Date, a number,
|
|
// or a mix of such. Ultimately, The dojo/data/api/Read API is agnostic about what the query
|
|
// format.
|
|
//
|
|
// Further note: In general for query objects that accept strings as attribute
|
|
// value matches, the store should also support basic filtering capability, such as *
|
|
// (match any character) and ? (match single character). An example query that is a query object
|
|
// would be like: { attrFoo: "value*"}. Which generally means match all items where they have
|
|
// an attribute named attrFoo, with a value that starts with 'value'.
|
|
//
|
|
// ####The *queryOptions* parameter
|
|
//
|
|
// The queryOptions parameter is an optional parameter used to specify options that may modify
|
|
// the query in some fashion, such as doing a case insensitive search, or doing a deep search
|
|
// where all items in a hierarchical representation of data are scanned instead of just the root
|
|
// items. It currently defines two options that all datastores should attempt to honor if possible:
|
|
// | {
|
|
// | ignoreCase: boolean, // Whether or not the query should match case sensitively or not. Default behaviour is false.
|
|
// | deep: boolean // Whether or not a fetch should do a deep search of items and all child
|
|
// | // items instead of just root-level items in a datastore. Default is false.
|
|
// | }
|
|
//
|
|
// ####The *onBegin* parameter.
|
|
//
|
|
// function(size, request);
|
|
// If an onBegin callback function is provided, the callback function
|
|
// will be called just once, before the first onItem callback is called.
|
|
// The onBegin callback function will be passed two arguments, the
|
|
// the total number of items identified and the Request object. If the total number is
|
|
// unknown, then size will be -1. Note that size is not necessarily the size of the
|
|
// collection of items returned from the query, as the request may have specified to return only a
|
|
// subset of the total set of items through the use of the start and count parameters.
|
|
//
|
|
// ####The *onItem* parameter.
|
|
//
|
|
// function(item, request);
|
|
//
|
|
// If an onItem callback function is provided, the callback function
|
|
// will be called as each item in the result is received. The callback
|
|
// function will be passed two arguments: the item itself, and the
|
|
// Request object.
|
|
//
|
|
// ####The *onComplete* parameter.
|
|
//
|
|
// function(items, request);
|
|
//
|
|
// If an onComplete callback function is provided, the callback function
|
|
// will be called just once, after the last onItem callback is called.
|
|
// Note that if the onItem callback is not present, then onComplete will be passed
|
|
// an array containing all items which matched the query and the request object.
|
|
// If the onItem callback is present, then onComplete is called as:
|
|
// onComplete(null, request).
|
|
//
|
|
// ####The *onError* parameter.
|
|
//
|
|
// function(errorData, request);
|
|
//
|
|
// If an onError callback function is provided, the callback function
|
|
// will be called if there is any sort of error while attempting to
|
|
// execute the query.
|
|
// The onError callback function will be passed two arguments:
|
|
// an Error object and the Request object.
|
|
//
|
|
// ####The *scope* parameter.
|
|
//
|
|
// If a scope object is provided, all of the callback functions (onItem,
|
|
// onComplete, onError, etc) will be invoked in the context of the scope
|
|
// object. In the body of the callback function, the value of the "this"
|
|
// keyword will be the scope object. If no scope object is provided,
|
|
// the callback functions will be called in the context of dojo.global().
|
|
// For example, onItem.call(scope, item, request) vs.
|
|
// onItem.call(dojo.global(), item, request)
|
|
//
|
|
// ####The *start* parameter.
|
|
//
|
|
// If a start parameter is specified, this is a indication to the datastore to
|
|
// only start returning items once the start number of items have been located and
|
|
// skipped. When this parameter is paired with 'count', the store should be able
|
|
// to page across queries with millions of hits by only returning subsets of the
|
|
// hits for each query
|
|
//
|
|
// ####The *count* parameter.
|
|
//
|
|
// If a count parameter is specified, this is a indication to the datastore to
|
|
// only return up to that many items. This allows a fetch call that may have
|
|
// millions of item matches to be paired down to something reasonable.
|
|
//
|
|
// ####The *sort* parameter.
|
|
//
|
|
// If a sort parameter is specified, this is a indication to the datastore to
|
|
// sort the items in some manner before returning the items. The array is an array of
|
|
// javascript objects that must conform to the following format to be applied to the
|
|
// fetching of items:
|
|
// | {
|
|
// | attribute: attribute || attribute-name-string,
|
|
// | descending: true|false; // Optional. Default is false.
|
|
// | }
|
|
// Note that when comparing attributes, if an item contains no value for the attribute
|
|
// (undefined), then it the default ascending sort logic should push it to the bottom
|
|
// of the list. In the descending order case, it such items should appear at the top of the list.
|
|
|
|
request = request || {};
|
|
if(!request.store){
|
|
request.store = this;
|
|
}
|
|
|
|
this._fetchItems(request, lang.hitch(this, "fetchHandler"), lang.hitch(this, "errorHandler"));
|
|
return request; // Object
|
|
};
|
|
|
|
return simpleFetch;
|
|
});
|
|
|
|
},
|
|
'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/keys", // keys.F10
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/on",
|
|
"dojo/sniff", // has("ie"), has("quirks")
|
|
"dojo/_base/window", // win.body win.doc.documentElement win.doc.frames
|
|
"dojo/window", // winUtils.get
|
|
"./popup",
|
|
"./DropDownMenu",
|
|
"dojo/ready"
|
|
], function(require, array, declare, event, dom, domAttr, domGeometry, domStyle, keys, lang, on,
|
|
has, win, winUtils, pm, DropDownMenu, ready){
|
|
|
|
// module:
|
|
// dijit/Menu
|
|
|
|
// Back compat w/1.6, remove for 2.0
|
|
if(has("dijit-legacy-requires")){
|
|
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(/*===== params, srcNodeRef =====*/){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified:
|
|
//
|
|
// - use srcNodeRef.innerHTML as my contents
|
|
// - replace srcNodeRef with my generated DOM tree
|
|
|
|
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: [],
|
|
|
|
// selector: String?
|
|
// CSS expression to apply this Menu to descendants of targetNodeIds, rather than to
|
|
// the nodes specified by targetNodeIds themselves. Useful for applying a Menu to
|
|
// a range of rows in a table, tree, etc.
|
|
//
|
|
// The application must require() an appropriate level of dojo/query to handle the selector.
|
|
selector: "",
|
|
|
|
// TODO: in 2.0 remove support for multiple targetNodeIds. selector gives the same effect.
|
|
// So, change targetNodeIds to a targetNodeId: "", remove bindDomNode()/unBindDomNode(), etc.
|
|
|
|
/*=====
|
|
// currentTarget: [readonly] DOMNode
|
|
// For context menus, set to the current node that the Menu is being displayed for.
|
|
// Useful so that the menu actions can be tailored according to the node
|
|
currentTarget: null,
|
|
=====*/
|
|
|
|
// 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(this.ownerDocumentBody);
|
|
}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, this.ownerDocument);
|
|
|
|
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.body(window.document);
|
|
}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(this.ownerDocument) ? this.ownerDocument.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){
|
|
var selector = this.selector,
|
|
delegatedEvent = selector ?
|
|
function(eventType){ return on.selector(selector, eventType); } :
|
|
function(eventType){ return eventType; },
|
|
self = this;
|
|
return [
|
|
// TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu,
|
|
// rather than shift-F10?
|
|
on(cn, delegatedEvent(this.leftClickToOpen ? "click" : "contextmenu"), function(evt){
|
|
// Schedule context menu to be opened unless it's already been scheduled from onkeydown handler
|
|
event.stop(evt);
|
|
self._scheduleOpen(this, iframe, {x: evt.pageX, y: evt.pageY});
|
|
}),
|
|
on(cn, delegatedEvent("keydown"), function(evt){
|
|
if(evt.shiftKey && evt.keyCode == keys.F10){
|
|
event.stop(evt);
|
|
self._scheduleOpen(this, 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.body(window.document)
|
|
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, this.ownerDocument);
|
|
}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 = this.defer(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;
|
|
|
|
// To be used by MenuItem event handlers to tell which node the menu was opened on
|
|
this.currentTarget = target;
|
|
|
|
// 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 = domGeometry.docScroll(window.document);
|
|
|
|
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
|
|
};
|
|
},
|
|
|
|
destroy: function(){
|
|
array.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this);
|
|
this.inherited(arguments);
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/_CheckBoxMixin':function(){
|
|
define("dijit/form/_CheckBoxMixin", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/_base/event" // event.stop
|
|
], function(declare, domAttr, event){
|
|
|
|
// module:
|
|
// dijit/form/_CheckBoxMixin
|
|
|
|
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);
|
|
},
|
|
|
|
// 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,
|
|
|
|
_getSubmitValue: function(/*String*/ value){
|
|
return !value && value !== 0 ? "on" : value;
|
|
},
|
|
|
|
_setValueAttr: function(newValue){
|
|
newValue = this._getSubmitValue(newValue); // "on" to match browser native behavior when value unspecified
|
|
this._set("value", newValue);
|
|
domAttr.set(this.focusNode, "value", newValue);
|
|
},
|
|
|
|
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("dijit/layout/ContentPane", [
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/_base/lang", // lang.mixin lang.delegate lang.hitch lang.isFunction lang.isObject
|
|
"../_Widget",
|
|
"../_Container",
|
|
"./_ContentPaneResizeMixin",
|
|
"dojo/string", // string.substitute
|
|
"dojo/html", // html._ContentSetter
|
|
"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/dom-construct", // empty()
|
|
"dojo/_base/xhr", // xhr.get
|
|
"dojo/i18n", // i18n.getLocalization
|
|
"dojo/when"
|
|
], function(kernel, lang, _Widget, _Container, _ContentPaneResizeMixin, string, html, nlsLoading,
|
|
array, declare, Deferred, dom, domAttr, domConstruct, xhr, i18n, when){
|
|
|
|
// module:
|
|
// dijit/layout/ContentPane
|
|
|
|
|
|
return declare("dijit.layout.ContentPane", [_Widget, _Container, _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/_base/xhr.get|dojo._base/xhr.post
|
|
// 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.then() 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)){
|
|
srcNodeRef = dom.byId(srcNodeRef);
|
|
var df = srcNodeRef.ownerDocument.createDocumentFragment();
|
|
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);
|
|
}
|
|
},
|
|
|
|
_startChildren: function(){
|
|
// summary:
|
|
// Called when content is loaded. Calls startup on each child widget. Similar to ContentPane.startup()
|
|
// itself, but avoids marking the ContentPane itself as "restarted" (see #15581).
|
|
|
|
// This starts all the widgets
|
|
array.forEach(this.getChildren(), function(obj){
|
|
if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){
|
|
obj.startup();
|
|
obj._started = true;
|
|
}
|
|
});
|
|
|
|
// 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.then(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.then(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;
|
|
},
|
|
|
|
destroy: function(){
|
|
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.then(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)),
|
|
returnedHtml;
|
|
|
|
hand.then(
|
|
function(html){
|
|
returnedHtml = html;
|
|
try{
|
|
self._isDownloaded = true;
|
|
return self._setContent(html, false);
|
|
}catch(err){
|
|
self._onError('Content', err); // onContentError
|
|
}
|
|
},
|
|
function(err){
|
|
if(!hand.canceled){
|
|
// show error message in the pane
|
|
self._onError('Download', err); // onDownloadError
|
|
}
|
|
delete self._xhrDfd;
|
|
return err;
|
|
}
|
|
).then(function(){
|
|
self.onDownloadEnd();
|
|
delete self._xhrDfd;
|
|
return returnedHtml;
|
|
});
|
|
|
|
// 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.resolve(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){
|
|
// All widgets will hit this branch
|
|
widget.destroyRecursive(preserveDom);
|
|
}else if(widget.destroy){
|
|
// Things like dojo/dnd/Source have destroy(), not destroyRecursive()
|
|
widget.destroy(preserveDom);
|
|
}
|
|
widget._destroyed = true;
|
|
});
|
|
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._destroyed){
|
|
if(widget.destroyRecursive){
|
|
// All widgets will hit this branch
|
|
widget.destroyRecursive(preserveDom);
|
|
}else if(widget.destroy){
|
|
// Things like dojo/dnd/Source have destroy(), not destroyRecursive()
|
|
widget.destroy(preserveDom);
|
|
}
|
|
widget._destroyed = true;
|
|
}
|
|
});
|
|
delete setter.parseResults;
|
|
}
|
|
|
|
// And then clear away all the DOM nodes
|
|
if(!preserveDom){
|
|
domConstruct.empty(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
|
|
// returns:
|
|
// Returns a Deferred promise that is resolved when the content is parsed.
|
|
|
|
// 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 || {});
|
|
|
|
var p = setter.set( (lang.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams );
|
|
|
|
// dojox/layout/html/_base::_ContentSetter.set() returns a Promise that indicates when everything is completed.
|
|
// dojo/html::_ContentSetter.set() currently returns the DOMNode, but that will be changed for 2.0.
|
|
// So, if set() returns a promise then use it, otherwise fallback to waiting on setter.parseDeferred
|
|
var self = this;
|
|
return when(p && p.then ? p : setter.parseDeferred, function(){
|
|
// setter params must be pulled afresh from the ContentPane each time
|
|
delete self._contentSetterParams;
|
|
|
|
if(!isFakeContent){
|
|
if(self._started){
|
|
// Startup each top level child widget (and they will start their children, recursively)
|
|
self._startChildren();
|
|
|
|
// 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
|
|
self._scheduleLayout();
|
|
}
|
|
self._onLoadHandler(cont);
|
|
}
|
|
});
|
|
},
|
|
|
|
_onError: function(type, err, consoleText){
|
|
this.onLoadDeferred.reject(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("dijit/_KeyNavContainer", [
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"./_Container",
|
|
"./_FocusMixin",
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/keys", // keys.END keys.HOME
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/_base/lang" // lang.hitch
|
|
], function(kernel, _Container, _FocusMixin, array, keys, declare, event, domAttr, lang){
|
|
|
|
|
|
// module:
|
|
// dijit/_KeyNavContainer
|
|
|
|
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: String
|
|
// 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/_WidgetBase*/ 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/_WidgetBase*/ 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/_WidgetBase*/ 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/_WidgetBase*/ /*===== 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/_WidgetBase
|
|
},
|
|
|
|
_getLastFocusableChild: function(){
|
|
// summary:
|
|
// Returns last child that can be focused
|
|
return this._getNextFocusableChild(null, -1); // dijit/_WidgetBase
|
|
},
|
|
|
|
_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/_WidgetBase
|
|
}
|
|
child = this._getSiblingOfChild(child, dir);
|
|
}
|
|
// no focusable child found
|
|
return null; // dijit/_WidgetBase
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/layout/utils':function(){
|
|
define("dijit/layout/utils", [
|
|
"dojo/_base/array", // array.filter array.forEach
|
|
"dojo/dom-class", // domClass.add domClass.remove
|
|
"dojo/dom-geometry", // domGeometry.marginBox
|
|
"dojo/dom-style", // domStyle.getComputedStyle
|
|
"dojo/_base/lang", // lang.mixin
|
|
"../main" // for exporting symbols to dijit, remove in 2.0
|
|
], function(array, domClass, domGeometry, domStyle, lang, dijit){
|
|
|
|
// module:
|
|
// dijit/layout/utils
|
|
|
|
var layout = lang.getObject("layout", true, dijit);
|
|
/*=====
|
|
layout = {
|
|
// summary:
|
|
// marginBox2contentBox() and layoutChildren()
|
|
};
|
|
=====*/
|
|
|
|
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/_Contained':function(){
|
|
define("dijit/_Contained", [
|
|
"dojo/_base/declare", // declare
|
|
"./registry" // registry.getEnclosingWidget(), registry.byNode()
|
|
], function(declare, registry){
|
|
|
|
// module:
|
|
// dijit/_Contained
|
|
|
|
return declare("dijit._Contained", null, {
|
|
// summary:
|
|
// Mixin for widgets that are children of a container widget
|
|
//
|
|
// example:
|
|
// | // make a basic custom widget that knows about it's parents
|
|
// | declare("my.customClass",[dijit._Widget,dijit._Contained],{});
|
|
|
|
_getSibling: function(/*String*/ which){
|
|
// summary:
|
|
// Returns next or previous sibling
|
|
// which:
|
|
// Either "next" or "previous"
|
|
// tags:
|
|
// private
|
|
var node = this.domNode;
|
|
do{
|
|
node = node[which+"Sibling"];
|
|
}while(node && node.nodeType != 1);
|
|
return node && registry.byNode(node); // dijit/_WidgetBase
|
|
},
|
|
|
|
getPreviousSibling: function(){
|
|
// summary:
|
|
// Returns null if this is the first child of the parent,
|
|
// otherwise returns the next element sibling to the "left".
|
|
|
|
return this._getSibling("previous"); // dijit/_WidgetBase
|
|
},
|
|
|
|
getNextSibling: function(){
|
|
// summary:
|
|
// Returns null if this is the last child of the parent,
|
|
// otherwise returns the next element sibling to the "right".
|
|
|
|
return this._getSibling("next"); // dijit/_WidgetBase
|
|
},
|
|
|
|
getIndexInParent: function(){
|
|
// summary:
|
|
// Returns the index of this widget within its container parent.
|
|
// It returns -1 if the parent does not exist, or if the parent
|
|
// is not a dijit._Container
|
|
|
|
var p = this.getParent();
|
|
if(!p || !p.getIndexOfChild){
|
|
return -1; // int
|
|
}
|
|
return p.getIndexOfChild(this); // int
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/form/DataList':function(){
|
|
define("dijit/form/DataList", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.byId
|
|
"dojo/_base/lang", // lang.trim
|
|
"dojo/query", // query
|
|
"dojo/store/Memory",
|
|
"../registry" // registry.add registry.remove
|
|
], function(declare, dom, lang, query, MemoryStore, registry){
|
|
|
|
// module:
|
|
// dijit/form/DataList
|
|
|
|
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(params, srcNodeRef){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String
|
|
// Attach widget to this DOM node.
|
|
|
|
// 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\t<span data-dojo-attach-point=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"\n\t\t\t\trole=\"heading\" level=\"1\"></span>\n\t\t<span data-dojo-attach-point=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" data-dojo-attach-event=\"ondijitclick: onCancel\" title=\"${buttonCancel}\" role=\"button\" tabIndex=\"-1\">\n\t\t\t<span data-dojo-attach-point=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t\t</span>\n\t</div>\n\t<div data-dojo-attach-point=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n",
|
|
'dijit/form/CheckBox':function(){
|
|
require({cache:{
|
|
'url:dijit/form/templates/CheckBox.html':"<div class=\"dijit dijitReset dijitInline\" role=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" role=\"${type}\" aria-checked=\"false\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdata-dojo-attach-point=\"focusNode\"\n\t \tdata-dojo-attach-event=\"onclick:_onClick\"\n/></div>\n"}});
|
|
define("dijit/form/CheckBox", [
|
|
"require",
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/has", // has("dijit-legacy-requires")
|
|
"dojo/query", // query
|
|
"dojo/ready",
|
|
"./ToggleButton",
|
|
"./_CheckBoxMixin",
|
|
"dojo/text!./templates/CheckBox.html",
|
|
"dojo/NodeList-dom" // NodeList.addClass/removeClass
|
|
], function(require, declare, domAttr, has, query, ready, ToggleButton, _CheckBoxMixin, template){
|
|
|
|
// module:
|
|
// dijit/form/CheckBox
|
|
|
|
// Back compat w/1.6, remove for 2.0
|
|
if(has("dijit-legacy-requires")){
|
|
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.inherited(arguments);
|
|
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);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/tree/_dndSelector':function(){
|
|
define("dijit/tree/_dndSelector", [
|
|
"dojo/_base/array", // array.filter array.forEach array.map
|
|
"dojo/_base/connect", // connect.isCopyKey
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/Deferred", // Deferred
|
|
"dojo/_base/kernel", // global
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/cookie", // cookie
|
|
"dojo/mouse", // mouse.isLeft
|
|
"dojo/on",
|
|
"dojo/touch",
|
|
"./_dndContainer"
|
|
], function(array, connect, declare, Deferred, kernel, lang, cookie, mouse, on, touch, _dndContainer){
|
|
|
|
// module:
|
|
// dijit/tree/_dndSelector
|
|
|
|
|
|
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: Object
|
|
// (id to 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;
|
|
|
|
if(!this.cookieName && this.tree.id){
|
|
this.cookieName = this.tree.id + "SaveSelectedCookie";
|
|
}
|
|
|
|
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/Tree._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/Tree._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/Tree._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/Tree._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 = [], selects = [];
|
|
array.forEach(selected, function(node){
|
|
var ary = node.getTreePath(), model = this.tree.model;
|
|
nodes.push(node);
|
|
paths.push(ary);
|
|
ary = array.map(ary, function(item){
|
|
return model.getIdentity(item);
|
|
}, this);
|
|
selects.push(ary.join("/"))
|
|
}, this);
|
|
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);
|
|
if (this.tree.persist && selects.length > 0) {
|
|
cookie(this.cookieName, selects.join(","), {expires:365});
|
|
}
|
|
},
|
|
_getSavedPaths: function(){
|
|
// summary:
|
|
// Returns paths of nodes that were selected previously and saved in the cookie.
|
|
|
|
var tree = this.tree;
|
|
if(tree.persist && tree.dndController.cookieName){
|
|
var oreo, paths = [];
|
|
oreo = cookie(tree.dndController.cookieName);
|
|
if(oreo){
|
|
paths = array.map(oreo.split(","), function(path){
|
|
return path.split("/");
|
|
})
|
|
}
|
|
return paths;
|
|
}
|
|
},
|
|
// 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)){
|
|
// Prevent text selection while dragging on desktop, see #16328. But don't call preventDefault()
|
|
// for mobile because it will break things completely, see #15838.
|
|
e.preventDefault();
|
|
}else if(e.type != "touchstart"){
|
|
// Ignore right click
|
|
return;
|
|
}
|
|
|
|
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/Container._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/Container._Item
|
|
},
|
|
|
|
forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){
|
|
// summary:
|
|
// Iterates over selected items;
|
|
// see `dojo/dnd/Container.forInItems()` for details
|
|
o = o || kernel.global;
|
|
for(var id in this.selection){
|
|
// console.log("selected item id: " + id);
|
|
f.call(o, this.getItem(id), id, this);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/_Container':function(){
|
|
define("dijit/_Container", [
|
|
"dojo/_base/array", // array.forEach array.indexOf
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-construct" // domConstruct.place
|
|
], function(array, declare, domConstruct){
|
|
|
|
// module:
|
|
// dijit/_Container
|
|
|
|
return declare("dijit._Container", null, {
|
|
// summary:
|
|
// Mixin for widgets that contain HTML and/or a set of widget children.
|
|
|
|
buildRendering: function(){
|
|
this.inherited(arguments);
|
|
if(!this.containerNode){
|
|
// all widgets with descendants must set containerNode
|
|
this.containerNode = this.domNode;
|
|
}
|
|
},
|
|
|
|
addChild: function(/*dijit/_WidgetBase*/ widget, /*int?*/ insertIndex){
|
|
// summary:
|
|
// Makes the given widget a child of this widget.
|
|
// description:
|
|
// Inserts specified child widget's dom node as a child of this widget's
|
|
// container node, and possibly does other processing (such as layout).
|
|
//
|
|
// Functionality is undefined if this widget contains anything besides
|
|
// a list of child widgets (ie, if it contains arbitrary non-widget HTML).
|
|
|
|
var refNode = this.containerNode;
|
|
if(insertIndex && typeof insertIndex == "number"){
|
|
var children = this.getChildren();
|
|
if(children && children.length >= insertIndex){
|
|
refNode = children[insertIndex-1].domNode;
|
|
insertIndex = "after";
|
|
}
|
|
}
|
|
domConstruct.place(widget.domNode, refNode, insertIndex);
|
|
|
|
// If I've been started but the child widget hasn't been started,
|
|
// start it now. Make sure to do this after widget has been
|
|
// inserted into the DOM tree, so it can see that it's being controlled by me,
|
|
// so it doesn't try to size itself.
|
|
if(this._started && !widget._started){
|
|
widget.startup();
|
|
}
|
|
},
|
|
|
|
removeChild: function(/*Widget|int*/ widget){
|
|
// summary:
|
|
// Removes the passed widget instance from this widget but does
|
|
// not destroy it. You can also pass in an integer indicating
|
|
// the index within the container to remove (ie, removeChild(5) removes the sixth widget).
|
|
|
|
if(typeof widget == "number"){
|
|
widget = this.getChildren()[widget];
|
|
}
|
|
|
|
if(widget){
|
|
var node = widget.domNode;
|
|
if(node && node.parentNode){
|
|
node.parentNode.removeChild(node); // detach but don't destroy
|
|
}
|
|
}
|
|
},
|
|
|
|
hasChildren: function(){
|
|
// summary:
|
|
// Returns true if widget has child widgets, i.e. if this.containerNode contains widgets.
|
|
return this.getChildren().length > 0; // Boolean
|
|
},
|
|
|
|
_getSiblingOfChild: function(/*dijit/_WidgetBase*/ child, /*int*/ dir){
|
|
// summary:
|
|
// Get the next or previous widget sibling of child
|
|
// dir:
|
|
// if 1, get the next sibling
|
|
// if -1, get the previous sibling
|
|
// tags:
|
|
// private
|
|
var children = this.getChildren(),
|
|
idx = array.indexOf(this.getChildren(), child); // int
|
|
return children[idx + dir];
|
|
},
|
|
|
|
getIndexOfChild: function(/*dijit/_WidgetBase*/ child){
|
|
// summary:
|
|
// Gets the index of the child in this container or -1 if not found
|
|
return array.indexOf(this.getChildren(), child); // int
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/data/ItemFileReadStore':function(){
|
|
define("dojo/data/ItemFileReadStore", ["../_base/kernel", "../_base/lang", "../_base/declare", "../_base/array", "../_base/xhr",
|
|
"../Evented", "./util/filter", "./util/simpleFetch", "../date/stamp"
|
|
], function(kernel, lang, declare, array, xhr, Evented, filterUtil, simpleFetch, dateStamp){
|
|
|
|
// module:
|
|
// dojo/data/ItemFileReadStore
|
|
|
|
var ItemFileReadStore = declare("dojo.data.ItemFileReadStore", [Evented],{
|
|
// summary:
|
|
// The ItemFileReadStore implements the dojo/data/api/Read API and reads
|
|
// data from JSON files that have contents in this format --
|
|
// | { items: [
|
|
// | { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]},
|
|
// | { name:'Fozzie Bear', wears:['hat', 'tie']},
|
|
// | { name:'Miss Piggy', pets:'Foo-Foo'}
|
|
// | ]}
|
|
// Note that it can also contain an 'identifier' property that specified which attribute on the items
|
|
// in the array of items that acts as the unique identifier for that item.
|
|
|
|
constructor: function(/* Object */ keywordParameters){
|
|
// summary:
|
|
// constructor
|
|
// keywordParameters:
|
|
// {url: String} {data: jsonObject} {typeMap: object}
|
|
// The structure of the typeMap object is as follows:
|
|
// | {
|
|
// | type0: function || object,
|
|
// | type1: function || object,
|
|
// | ...
|
|
// | typeN: function || object
|
|
// | }
|
|
// Where if it is a function, it is assumed to be an object constructor that takes the
|
|
// value of _value as the initialization parameters. If it is an object, then it is assumed
|
|
// to be an object of general form:
|
|
// | {
|
|
// | type: function, //constructor.
|
|
// | deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
|
|
// | }
|
|
|
|
this._arrayOfAllItems = [];
|
|
this._arrayOfTopLevelItems = [];
|
|
this._loadFinished = false;
|
|
this._jsonFileUrl = keywordParameters.url;
|
|
this._ccUrl = keywordParameters.url;
|
|
this.url = keywordParameters.url;
|
|
this._jsonData = keywordParameters.data;
|
|
this.data = null;
|
|
this._datatypeMap = keywordParameters.typeMap || {};
|
|
if(!this._datatypeMap['Date']){
|
|
//If no default mapping for dates, then set this as default.
|
|
//We use the dojo/date/stamp here because the ISO format is the 'dojo way'
|
|
//of generically representing dates.
|
|
this._datatypeMap['Date'] = {
|
|
type: Date,
|
|
deserialize: function(value){
|
|
return dateStamp.fromISOString(value);
|
|
}
|
|
};
|
|
}
|
|
this._features = {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true};
|
|
this._itemsByIdentity = null;
|
|
this._storeRefPropName = "_S"; // Default name for the store reference to attach to every item.
|
|
this._itemNumPropName = "_0"; // Default Item Id for isItem to attach to every item.
|
|
this._rootItemPropName = "_RI"; // Default Item Id for isItem to attach to every item.
|
|
this._reverseRefMap = "_RRM"; // Default attribute for constructing a reverse reference map for use with reference integrity
|
|
this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset.
|
|
this._queuedFetches = [];
|
|
if(keywordParameters.urlPreventCache !== undefined){
|
|
this.urlPreventCache = keywordParameters.urlPreventCache?true:false;
|
|
}
|
|
if(keywordParameters.hierarchical !== undefined){
|
|
this.hierarchical = keywordParameters.hierarchical?true:false;
|
|
}
|
|
if(keywordParameters.clearOnClose){
|
|
this.clearOnClose = true;
|
|
}
|
|
if("failOk" in keywordParameters){
|
|
this.failOk = keywordParameters.failOk?true:false;
|
|
}
|
|
},
|
|
|
|
url: "", // use "" rather than undefined for the benefit of the parser (#3539)
|
|
|
|
//Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload
|
|
//when clearOnClose and close is used.
|
|
_ccUrl: "",
|
|
|
|
data: null, // define this so that the parser can populate it
|
|
|
|
typeMap: null, //Define so parser can populate.
|
|
|
|
// clearOnClose: Boolean
|
|
// Parameter to allow users to specify if a close call should force a reload or not.
|
|
// By default, it retains the old behavior of not clearing if close is called. But
|
|
// if set true, the store will be reset to default state. Note that by doing this,
|
|
// all item handles will become invalid and a new fetch must be issued.
|
|
clearOnClose: false,
|
|
|
|
// urlPreventCache: Boolean
|
|
// Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url.
|
|
// Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option.
|
|
// Added for tracker: #6072
|
|
urlPreventCache: false,
|
|
|
|
// failOk: Boolean
|
|
// Parameter for specifying that it is OK for the xhrGet call to fail silently.
|
|
failOk: false,
|
|
|
|
// hierarchical: Boolean
|
|
// Parameter to indicate to process data from the url as hierarchical
|
|
// (data items can contain other data items in js form). Default is true
|
|
// for backwards compatibility. False means only root items are processed
|
|
// as items, all child objects outside of type-mapped objects and those in
|
|
// specific reference format, are left straight JS data objects.
|
|
hierarchical: true,
|
|
|
|
_assertIsItem: function(/* dojo/data/api/Item */ item){
|
|
// summary:
|
|
// This function tests whether the item passed in is indeed an item in the store.
|
|
// item:
|
|
// The item to test for being contained by the store.
|
|
if(!this.isItem(item)){
|
|
throw new Error(this.declaredClass + ": Invalid item argument.");
|
|
}
|
|
},
|
|
|
|
_assertIsAttribute: function(/* attribute-name-string */ attribute){
|
|
// summary:
|
|
// This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
|
|
// attribute:
|
|
// The attribute to test for being contained by the store.
|
|
if(typeof attribute !== "string"){
|
|
throw new Error(this.declaredClass + ": Invalid attribute argument.");
|
|
}
|
|
},
|
|
|
|
getValue: function( /* dojo/data/api/Item */ item,
|
|
/* attribute-name-string */ attribute,
|
|
/* value? */ defaultValue){
|
|
// summary:
|
|
// See dojo/data/api/Read.getValue()
|
|
var values = this.getValues(item, attribute);
|
|
return (values.length > 0)?values[0]:defaultValue; // mixed
|
|
},
|
|
|
|
getValues: function(/* dojo/data/api/Item */ item,
|
|
/* attribute-name-string */ attribute){
|
|
// summary:
|
|
// See dojo/data/api/Read.getValues()
|
|
|
|
this._assertIsItem(item);
|
|
this._assertIsAttribute(attribute);
|
|
// Clone it before returning. refs: #10474
|
|
return (item[attribute] || []).slice(0); // Array
|
|
},
|
|
|
|
getAttributes: function(/* dojo/data/api/Item */ item){
|
|
// summary:
|
|
// See dojo/data/api/Read.getAttributes()
|
|
this._assertIsItem(item);
|
|
var attributes = [];
|
|
for(var key in item){
|
|
// Save off only the real item attributes, not the special id marks for O(1) isItem.
|
|
if((key !== this._storeRefPropName) && (key !== this._itemNumPropName) && (key !== this._rootItemPropName) && (key !== this._reverseRefMap)){
|
|
attributes.push(key);
|
|
}
|
|
}
|
|
return attributes; // Array
|
|
},
|
|
|
|
hasAttribute: function( /* dojo/data/api/Item */ item,
|
|
/* attribute-name-string */ attribute){
|
|
// summary:
|
|
// See dojo/data/api/Read.hasAttribute()
|
|
this._assertIsItem(item);
|
|
this._assertIsAttribute(attribute);
|
|
return (attribute in item);
|
|
},
|
|
|
|
containsValue: function(/* dojo/data/api/Item */ item,
|
|
/* attribute-name-string */ attribute,
|
|
/* anything */ value){
|
|
// summary:
|
|
// See dojo/data/api/Read.containsValue()
|
|
var regexp = undefined;
|
|
if(typeof value === "string"){
|
|
regexp = filterUtil.patternToRegExp(value, false);
|
|
}
|
|
return this._containsValue(item, attribute, value, regexp); //boolean.
|
|
},
|
|
|
|
_containsValue: function( /* dojo/data/api/Item */ item,
|
|
/* attribute-name-string */ attribute,
|
|
/* anything */ value,
|
|
/* RegExp?*/ regexp){
|
|
// summary:
|
|
// Internal function for looking at the values contained by the item.
|
|
// description:
|
|
// Internal function for looking at the values contained by the item. This
|
|
// function allows for denoting if the comparison should be case sensitive for
|
|
// strings or not (for handling filtering cases where string case should not matter)
|
|
// item:
|
|
// The data item to examine for attribute values.
|
|
// attribute:
|
|
// The attribute to inspect.
|
|
// value:
|
|
// The value to match.
|
|
// regexp:
|
|
// Optional regular expression generated off value if value was of string type to handle wildcarding.
|
|
// If present and attribute values are string, then it can be used for comparison instead of 'value'
|
|
return array.some(this.getValues(item, attribute), function(possibleValue){
|
|
if(possibleValue !== null && !lang.isObject(possibleValue) && regexp){
|
|
if(possibleValue.toString().match(regexp)){
|
|
return true; // Boolean
|
|
}
|
|
}else if(value === possibleValue){
|
|
return true; // Boolean
|
|
}
|
|
});
|
|
},
|
|
|
|
isItem: function(/* anything */ something){
|
|
// summary:
|
|
// See dojo/data/api/Read.isItem()
|
|
if(something && something[this._storeRefPropName] === this){
|
|
if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){
|
|
return true;
|
|
}
|
|
}
|
|
return false; // Boolean
|
|
},
|
|
|
|
isItemLoaded: function(/* anything */ something){
|
|
// summary:
|
|
// See dojo/data/api/Read.isItemLoaded()
|
|
return this.isItem(something); //boolean
|
|
},
|
|
|
|
loadItem: function(/* object */ keywordArgs){
|
|
// summary:
|
|
// See dojo/data/api/Read.loadItem()
|
|
this._assertIsItem(keywordArgs.item);
|
|
},
|
|
|
|
getFeatures: function(){
|
|
// summary:
|
|
// See dojo/data/api/Read.getFeatures()
|
|
return this._features; //Object
|
|
},
|
|
|
|
getLabel: function(/* dojo/data/api/Item */ item){
|
|
// summary:
|
|
// See dojo/data/api/Read.getLabel()
|
|
if(this._labelAttr && this.isItem(item)){
|
|
return this.getValue(item,this._labelAttr); //String
|
|
}
|
|
return undefined; //undefined
|
|
},
|
|
|
|
getLabelAttributes: function(/* dojo/data/api/Item */ item){
|
|
// summary:
|
|
// See dojo/data/api/Read.getLabelAttributes()
|
|
if(this._labelAttr){
|
|
return [this._labelAttr]; //array
|
|
}
|
|
return null; //null
|
|
},
|
|
|
|
filter: function(/* Object */ requestArgs, /* item[] */ arrayOfItems, /* Function */ findCallback){
|
|
// summary:
|
|
// This method handles the basic filtering needs for ItemFile* based stores.
|
|
var items = [],
|
|
i, key;
|
|
|
|
if(requestArgs.query){
|
|
var value,
|
|
ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false;
|
|
|
|
//See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
|
|
//same value for each item examined. Much more efficient.
|
|
var regexpList = {};
|
|
for(key in requestArgs.query){
|
|
value = requestArgs.query[key];
|
|
if(typeof value === "string"){
|
|
regexpList[key] = filterUtil.patternToRegExp(value, ignoreCase);
|
|
}else if(value instanceof RegExp){
|
|
regexpList[key] = value;
|
|
}
|
|
}
|
|
for(i = 0; i < arrayOfItems.length; ++i){
|
|
var match = true;
|
|
var candidateItem = arrayOfItems[i];
|
|
if(candidateItem === null){
|
|
match = false;
|
|
}else{
|
|
for(key in requestArgs.query){
|
|
value = requestArgs.query[key];
|
|
if(!this._containsValue(candidateItem, key, value, regexpList[key])){
|
|
match = false;
|
|
}
|
|
}
|
|
}
|
|
if(match){
|
|
items.push(candidateItem);
|
|
}
|
|
}
|
|
findCallback(items, requestArgs);
|
|
}else{
|
|
// We want a copy to pass back in case the parent wishes to sort the array.
|
|
// We shouldn't allow resort of the internal list, so that multiple callers
|
|
// can get lists and sort without affecting each other. We also need to
|
|
// filter out any null values that have been left as a result of deleteItem()
|
|
// calls in ItemFileWriteStore.
|
|
for(i = 0; i < arrayOfItems.length; ++i){
|
|
var item = arrayOfItems[i];
|
|
if(item !== null){
|
|
items.push(item);
|
|
}
|
|
}
|
|
findCallback(items, requestArgs);
|
|
}
|
|
},
|
|
|
|
_fetchItems: function( /* Object */ keywordArgs,
|
|
/* Function */ findCallback,
|
|
/* Function */ errorCallback){
|
|
// summary:
|
|
// See dojo/data/util.simpleFetch.fetch()
|
|
var self = this;
|
|
|
|
if(this._loadFinished){
|
|
this.filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions), findCallback);
|
|
}else{
|
|
//Do a check on the JsonFileUrl and crosscheck it.
|
|
//If it doesn't match the cross-check, it needs to be updated
|
|
//This allows for either url or _jsonFileUrl to he changed to
|
|
//reset the store load location. Done this way for backwards
|
|
//compatibility. People use _jsonFileUrl (even though officially
|
|
//private.
|
|
if(this._jsonFileUrl !== this._ccUrl){
|
|
kernel.deprecated(this.declaredClass + ": ",
|
|
"To change the url, set the url property of the store," +
|
|
" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
|
|
this._ccUrl = this._jsonFileUrl;
|
|
this.url = this._jsonFileUrl;
|
|
}else if(this.url !== this._ccUrl){
|
|
this._jsonFileUrl = this.url;
|
|
this._ccUrl = this.url;
|
|
}
|
|
|
|
//See if there was any forced reset of data.
|
|
if(this.data != null){
|
|
this._jsonData = this.data;
|
|
this.data = null;
|
|
}
|
|
|
|
if(this._jsonFileUrl){
|
|
//If fetches come in before the loading has finished, but while
|
|
//a load is in progress, we have to defer the fetching to be
|
|
//invoked in the callback.
|
|
if(this._loadInProgress){
|
|
this._queuedFetches.push({args: keywordArgs, filter: lang.hitch(self, "filter"), findCallback: lang.hitch(self, findCallback)});
|
|
}else{
|
|
this._loadInProgress = true;
|
|
var getArgs = {
|
|
url: self._jsonFileUrl,
|
|
handleAs: "json-comment-optional",
|
|
preventCache: this.urlPreventCache,
|
|
failOk: this.failOk
|
|
};
|
|
var getHandler = xhr.get(getArgs);
|
|
getHandler.addCallback(function(data){
|
|
try{
|
|
self._getItemsFromLoadedData(data);
|
|
self._loadFinished = true;
|
|
self._loadInProgress = false;
|
|
|
|
self.filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions), findCallback);
|
|
self._handleQueuedFetches();
|
|
}catch(e){
|
|
self._loadFinished = true;
|
|
self._loadInProgress = false;
|
|
errorCallback(e, keywordArgs);
|
|
}
|
|
});
|
|
getHandler.addErrback(function(error){
|
|
self._loadInProgress = false;
|
|
errorCallback(error, keywordArgs);
|
|
});
|
|
|
|
//Wire up the cancel to abort of the request
|
|
//This call cancel on the deferred if it hasn't been called
|
|
//yet and then will chain to the simple abort of the
|
|
//simpleFetch keywordArgs
|
|
var oldAbort = null;
|
|
if(keywordArgs.abort){
|
|
oldAbort = keywordArgs.abort;
|
|
}
|
|
keywordArgs.abort = function(){
|
|
var df = getHandler;
|
|
if(df && df.fired === -1){
|
|
df.cancel();
|
|
df = null;
|
|
}
|
|
if(oldAbort){
|
|
oldAbort.call(keywordArgs);
|
|
}
|
|
};
|
|
}
|
|
}else if(this._jsonData){
|
|
try{
|
|
this._loadFinished = true;
|
|
this._getItemsFromLoadedData(this._jsonData);
|
|
this._jsonData = null;
|
|
self.filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions), findCallback);
|
|
}catch(e){
|
|
errorCallback(e, keywordArgs);
|
|
}
|
|
}else{
|
|
errorCallback(new Error(this.declaredClass + ": No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs);
|
|
}
|
|
}
|
|
},
|
|
|
|
_handleQueuedFetches: function(){
|
|
// summary:
|
|
// Internal function to execute delayed request in the store.
|
|
|
|
//Execute any deferred fetches now.
|
|
if(this._queuedFetches.length > 0){
|
|
for(var i = 0; i < this._queuedFetches.length; i++){
|
|
var fData = this._queuedFetches[i],
|
|
delayedQuery = fData.args,
|
|
delayedFilter = fData.filter,
|
|
delayedFindCallback = fData.findCallback;
|
|
if(delayedFilter){
|
|
delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions), delayedFindCallback);
|
|
}else{
|
|
this.fetchItemByIdentity(delayedQuery);
|
|
}
|
|
}
|
|
this._queuedFetches = [];
|
|
}
|
|
},
|
|
|
|
_getItemsArray: function(/*object?*/queryOptions){
|
|
// summary:
|
|
// Internal function to determine which list of items to search over.
|
|
// queryOptions: The query options parameter, if any.
|
|
if(queryOptions && queryOptions.deep){
|
|
return this._arrayOfAllItems;
|
|
}
|
|
return this._arrayOfTopLevelItems;
|
|
},
|
|
|
|
close: function(/*dojo/data/api/Request|Object?*/ request){
|
|
// summary:
|
|
// See dojo/data/api/Read.close()
|
|
if(this.clearOnClose &&
|
|
this._loadFinished &&
|
|
!this._loadInProgress){
|
|
//Reset all internalsback to default state. This will force a reload
|
|
//on next fetch. This also checks that the data or url param was set
|
|
//so that the store knows it can get data. Without one of those being set,
|
|
//the next fetch will trigger an error.
|
|
|
|
if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) &&
|
|
(this.url == "" || this.url == null)
|
|
) && this.data == null){
|
|
console.debug(this.declaredClass + ": WARNING! Data reload " +
|
|
" information has not been provided." +
|
|
" Please set 'url' or 'data' to the appropriate value before" +
|
|
" the next fetch");
|
|
}
|
|
this._arrayOfAllItems = [];
|
|
this._arrayOfTopLevelItems = [];
|
|
this._loadFinished = false;
|
|
this._itemsByIdentity = null;
|
|
this._loadInProgress = false;
|
|
this._queuedFetches = [];
|
|
}
|
|
},
|
|
|
|
_getItemsFromLoadedData: function(/* Object */ dataObject){
|
|
// summary:
|
|
// Function to parse the loaded data into item format and build the internal items array.
|
|
// description:
|
|
// Function to parse the loaded data into item format and build the internal items array.
|
|
// dataObject:
|
|
// The JS data object containing the raw data to convery into item format.
|
|
// returns: Array
|
|
// Array of items in store item format.
|
|
|
|
// First, we define a couple little utility functions...
|
|
var addingArrays = false,
|
|
self = this;
|
|
|
|
function valueIsAnItem(/* anything */ aValue){
|
|
// summary:
|
|
// Given any sort of value that could be in the raw json data,
|
|
// return true if we should interpret the value as being an
|
|
// item itself, rather than a literal value or a reference.
|
|
// example:
|
|
// | false == valueIsAnItem("Kermit");
|
|
// | false == valueIsAnItem(42);
|
|
// | false == valueIsAnItem(new Date());
|
|
// | false == valueIsAnItem({_type:'Date', _value:'1802-05-14'});
|
|
// | false == valueIsAnItem({_reference:'Kermit'});
|
|
// | true == valueIsAnItem({name:'Kermit', color:'green'});
|
|
// | true == valueIsAnItem({iggy:'pop'});
|
|
// | true == valueIsAnItem({foo:42});
|
|
return (aValue !== null) &&
|
|
(typeof aValue === "object") &&
|
|
(!lang.isArray(aValue) || addingArrays) &&
|
|
(!lang.isFunction(aValue)) &&
|
|
(aValue.constructor == Object || lang.isArray(aValue)) &&
|
|
(typeof aValue._reference === "undefined") &&
|
|
(typeof aValue._type === "undefined") &&
|
|
(typeof aValue._value === "undefined") &&
|
|
self.hierarchical;
|
|
}
|
|
|
|
function addItemAndSubItemsToArrayOfAllItems(/* dojo/data/api/Item */ anItem){
|
|
self._arrayOfAllItems.push(anItem);
|
|
for(var attribute in anItem){
|
|
var valueForAttribute = anItem[attribute];
|
|
if(valueForAttribute){
|
|
if(lang.isArray(valueForAttribute)){
|
|
var valueArray = valueForAttribute;
|
|
for(var k = 0; k < valueArray.length; ++k){
|
|
var singleValue = valueArray[k];
|
|
if(valueIsAnItem(singleValue)){
|
|
addItemAndSubItemsToArrayOfAllItems(singleValue);
|
|
}
|
|
}
|
|
}else{
|
|
if(valueIsAnItem(valueForAttribute)){
|
|
addItemAndSubItemsToArrayOfAllItems(valueForAttribute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this._labelAttr = dataObject.label;
|
|
|
|
// We need to do some transformations to convert the data structure
|
|
// that we read from the file into a format that will be convenient
|
|
// to work with in memory.
|
|
|
|
// Step 1: Walk through the object hierarchy and build a list of all items
|
|
var i,
|
|
item;
|
|
this._arrayOfAllItems = [];
|
|
this._arrayOfTopLevelItems = dataObject.items;
|
|
|
|
for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){
|
|
item = this._arrayOfTopLevelItems[i];
|
|
if(lang.isArray(item)){
|
|
addingArrays = true;
|
|
}
|
|
addItemAndSubItemsToArrayOfAllItems(item);
|
|
item[this._rootItemPropName]=true;
|
|
}
|
|
|
|
// Step 2: Walk through all the attribute values of all the items,
|
|
// and replace single values with arrays. For example, we change this:
|
|
// { name:'Miss Piggy', pets:'Foo-Foo'}
|
|
// into this:
|
|
// { name:['Miss Piggy'], pets:['Foo-Foo']}
|
|
//
|
|
// We also store the attribute names so we can validate our store
|
|
// reference and item id special properties for the O(1) isItem
|
|
var allAttributeNames = {},
|
|
key;
|
|
|
|
for(i = 0; i < this._arrayOfAllItems.length; ++i){
|
|
item = this._arrayOfAllItems[i];
|
|
for(key in item){
|
|
if(key !== this._rootItemPropName){
|
|
var value = item[key];
|
|
if(value !== null){
|
|
if(!lang.isArray(value)){
|
|
item[key] = [value];
|
|
}
|
|
}else{
|
|
item[key] = [null];
|
|
}
|
|
}
|
|
allAttributeNames[key]=key;
|
|
}
|
|
}
|
|
|
|
// Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName
|
|
// This should go really fast, it will generally never even run the loop.
|
|
while(allAttributeNames[this._storeRefPropName]){
|
|
this._storeRefPropName += "_";
|
|
}
|
|
while(allAttributeNames[this._itemNumPropName]){
|
|
this._itemNumPropName += "_";
|
|
}
|
|
while(allAttributeNames[this._reverseRefMap]){
|
|
this._reverseRefMap += "_";
|
|
}
|
|
|
|
// Step 4: Some data files specify an optional 'identifier', which is
|
|
// the name of an attribute that holds the identity of each item.
|
|
// If this data file specified an identifier attribute, then build a
|
|
// hash table of items keyed by the identity of the items.
|
|
var arrayOfValues;
|
|
|
|
var identifier = dataObject.identifier;
|
|
if(identifier){
|
|
this._itemsByIdentity = {};
|
|
this._features['dojo.data.api.Identity'] = identifier;
|
|
for(i = 0; i < this._arrayOfAllItems.length; ++i){
|
|
item = this._arrayOfAllItems[i];
|
|
arrayOfValues = item[identifier];
|
|
var identity = arrayOfValues[0];
|
|
if(!Object.hasOwnProperty.call(this._itemsByIdentity, identity)){
|
|
this._itemsByIdentity[identity] = item;
|
|
}else{
|
|
if(this._jsonFileUrl){
|
|
throw new Error(this.declaredClass + ": The json data as specified by: [" + this._jsonFileUrl + "] is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]");
|
|
}else if(this._jsonData){
|
|
throw new Error(this.declaredClass + ": The json data provided by the creation arguments is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]");
|
|
}
|
|
}
|
|
}
|
|
}else{
|
|
this._features['dojo.data.api.Identity'] = Number;
|
|
}
|
|
|
|
// Step 5: Walk through all the items, and set each item's properties
|
|
// for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true.
|
|
for(i = 0; i < this._arrayOfAllItems.length; ++i){
|
|
item = this._arrayOfAllItems[i];
|
|
item[this._storeRefPropName] = this;
|
|
item[this._itemNumPropName] = i;
|
|
}
|
|
|
|
// Step 6: We walk through all the attribute values of all the items,
|
|
// looking for type/value literals and item-references.
|
|
//
|
|
// We replace item-references with pointers to items. For example, we change:
|
|
// { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
|
|
// into this:
|
|
// { name:['Kermit'], friends:[miss_piggy] }
|
|
// (where miss_piggy is the object representing the 'Miss Piggy' item).
|
|
//
|
|
// We replace type/value pairs with typed-literals. For example, we change:
|
|
// { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] }
|
|
// into this:
|
|
// { name:['Kermit'], born:(new Date(1918, 6, 18)) }
|
|
//
|
|
// We also generate the associate map for all items for the O(1) isItem function.
|
|
for(i = 0; i < this._arrayOfAllItems.length; ++i){
|
|
item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
|
|
for(key in item){
|
|
arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}]
|
|
for(var j = 0; j < arrayOfValues.length; ++j){
|
|
value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}}
|
|
if(value !== null && typeof value == "object"){
|
|
if(("_type" in value) && ("_value" in value)){
|
|
var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber'
|
|
var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}}
|
|
if(!mappingObj){
|
|
throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'");
|
|
}else if(lang.isFunction(mappingObj)){
|
|
arrayOfValues[j] = new mappingObj(value._value);
|
|
}else if(lang.isFunction(mappingObj.deserialize)){
|
|
arrayOfValues[j] = mappingObj.deserialize(value._value);
|
|
}else{
|
|
throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function");
|
|
}
|
|
}
|
|
if(value._reference){
|
|
var referenceDescription = value._reference; // example: {name:'Miss Piggy'}
|
|
if(!lang.isObject(referenceDescription)){
|
|
// example: 'Miss Piggy'
|
|
// from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]}
|
|
arrayOfValues[j] = this._getItemByIdentity(referenceDescription);
|
|
}else{
|
|
// example: {name:'Miss Piggy'}
|
|
// from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
|
|
for(var k = 0; k < this._arrayOfAllItems.length; ++k){
|
|
var candidateItem = this._arrayOfAllItems[k],
|
|
found = true;
|
|
for(var refKey in referenceDescription){
|
|
if(candidateItem[refKey] != referenceDescription[refKey]){
|
|
found = false;
|
|
}
|
|
}
|
|
if(found){
|
|
arrayOfValues[j] = candidateItem;
|
|
}
|
|
}
|
|
}
|
|
if(this.referenceIntegrity){
|
|
var refItem = arrayOfValues[j];
|
|
if(this.isItem(refItem)){
|
|
this._addReferenceToMap(refItem, item, key);
|
|
}
|
|
}
|
|
}else if(this.isItem(value)){
|
|
//It's a child item (not one referenced through _reference).
|
|
//We need to treat this as a referenced item, so it can be cleaned up
|
|
//in a write store easily.
|
|
if(this.referenceIntegrity){
|
|
this._addReferenceToMap(value, item, key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){
|
|
// summary:
|
|
// Method to add an reference map entry for an item and attribute.
|
|
// description:
|
|
// Method to add an reference map entry for an item and attribute.
|
|
// refItem:
|
|
// The item that is referenced.
|
|
// parentItem:
|
|
// The item that holds the new reference to refItem.
|
|
// attribute:
|
|
// The attribute on parentItem that contains the new reference.
|
|
|
|
//Stub function, does nothing. Real processing is in ItemFileWriteStore.
|
|
},
|
|
|
|
getIdentity: function(/* dojo/data/api/Item */ item){
|
|
// summary:
|
|
// See dojo/data/api/Identity.getIdentity()
|
|
var identifier = this._features['dojo.data.api.Identity'];
|
|
if(identifier === Number){
|
|
return item[this._itemNumPropName]; // Number
|
|
}else{
|
|
var arrayOfValues = item[identifier];
|
|
if(arrayOfValues){
|
|
return arrayOfValues[0]; // Object|String
|
|
}
|
|
}
|
|
return null; // null
|
|
},
|
|
|
|
fetchItemByIdentity: function(/* Object */ keywordArgs){
|
|
// summary:
|
|
// See dojo/data/api/Identity.fetchItemByIdentity()
|
|
|
|
// Hasn't loaded yet, we have to trigger the load.
|
|
var item,
|
|
scope;
|
|
if(!this._loadFinished){
|
|
var self = this;
|
|
//Do a check on the JsonFileUrl and crosscheck it.
|
|
//If it doesn't match the cross-check, it needs to be updated
|
|
//This allows for either url or _jsonFileUrl to he changed to
|
|
//reset the store load location. Done this way for backwards
|
|
//compatibility. People use _jsonFileUrl (even though officially
|
|
//private.
|
|
if(this._jsonFileUrl !== this._ccUrl){
|
|
kernel.deprecated(this.declaredClass + ": ",
|
|
"To change the url, set the url property of the store," +
|
|
" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
|
|
this._ccUrl = this._jsonFileUrl;
|
|
this.url = this._jsonFileUrl;
|
|
}else if(this.url !== this._ccUrl){
|
|
this._jsonFileUrl = this.url;
|
|
this._ccUrl = this.url;
|
|
}
|
|
|
|
//See if there was any forced reset of data.
|
|
if(this.data != null && this._jsonData == null){
|
|
this._jsonData = this.data;
|
|
this.data = null;
|
|
}
|
|
|
|
if(this._jsonFileUrl){
|
|
|
|
if(this._loadInProgress){
|
|
this._queuedFetches.push({args: keywordArgs});
|
|
}else{
|
|
this._loadInProgress = true;
|
|
var getArgs = {
|
|
url: self._jsonFileUrl,
|
|
handleAs: "json-comment-optional",
|
|
preventCache: this.urlPreventCache,
|
|
failOk: this.failOk
|
|
};
|
|
var getHandler = xhr.get(getArgs);
|
|
getHandler.addCallback(function(data){
|
|
var scope = keywordArgs.scope?keywordArgs.scope:kernel.global;
|
|
try{
|
|
self._getItemsFromLoadedData(data);
|
|
self._loadFinished = true;
|
|
self._loadInProgress = false;
|
|
item = self._getItemByIdentity(keywordArgs.identity);
|
|
if(keywordArgs.onItem){
|
|
keywordArgs.onItem.call(scope, item);
|
|
}
|
|
self._handleQueuedFetches();
|
|
}catch(error){
|
|
self._loadInProgress = false;
|
|
if(keywordArgs.onError){
|
|
keywordArgs.onError.call(scope, error);
|
|
}
|
|
}
|
|
});
|
|
getHandler.addErrback(function(error){
|
|
self._loadInProgress = false;
|
|
if(keywordArgs.onError){
|
|
var scope = keywordArgs.scope?keywordArgs.scope:kernel.global;
|
|
keywordArgs.onError.call(scope, error);
|
|
}
|
|
});
|
|
}
|
|
|
|
}else if(this._jsonData){
|
|
// Passed in data, no need to xhr.
|
|
self._getItemsFromLoadedData(self._jsonData);
|
|
self._jsonData = null;
|
|
self._loadFinished = true;
|
|
item = self._getItemByIdentity(keywordArgs.identity);
|
|
if(keywordArgs.onItem){
|
|
scope = keywordArgs.scope?keywordArgs.scope:kernel.global;
|
|
keywordArgs.onItem.call(scope, item);
|
|
}
|
|
}
|
|
}else{
|
|
// Already loaded. We can just look it up and call back.
|
|
item = this._getItemByIdentity(keywordArgs.identity);
|
|
if(keywordArgs.onItem){
|
|
scope = keywordArgs.scope?keywordArgs.scope:kernel.global;
|
|
keywordArgs.onItem.call(scope, item);
|
|
}
|
|
}
|
|
},
|
|
|
|
_getItemByIdentity: function(/* Object */ identity){
|
|
// summary:
|
|
// Internal function to look an item up by its identity map.
|
|
var item = null;
|
|
if(this._itemsByIdentity){
|
|
// If this map is defined, we need to just try to get it. If it fails
|
|
// the item does not exist.
|
|
if(Object.hasOwnProperty.call(this._itemsByIdentity, identity)){
|
|
item = this._itemsByIdentity[identity];
|
|
}
|
|
}else if (Object.hasOwnProperty.call(this._arrayOfAllItems, identity)){
|
|
item = this._arrayOfAllItems[identity];
|
|
}
|
|
if(item === undefined){
|
|
item = null;
|
|
}
|
|
return item; // Object
|
|
},
|
|
|
|
getIdentityAttributes: function(/* dojo/data/api/Item */ item){
|
|
// summary:
|
|
// See dojo/data/api/Identity.getIdentityAttributes()
|
|
|
|
var identifier = this._features['dojo.data.api.Identity'];
|
|
if(identifier === Number){
|
|
// If (identifier === Number) it means getIdentity() just returns
|
|
// an integer item-number for each item. The dojo/data/api/Identity
|
|
// spec says we need to return null if the identity is not composed
|
|
// of attributes
|
|
return null; // null
|
|
}else{
|
|
return [identifier]; // Array
|
|
}
|
|
},
|
|
|
|
_forceLoad: function(){
|
|
// summary:
|
|
// Internal function to force a load of the store if it hasn't occurred yet. This is required
|
|
// for specific functions to work properly.
|
|
var self = this;
|
|
//Do a check on the JsonFileUrl and crosscheck it.
|
|
//If it doesn't match the cross-check, it needs to be updated
|
|
//This allows for either url or _jsonFileUrl to he changed to
|
|
//reset the store load location. Done this way for backwards
|
|
//compatibility. People use _jsonFileUrl (even though officially
|
|
//private.
|
|
if(this._jsonFileUrl !== this._ccUrl){
|
|
kernel.deprecated(this.declaredClass + ": ",
|
|
"To change the url, set the url property of the store," +
|
|
" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
|
|
this._ccUrl = this._jsonFileUrl;
|
|
this.url = this._jsonFileUrl;
|
|
}else if(this.url !== this._ccUrl){
|
|
this._jsonFileUrl = this.url;
|
|
this._ccUrl = this.url;
|
|
}
|
|
|
|
//See if there was any forced reset of data.
|
|
if(this.data != null){
|
|
this._jsonData = this.data;
|
|
this.data = null;
|
|
}
|
|
|
|
if(this._jsonFileUrl){
|
|
var getArgs = {
|
|
url: this._jsonFileUrl,
|
|
handleAs: "json-comment-optional",
|
|
preventCache: this.urlPreventCache,
|
|
failOk: this.failOk,
|
|
sync: true
|
|
};
|
|
var getHandler = xhr.get(getArgs);
|
|
getHandler.addCallback(function(data){
|
|
try{
|
|
//Check to be sure there wasn't another load going on concurrently
|
|
//So we don't clobber data that comes in on it. If there is a load going on
|
|
//then do not save this data. It will potentially clobber current data.
|
|
//We mainly wanted to sync/wait here.
|
|
//TODO: Revisit the loading scheme of this store to improve multi-initial
|
|
//request handling.
|
|
if(self._loadInProgress !== true && !self._loadFinished){
|
|
self._getItemsFromLoadedData(data);
|
|
self._loadFinished = true;
|
|
}else if(self._loadInProgress){
|
|
//Okay, we hit an error state we can't recover from. A forced load occurred
|
|
//while an async load was occurring. Since we cannot block at this point, the best
|
|
//that can be managed is to throw an error.
|
|
throw new Error(this.declaredClass + ": Unable to perform a synchronous load, an async load is in progress.");
|
|
}
|
|
}catch(e){
|
|
console.log(e);
|
|
throw e;
|
|
}
|
|
});
|
|
getHandler.addErrback(function(error){
|
|
throw error;
|
|
});
|
|
}else if(this._jsonData){
|
|
self._getItemsFromLoadedData(self._jsonData);
|
|
self._jsonData = null;
|
|
self._loadFinished = true;
|
|
}
|
|
}
|
|
});
|
|
//Mix in the simple fetch implementation to this class.
|
|
lang.extend(ItemFileReadStore,simpleFetch);
|
|
|
|
return ItemFileReadStore;
|
|
|
|
});
|
|
|
|
},
|
|
'dojo/html':function(){
|
|
define("dojo/html", ["./_base/kernel", "./_base/lang", "./_base/array", "./_base/declare", "./dom", "./dom-construct", "./parser"],
|
|
function(kernel, lang, darray, declare, dom, domConstruct, parser){
|
|
// module:
|
|
// dojo/html
|
|
|
|
var html = {
|
|
// summary:
|
|
// TODOC
|
|
};
|
|
lang.setObject("dojo.html", html);
|
|
|
|
// the parser might be needed..
|
|
|
|
// idCounter is incremented with each instantiation to allow assignment of a unique id for tracking, logging purposes
|
|
var idCounter = 0;
|
|
|
|
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
|
|
};
|
|
|
|
html._emptyNode = domConstruct.empty;
|
|
/*=====
|
|
dojo.html._emptyNode = function(node){
|
|
// summary:
|
|
// Removes all child nodes from the given node. Deprecated, should use dojo/dom-constuct.empty() directly
|
|
// instead.
|
|
// node: DOMNode
|
|
// the parent element
|
|
};
|
|
=====*/
|
|
|
|
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
|
|
html._ContentSetter = 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: kernel._scopeName,
|
|
|
|
// startup: Boolean
|
|
// Start the child widgets after parsing them. Only obeyed if parseContent is true.
|
|
startup: true,
|
|
|
|
// lifecycle 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();
|
|
|
|
var ret = this.onEnd();
|
|
|
|
if(ret && ret.then){
|
|
// Make dojox/html/_ContentSetter.set() return a Promise that resolves when load and parse complete.
|
|
return ret;
|
|
}else{
|
|
// Vanilla dojo/html._ContentSetter.set() returns a DOMNode for back compat. For 2.0, switch it to
|
|
// return a Deferred like above.
|
|
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 = 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
|
|
|
|
// If there is a parse in progress, cancel it.
|
|
if(this.parseDeferred){
|
|
if(!this.parseDeferred.isResolved()){
|
|
this.parseDeferred.cancel();
|
|
}
|
|
delete this.parseDeferred;
|
|
}
|
|
|
|
// destroy any widgets from a previous run
|
|
// NOTE: if you don't want this you'll need to empty
|
|
// the parseResults array property yourself to avoid bad things happening
|
|
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
|
|
domConstruct.empty(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 = 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 and this.parseDeferred if you need those..
|
|
this._parse();
|
|
}
|
|
return this.node; // DomNode
|
|
// TODO: for 2.0 return a Promise indicating that the parse completed.
|
|
},
|
|
|
|
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.parseDeferred;
|
|
delete this.node;
|
|
delete this.content;
|
|
},
|
|
|
|
onContentError: function(err){
|
|
return "Error occurred setting content: " + err;
|
|
},
|
|
|
|
onExecError: function(err){
|
|
return "Error occurred executing scripts: " + 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 don't 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
|
|
// and the parse promise in this.parseDeferred
|
|
// 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);
|
|
var self = this;
|
|
this.parseDeferred = parser.parse({
|
|
rootNode: rootNode,
|
|
noStart: !this.startup,
|
|
inherited: inherited,
|
|
scope: this.parserScope
|
|
}).then(function(results){
|
|
return self.parseResults = results;
|
|
});
|
|
}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
|
|
// override/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
|
|
html._setNodeContent(this.node, errText, true);
|
|
}
|
|
}
|
|
}); // end declare()
|
|
|
|
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:
|
|
// | html.set(node, "some string");
|
|
// | html.set(node, contentNode, {options});
|
|
// | 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 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 html._ContentSetter(lang.mixin(
|
|
params,
|
|
{ content: cont, node: node }
|
|
));
|
|
return op.set();
|
|
}
|
|
};
|
|
|
|
return html;
|
|
});
|
|
|
|
},
|
|
'dijit/_PaletteMixin':function(){
|
|
define("dijit/_PaletteMixin", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/dom-class", // domClass.add domClass.remove
|
|
"dojo/dom-construct", // domConstruct.create domConstruct.place
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/keys", // keys
|
|
"dojo/_base/lang", // lang.getObject
|
|
"./_CssStateMixin",
|
|
"./focus",
|
|
"./typematic"
|
|
], function(declare, domAttr, domClass, domConstruct, event, keys, lang, _CssStateMixin, focus, typematic){
|
|
|
|
// module:
|
|
// dijit/_PaletteMixin
|
|
|
|
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
|
|
// Less than 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] Constructor
|
|
// Constructor for Object created for each cell of the palette.
|
|
// dyeClass should implements dijit.Dye interface
|
|
dyeClass: null,
|
|
|
|
// summary: String
|
|
// Localized summary for the palette table
|
|
summary: '',
|
|
_setSummaryAttr: "paletteTableNode",
|
|
|
|
_dyeFactory: function(value /*===== , row, col, title =====*/){
|
|
// summary:
|
|
// Return instance of dijit.Dye for specified cell of palette
|
|
// tags:
|
|
// extension
|
|
|
|
// Remove string support for 2.0
|
|
var dyeClassObj = typeof this.dyeClass == "string" ? lang.getObject(this.dyeClass) : 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, titles[value]);
|
|
|
|
var cellNode = domConstruct.create("td", {
|
|
"class": this.cellClass,
|
|
tabIndex: "-1",
|
|
title: titles[value],
|
|
role: "gridcell"
|
|
}, rowNode);
|
|
|
|
// prepare cell inner structure
|
|
cellObject.fillCell(cellNode, url);
|
|
|
|
cellNode.idx = 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.own(
|
|
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: Boolean?
|
|
// 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.idx + 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 defer because IE doesn't like changing focus inside of an event handler
|
|
this.defer(lang.hitch(focus, "focus", focusNode));
|
|
}
|
|
},
|
|
|
|
_getDye: function(/*DomNode*/ cell){
|
|
// summary:
|
|
// Get JS object for given cell DOMNode
|
|
|
|
return this._cells[cell.idx].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
|
|
}
|
|
}
|
|
);
|
|
=====*/
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/ValidationTextBox':function(){
|
|
require({cache:{
|
|
'url:dijit/form/templates/ValidationTextBox.html':"<div class=\"dijit dijitReset dijitInline dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" data-dojo-attach-point='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"}});
|
|
define("dijit/form/ValidationTextBox", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/i18n", // i18n.getLocalization
|
|
"./TextBox",
|
|
"../Tooltip",
|
|
"dojo/text!./templates/ValidationTextBox.html",
|
|
"dojo/i18n!./nls/validate"
|
|
], function(declare, kernel, i18n, TextBox, Tooltip, template){
|
|
|
|
// module:
|
|
// dijit/form/ValidationTextBox
|
|
|
|
|
|
/*=====
|
|
var __Constraints = {
|
|
// locale: String
|
|
// locale used for validation, picks up value from this widget's lang attribute
|
|
// _flags_: anything
|
|
// various flags passed to pattern function
|
|
};
|
|
=====*/
|
|
|
|
var ValidationTextBox;
|
|
return ValidationTextBox = declare("dijit.form.ValidationTextBox", TextBox, {
|
|
// summary:
|
|
// Base class for textbox widgets with the ability to validate content of various types and provide user feedback.
|
|
|
|
templateString: template,
|
|
|
|
// 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: __Constraints
|
|
// user-defined object needed to pass parameters to the validator functions
|
|
constraints: {},
|
|
|
|
// pattern: [extension protected] String|Function(constraints) returning a string.
|
|
// This defines the regular expression used to validate the input.
|
|
// Do not add leading ^ or $ characters since the widget adds these.
|
|
// A function may be used to generate a valid pattern when dependent on constraints or other runtime factors.
|
|
// set('pattern', String|Function).
|
|
pattern: ".*",
|
|
|
|
// regExp: Deprecated [extension protected] String. Use "pattern" instead.
|
|
regExp: "",
|
|
|
|
regExpGen: function(/*__Constraints*/ /*===== constraints =====*/){
|
|
// summary:
|
|
// Deprecated. Use set('pattern', Function) instead.
|
|
},
|
|
|
|
// 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: [],
|
|
|
|
_deprecateRegExp: function(attr, value){
|
|
if(value != ValidationTextBox.prototype[attr]){
|
|
kernel.deprecated("ValidationTextBox id="+this.id+", set('" + attr + "', ...) is deprecated. Use set('pattern', ...) instead.", "", "2.0");
|
|
this.set('pattern', value);
|
|
}
|
|
},
|
|
_setRegExpGenAttr: function(/*Function*/ newFcn){
|
|
this._deprecateRegExp("regExpGen", newFcn);
|
|
this.regExpGen = this._getPatternAttr; // backward compat with this.regExpGen(this.constraints)
|
|
},
|
|
_setRegExpAttr: function(/*String*/ value){
|
|
this._deprecateRegExp("regExp", value);
|
|
},
|
|
|
|
_setValueAttr: function(){
|
|
// summary:
|
|
// Hook so set('value', ...) works.
|
|
this.inherited(arguments);
|
|
this.validate(this.focused);
|
|
},
|
|
|
|
validator: function(/*anything*/ value, /*__Constraints*/ constraints){
|
|
// summary:
|
|
// Overridable function used to validate the text input against the regular expression.
|
|
// tags:
|
|
// protected
|
|
return (new RegExp("^(?:" + this._getPatternAttr(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
|
|
var invalid = this.invalidMessage == "$_unset_$" ? this.messages.invalidMessage :
|
|
!this.invalidMessage ? this.promptMessage : this.invalidMessage;
|
|
var missing = this.missingMessage == "$_unset_$" ? this.messages.missingMessage :
|
|
!this.missingMessage ? invalid : this.missingMessage;
|
|
return (this.required && this._isEmpty(this.textbox.value)) ? missing : invalid; // 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 || (isValidSubset && !this._hasBeenBlurred && isFocused))) ? "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()
|
|
if(this._created){
|
|
this.validate(this.focused);
|
|
}
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
//////////// INITIALIZATION METHODS ///////////////////////////////////////
|
|
|
|
constructor: function(params /*===== , srcNodeRef =====*/){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree.
|
|
|
|
this.constraints = {};
|
|
this.baseClass += ' dijitValidationTextBox';
|
|
},
|
|
|
|
startup: function(){
|
|
this.inherited(arguments);
|
|
this._refreshState(); // after all _set* methods have run
|
|
},
|
|
|
|
_setConstraintsAttr: function(/*__Constraints*/ constraints){
|
|
if(!constraints.locale && this.lang){
|
|
constraints.locale = this.lang;
|
|
}
|
|
this._set("constraints", constraints);
|
|
this._refreshState();
|
|
},
|
|
|
|
_setPatternAttr: function(/*String|Function*/ pattern){
|
|
this._set("pattern", pattern); // don't set on INPUT to avoid native HTML5 validation
|
|
},
|
|
|
|
_getPatternAttr: function(/*__Constraints*/ constraints){
|
|
// summary:
|
|
// Hook to get the current regExp and to compute the partial validation RE.
|
|
var p = this.pattern;
|
|
var type = (typeof p).toLowerCase();
|
|
if(type == "function"){
|
|
p = this.pattern(constraints || this.constraints);
|
|
}
|
|
if(p != this._lastRegExp){
|
|
var partialre = "";
|
|
this._lastRegExp = p;
|
|
// 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 != ".*"){
|
|
p.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.pattern;
|
|
console.warn('RegExp error in ' + this.declaredClass + ': ' + this.pattern);
|
|
} // should never be here unless the original RE is bad or the parsing is bad
|
|
this._partialre = "^(?:" + partialre + ")$";
|
|
}
|
|
return p;
|
|
},
|
|
|
|
postMixInProperties: function(){
|
|
this.inherited(arguments);
|
|
this.messages = i18n.getLocalization("dijit.form", "validate", this.lang);
|
|
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/_base/typematic':function(){
|
|
define("dijit/_base/typematic", ["../typematic"], function(){
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// Deprecated, for back-compat, just loads top level module
|
|
};
|
|
=====*/
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/layout/BorderContainer':function(){
|
|
define("dijit/layout/BorderContainer", [
|
|
"dojo/_base/array", // array.filter array.forEach array.map
|
|
"dojo/cookie", // cookie
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.add domClass.remove domClass.toggle
|
|
"dojo/dom-construct", // domConstruct.destroy domConstruct.place
|
|
"dojo/dom-geometry", // domGeometry.marginBox
|
|
"dojo/dom-style", // domStyle.style
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/keys",
|
|
"dojo/_base/lang", // lang.getObject lang.hitch
|
|
"dojo/on",
|
|
"dojo/touch",
|
|
"../_WidgetBase",
|
|
"../_Widget",
|
|
"../_TemplatedMixin",
|
|
"./_LayoutWidget",
|
|
"./utils" // layoutUtils.layoutChildren
|
|
], function(array, cookie, declare, domClass, domConstruct, domGeometry, domStyle, event, keys, lang, on, touch,
|
|
_WidgetBase, _Widget, _TemplatedMixin, _LayoutWidget, layoutUtils){
|
|
|
|
// module:
|
|
// dijit/layout/BorderContainer
|
|
|
|
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 = domConstruct.place("<div class=dijitSplitterCover></div>", 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 = this.ownerDocument;
|
|
|
|
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(this.ownerDocumentBody, "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.
|
|
//
|
|
// See `BorderContainer.ChildWidgetProperties` for details on the properties that can be set on
|
|
// children of a `BorderContainer`.
|
|
// 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/_WidgetBase*/ 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/_WidgetBase*/ child, /*Integer?*/ insertIndex){
|
|
// Override _LayoutWidget.addChild().
|
|
this.inherited(arguments);
|
|
if(this._started){
|
|
this.layout(); //OPT
|
|
}
|
|
},
|
|
|
|
removeChild: function(/*dijit/_WidgetBase*/ 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);
|
|
}
|
|
});
|
|
|
|
BorderContainer.ChildWidgetProperties = {
|
|
// summary:
|
|
// These properties can be specified for the children of a BorderContainer.
|
|
|
|
// region: [const] String
|
|
// Values: "top", "bottom", "leading", "trailing", "left", "right", "center".
|
|
// See the `dijit/layout/BorderContainer` description for details.
|
|
region: '',
|
|
|
|
// layoutPriority: [const] Number
|
|
// 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 children 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
|
|
// Specifies a minimum size (in pixels) for this widget when resized by a splitter.
|
|
minSize: 0,
|
|
|
|
// maxSize: [const] Number
|
|
// Specifies a maximum size (in pixels) for this widget when resized by a splitter.
|
|
maxSize: Infinity
|
|
};
|
|
|
|
// 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.)
|
|
// This is for the benefit of the parser. Remove for 2.0. Also, hide from doc viewer.
|
|
lang.extend(_WidgetBase, /*===== {} || =====*/ BorderContainer.ChildWidgetProperties);
|
|
|
|
// For monkey patching
|
|
BorderContainer._Splitter = _Splitter;
|
|
BorderContainer._Gutter = _Gutter;
|
|
|
|
return BorderContainer;
|
|
});
|
|
|
|
},
|
|
'dijit/_base':function(){
|
|
define("dijit/_base", [
|
|
"./main",
|
|
"./a11y", // used to be in dijit/_base/manager
|
|
"./WidgetSet", // used to be in dijit/_base/manager
|
|
"./_base/focus",
|
|
"./_base/manager",
|
|
"./_base/place",
|
|
"./_base/popup",
|
|
"./_base/scroll",
|
|
"./_base/sniff",
|
|
"./_base/typematic",
|
|
"./_base/wai",
|
|
"./_base/window"
|
|
], function(dijit){
|
|
|
|
// module:
|
|
// dijit/_base
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// Includes all the modules in dijit/_base
|
|
};
|
|
=====*/
|
|
|
|
return dijit._base;
|
|
});
|
|
|
|
},
|
|
'dojo/window':function(){
|
|
define("dojo/window", ["./_base/lang", "./sniff", "./_base/window", "./dom", "./dom-geometry", "./dom-style"],
|
|
function(lang, has, baseWindow, dom, geom, style){
|
|
|
|
// module:
|
|
// dojo/window
|
|
|
|
var window = {
|
|
// summary:
|
|
// TODOC
|
|
|
|
getBox: function(/*Document?*/ doc){
|
|
// summary:
|
|
// Returns the dimensions and scroll position of the viewable area of a browser window
|
|
|
|
doc = doc || baseWindow.doc;
|
|
|
|
var
|
|
scrollRoot = (doc.compatMode == 'BackCompat') ? baseWindow.body(doc) : doc.documentElement,
|
|
// get scroll position
|
|
scroll = geom.docScroll(doc), // scrollRoot.scrollTop/Left should work
|
|
w, h;
|
|
|
|
if(has("touch")){ // if(scrollbars not supported)
|
|
var uiWindow = window.get(doc); // use UI window, not dojo.global window
|
|
// on mobile, scrollRoot.clientHeight <= uiWindow.innerHeight <= scrollRoot.offsetHeight, return uiWindow.innerHeight
|
|
w = uiWindow.innerWidth || scrollRoot.clientWidth; // || scrollRoot.clientXXX probably never evaluated
|
|
h = uiWindow.innerHeight || scrollRoot.clientHeight;
|
|
}else{
|
|
// on desktops, scrollRoot.clientHeight <= scrollRoot.offsetHeight <= uiWindow.innerHeight, return scrollRoot.clientHeight
|
|
// uiWindow.innerWidth/Height includes the scrollbar and cannot be used
|
|
w = scrollRoot.clientWidth;
|
|
h = scrollRoot.clientHeight;
|
|
}
|
|
return {
|
|
l: scroll.x,
|
|
t: scroll.y,
|
|
w: w,
|
|
h: h
|
|
};
|
|
},
|
|
|
|
get: function(/*Document*/ doc){
|
|
// summary:
|
|
// Get window object associated with document doc.
|
|
// doc:
|
|
// The document to get the associated window for.
|
|
|
|
// In some IE versions (at least 6.0), document.parentWindow does not return a
|
|
// reference to the real window object (maybe a copy), so we must fix it as well
|
|
// We use IE specific execScript to attach the real window reference to
|
|
// document._parentWindow for later use
|
|
if(has("ie") && window !== document.parentWindow){
|
|
/*
|
|
In IE 6, only the variable "window" can be used to connect events (others
|
|
may be only copies).
|
|
*/
|
|
doc.parentWindow.execScript("document._parentWindow = window;", "Javascript");
|
|
//to prevent memory leak, unset it after use
|
|
//another possibility is to add an onUnload handler which seems overkill to me (liucougar)
|
|
var win = doc._parentWindow;
|
|
doc._parentWindow = null;
|
|
return win; // Window
|
|
}
|
|
|
|
return doc.parentWindow || doc.defaultView; // Window
|
|
},
|
|
|
|
scrollIntoView: function(/*DomNode*/ node, /*Object?*/ pos){
|
|
// summary:
|
|
// Scroll the passed node into view, if it is not already.
|
|
|
|
// don't rely on node.scrollIntoView working just because the function is there
|
|
|
|
try{ // catch unexpected/unrecreatable errors (#7808) since we can recover using a semi-acceptable native method
|
|
node = dom.byId(node);
|
|
var doc = node.ownerDocument || baseWindow.doc, // TODO: why baseWindow.doc? Isn't node.ownerDocument always defined?
|
|
body = baseWindow.body(doc),
|
|
html = doc.documentElement || body.parentNode,
|
|
isIE = has("ie"), isWK = has("webkit");
|
|
// if an untested browser, then use the native method
|
|
if((!(has("mozilla") || isIE || isWK || has("opera")) || node == body || node == html) && (typeof node.scrollIntoView != "undefined")){
|
|
node.scrollIntoView(false); // short-circuit to native if possible
|
|
return;
|
|
}
|
|
var backCompat = doc.compatMode == 'BackCompat',
|
|
clientAreaRoot = (isIE >= 9 && "frameElement" in node.ownerDocument.parentWindow)
|
|
? ((html.clientHeight > 0 && html.clientWidth > 0 && (body.clientHeight == 0 || body.clientWidth == 0 || body.clientHeight > html.clientHeight || body.clientWidth > html.clientWidth)) ? html : body)
|
|
: (backCompat ? body : html),
|
|
scrollRoot = isWK ? body : clientAreaRoot,
|
|
rootWidth = clientAreaRoot.clientWidth,
|
|
rootHeight = clientAreaRoot.clientHeight,
|
|
rtl = !geom.isBodyLtr(doc),
|
|
nodePos = pos || geom.position(node),
|
|
el = node.parentNode,
|
|
isFixed = function(el){
|
|
return ((isIE <= 6 || (isIE && backCompat))? false : (style.get(el, 'position').toLowerCase() == "fixed"));
|
|
};
|
|
if(isFixed(node)){ return; } // nothing to do
|
|
|
|
while(el){
|
|
if(el == body){ el = scrollRoot; }
|
|
var elPos = geom.position(el),
|
|
fixedPos = isFixed(el);
|
|
|
|
if(el == scrollRoot){
|
|
elPos.w = rootWidth; elPos.h = rootHeight;
|
|
if(scrollRoot == html && isIE && rtl){ elPos.x += scrollRoot.offsetWidth-elPos.w; } // IE workaround where scrollbar causes negative x
|
|
if(elPos.x < 0 || !isIE){ elPos.x = 0; } // IE can have values > 0
|
|
if(elPos.y < 0 || !isIE){ elPos.y = 0; }
|
|
}else{
|
|
var pb = geom.getPadBorderExtents(el);
|
|
elPos.w -= pb.w; elPos.h -= pb.h; elPos.x += pb.l; elPos.y += pb.t;
|
|
var clientSize = el.clientWidth,
|
|
scrollBarSize = elPos.w - clientSize;
|
|
if(clientSize > 0 && scrollBarSize > 0){
|
|
elPos.w = clientSize;
|
|
elPos.x += (rtl && (isIE || el.clientLeft > pb.l/*Chrome*/)) ? scrollBarSize : 0;
|
|
}
|
|
clientSize = el.clientHeight;
|
|
scrollBarSize = elPos.h - clientSize;
|
|
if(clientSize > 0 && scrollBarSize > 0){
|
|
elPos.h = clientSize;
|
|
}
|
|
}
|
|
if(fixedPos){ // bounded by viewport, not parents
|
|
if(elPos.y < 0){
|
|
elPos.h += elPos.y; elPos.y = 0;
|
|
}
|
|
if(elPos.x < 0){
|
|
elPos.w += elPos.x; elPos.x = 0;
|
|
}
|
|
if(elPos.y + elPos.h > rootHeight){
|
|
elPos.h = rootHeight - elPos.y;
|
|
}
|
|
if(elPos.x + elPos.w > rootWidth){
|
|
elPos.w = rootWidth - elPos.x;
|
|
}
|
|
}
|
|
// calculate overflow in all 4 directions
|
|
var l = nodePos.x - elPos.x, // beyond left: < 0
|
|
t = nodePos.y - Math.max(elPos.y, 0), // beyond top: < 0
|
|
r = l + nodePos.w - elPos.w, // beyond right: > 0
|
|
bot = t + nodePos.h - elPos.h; // beyond bottom: > 0
|
|
if(r * l > 0){
|
|
var s = Math[l < 0? "max" : "min"](l, r);
|
|
if(rtl && ((isIE == 8 && !backCompat) || isIE >= 9)){ s = -s; }
|
|
nodePos.x += el.scrollLeft;
|
|
el.scrollLeft += s;
|
|
nodePos.x -= el.scrollLeft;
|
|
}
|
|
if(bot * t > 0){
|
|
nodePos.y += el.scrollTop;
|
|
el.scrollTop += Math[t < 0? "max" : "min"](t, bot);
|
|
nodePos.y -= el.scrollTop;
|
|
}
|
|
el = (el != scrollRoot) && !fixedPos && el.parentNode;
|
|
}
|
|
}catch(error){
|
|
console.error('scrollIntoView: ' + error);
|
|
node.scrollIntoView(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
1 && lang.setObject("dojo.window", window);
|
|
|
|
return window;
|
|
});
|
|
|
|
},
|
|
'dojo/number':function(){
|
|
define("dojo/number", [/*===== "./_base/declare", =====*/ "./_base/lang", "./i18n", "./i18n!./cldr/nls/number", "./string", "./regexp"],
|
|
function(/*===== declare, =====*/ lang, i18n, nlsNumber, dstring, dregexp){
|
|
|
|
// module:
|
|
// dojo/number
|
|
|
|
var number = {
|
|
// summary:
|
|
// localized formatting and parsing routines for Number
|
|
};
|
|
lang.setObject("dojo.number", number);
|
|
|
|
/*=====
|
|
number.__FormatOptions = declare(null, {
|
|
// 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.
|
|
});
|
|
=====*/
|
|
|
|
number.format = function(/*Number*/ value, /*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 number._applyPattern(value, pattern, options); // String
|
|
};
|
|
|
|
//number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
|
|
number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
|
|
|
|
number._applyPattern = function(/*Number*/ value, /*String*/ pattern, /*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: 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 = 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,
|
|
number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round}));
|
|
};
|
|
|
|
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:
|
|
// | >>> number.round(-0.5)
|
|
// | -1
|
|
// | >>> number.round(162.295, 2)
|
|
// | 162.29 // note floating point error. Should be 162.3
|
|
// | >>> 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 = number.round;
|
|
number.round = function(v, p, m){
|
|
var d = Math.pow(10, -p || 0), a = Math.abs(v);
|
|
if(!v || a >= d){
|
|
d = 0;
|
|
}else{
|
|
a /= d;
|
|
if(a < 0.5 || a >= 0.95){
|
|
d = 0;
|
|
}
|
|
}
|
|
return round(v, p, m) + (v > 0 ? d : -d);
|
|
};
|
|
|
|
// Use "doc hint" so the doc parser ignores this new definition of round(), and uses the one above.
|
|
/*===== number.round = round; =====*/
|
|
}
|
|
|
|
/*=====
|
|
number.__FormatAbsoluteOptions = declare(null, {
|
|
// 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.
|
|
});
|
|
=====*/
|
|
|
|
number._formatAbsolute = function(/*Number*/ value, /*String*/ pattern, /*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 = 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 || ".");
|
|
};
|
|
|
|
/*=====
|
|
number.__RegexpOptions = declare(null, {
|
|
// 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.
|
|
});
|
|
=====*/
|
|
number.regexp = function(/*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 number._parseInfo(options).regexp; // String
|
|
};
|
|
|
|
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(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 "("+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
|
|
};
|
|
|
|
/*=====
|
|
number.__ParseOptions = declare(null, {
|
|
// 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.
|
|
});
|
|
=====*/
|
|
number.parse = function(/*String*/ expression, /*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 = 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
|
|
};
|
|
|
|
/*=====
|
|
number.__RealNumberRegexpFlags = declare(null, {
|
|
// 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.
|
|
});
|
|
=====*/
|
|
|
|
number._realNumberRegexp = function(/*__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 = 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]" + 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
|
|
};
|
|
|
|
/*=====
|
|
number.__IntegerRegexpFlags = declare(null, {
|
|
// 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)
|
|
});
|
|
=====*/
|
|
|
|
number._integerRegexp = function(/*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 number;
|
|
});
|
|
|
|
},
|
|
'dijit/_FocusMixin':function(){
|
|
define("dijit/_FocusMixin", [
|
|
"./focus",
|
|
"./_WidgetBase",
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/lang" // lang.extend
|
|
], function(focus, _WidgetBase, declare, lang){
|
|
|
|
// module:
|
|
// dijit/_FocusMixin
|
|
|
|
// We don't know where _FocusMixin will occur in the inheritance chain, but we need the _onFocus()/_onBlur() below
|
|
// to be last in the inheritance chain, so mixin to _WidgetBase.
|
|
lang.extend(_WidgetBase, {
|
|
// focused: [readonly] Boolean
|
|
// This widget or a widget it contains has focus, or is "active" because
|
|
// it was recently clicked.
|
|
focused: false,
|
|
|
|
onFocus: function(){
|
|
// summary:
|
|
// Called when the widget becomes "active" because
|
|
// it or a widget inside of it either has focus, or has recently
|
|
// been clicked.
|
|
// tags:
|
|
// callback
|
|
},
|
|
|
|
onBlur: function(){
|
|
// summary:
|
|
// Called when the widget stops being "active" because
|
|
// focus moved to something outside of it, or the user
|
|
// clicked somewhere outside of it, or the widget was
|
|
// hidden.
|
|
// tags:
|
|
// callback
|
|
},
|
|
|
|
_onFocus: function(){
|
|
// summary:
|
|
// This is where widgets do processing for when they are active,
|
|
// such as changing CSS classes. See onFocus() for more details.
|
|
// tags:
|
|
// protected
|
|
this.onFocus();
|
|
},
|
|
|
|
_onBlur: function(){
|
|
// summary:
|
|
// This is where widgets do processing for when they stop being active,
|
|
// such as changing CSS classes. See onBlur() for more details.
|
|
// tags:
|
|
// protected
|
|
this.onBlur();
|
|
}
|
|
});
|
|
|
|
return declare("dijit._FocusMixin", null, {
|
|
// summary:
|
|
// Mixin to widget to provide _onFocus() and _onBlur() methods that
|
|
// fire when a widget or its descendants get/lose focus
|
|
|
|
// flag that I want _onFocus()/_onBlur() notifications from focus manager
|
|
_focusManager: focus
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dojo/data/util/filter':function(){
|
|
define("dojo/data/util/filter", ["../../_base/lang"], function(lang){
|
|
// module:
|
|
// dojo/data/util/filter
|
|
// summary:
|
|
// TODOC
|
|
|
|
var filter = {};
|
|
lang.setObject("dojo.data.util.filter", filter);
|
|
|
|
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("dijit/_WidgetsInTemplateMixin", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/parser" // parser.parse
|
|
], function(array, declare, parser){
|
|
|
|
// module:
|
|
// dijit/_WidgetsInTemplateMixin
|
|
|
|
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
|
|
}));
|
|
|
|
if(!cw.isFulfilled()){
|
|
throw new Error(this.declaredClass + ": parser returned unfilled promise (probably waiting for module auto-load), " +
|
|
"unsupported by _WidgetsInTemplateMixin. Must pre-load all supporting widgets before instantiation.");
|
|
}
|
|
|
|
// _WidgetBase::destroy() will destroy any supporting widgets under this.domNode.
|
|
// If we wanted to, we could call this.own() on anything in this._startupWidgets that was moved outside
|
|
// of this.domNode (like Dialog, which is moved to <body>).
|
|
|
|
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);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/fx/Toggler':function(){
|
|
define("dojo/fx/Toggler", ["../_base/lang","../_base/declare","../_base/fx", "../_base/connect"],
|
|
function(lang, declare, baseFx, connectUtil){
|
|
// module:
|
|
// dojo/fx/Toggler
|
|
|
|
return declare("dojo.fx.Toggler", null, {
|
|
// summary:
|
|
// A simple `dojo.Animation` toggler API.
|
|
// description:
|
|
// class constructor for an animation toggler. It accepts a packed
|
|
// set of arguments about what type of animation to use in each
|
|
// direction, duration, etc. All available members are mixed into
|
|
// these animations from the constructor (for example, `node`,
|
|
// `showDuration`, `hideDuration`).
|
|
// example:
|
|
// | var t = new dojo/fx/Toggler({
|
|
// | node: "nodeId",
|
|
// | showDuration: 500,
|
|
// | // hideDuration will default to "200"
|
|
// | showFunc: dojo/fx/wipeIn,
|
|
// | // hideFunc will default to "fadeOut"
|
|
// | });
|
|
// | t.show(100); // delay showing for 100ms
|
|
// | // ...time passes...
|
|
// | t.hide();
|
|
|
|
// node: DomNode
|
|
// the node to target for the showing and hiding animations
|
|
node: null,
|
|
|
|
// showFunc: Function
|
|
// The function that returns the `dojo.Animation` to show the node
|
|
showFunc: baseFx.fadeIn,
|
|
|
|
// hideFunc: Function
|
|
// The function that returns the `dojo.Animation` to hide the node
|
|
hideFunc: baseFx.fadeOut,
|
|
|
|
// showDuration:
|
|
// Time in milliseconds to run the show Animation
|
|
showDuration: 200,
|
|
|
|
// hideDuration:
|
|
// Time in milliseconds to run the hide Animation
|
|
hideDuration: 200,
|
|
|
|
// FIXME: need a policy for where the toggler should "be" the next
|
|
// time show/hide are called if we're stopped somewhere in the
|
|
// middle.
|
|
// FIXME: also would be nice to specify individual showArgs/hideArgs mixed into
|
|
// each animation individually.
|
|
// FIXME: also would be nice to have events from the animations exposed/bridged
|
|
|
|
/*=====
|
|
_showArgs: null,
|
|
_showAnim: null,
|
|
|
|
_hideArgs: null,
|
|
_hideAnim: null,
|
|
|
|
_isShowing: false,
|
|
_isHiding: false,
|
|
=====*/
|
|
|
|
constructor: function(args){
|
|
var _t = this;
|
|
|
|
lang.mixin(_t, args);
|
|
_t.node = args.node;
|
|
_t._showArgs = lang.mixin({}, args);
|
|
_t._showArgs.node = _t.node;
|
|
_t._showArgs.duration = _t.showDuration;
|
|
_t.showAnim = _t.showFunc(_t._showArgs);
|
|
|
|
_t._hideArgs = lang.mixin({}, args);
|
|
_t._hideArgs.node = _t.node;
|
|
_t._hideArgs.duration = _t.hideDuration;
|
|
_t.hideAnim = _t.hideFunc(_t._hideArgs);
|
|
|
|
connectUtil.connect(_t.showAnim, "beforeBegin", lang.hitch(_t.hideAnim, "stop", true));
|
|
connectUtil.connect(_t.hideAnim, "beforeBegin", lang.hitch(_t.showAnim, "stop", true));
|
|
},
|
|
|
|
show: function(delay){
|
|
// summary:
|
|
// Toggle the node to showing
|
|
// delay: Integer?
|
|
// Amount of time to stall playing the show animation
|
|
return this.showAnim.play(delay || 0);
|
|
},
|
|
|
|
hide: function(delay){
|
|
// summary:
|
|
// Toggle the node to hidden
|
|
// delay: Integer?
|
|
// Amount of time to stall playing the hide animation
|
|
return this.hideAnim.play(delay || 0);
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/FilteringSelect':function(){
|
|
define("dijit/form/FilteringSelect", [
|
|
"dojo/data/util/filter", // filter.patternToRegExp
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/lang", // lang.mixin
|
|
"dojo/when",
|
|
"./MappedTextBox",
|
|
"./ComboBoxMixin"
|
|
], function(filter, declare, lang, when, MappedTextBox, ComboBoxMixin){
|
|
|
|
// module:
|
|
// dijit/form/FilteringSelect
|
|
|
|
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;
|
|
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);
|
|
when(this._fetchHandle, function(result){
|
|
_this._fetchHandle = null;
|
|
_this._callbackSetLabel(result || [], query, options, priorityChange);
|
|
}, function(err){
|
|
_this._fetchHandle = null;
|
|
if(!_this._cancelingQuery){ // don't treat canceled query as an error
|
|
console.error('dijit.form.FilteringSelect: ' + err.toString());
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
undo: function(){
|
|
this.set('displayedValue', this._lastDisplayedValue);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dojo/data/util/sorter':function(){
|
|
define("dojo/data/util/sorter", ["../../_base/lang"], function(lang){
|
|
// module:
|
|
// dojo/data/util/sorter
|
|
// summary:
|
|
// TODOC
|
|
|
|
var sorter = {};
|
|
lang.setObject("dojo.data.util.sorter", sorter);
|
|
|
|
sorter.basicComparator = function( /*anything*/ a,
|
|
/*anything*/ b){
|
|
// summary:
|
|
// Basic comparison 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[] */sortSpec, /*dojo/data/api/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:
|
|
// 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:
|
|
// 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("dijit/form/_ButtonMixin", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.setSelectable
|
|
"dojo/_base/event", // event.stop
|
|
"../registry" // registry.byNode
|
|
], function(declare, dom, event, registry){
|
|
|
|
// module:
|
|
// dijit/form/_ButtonMixin
|
|
|
|
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 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("dojo/colors", ["./_base/kernel", "./_base/lang", "./_base/Color", "./_base/array"], function(dojo, lang, Color, ArrayUtil){
|
|
// module:
|
|
// dojo/colors
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// Color utilities, extending Base dojo.Color
|
|
};
|
|
=====*/
|
|
|
|
var ColorExt = {};
|
|
lang.setObject("dojo.colors", ColorExt);
|
|
|
|
//TODO: this module appears to break naming conventions
|
|
|
|
// 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/_base/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/_base/Color
|
|
}
|
|
return Color.fromArray(c, obj); // dojo/_base/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/_base/Color
|
|
}
|
|
}
|
|
return null; // dojo/_base/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/_base/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/_base/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; // TODO: return ColorExt, not Color
|
|
});
|
|
|
|
},
|
|
'dijit/registry':function(){
|
|
define("dijit/registry", [
|
|
"dojo/_base/array", // array.forEach array.map
|
|
"dojo/sniff", // has("ie")
|
|
"dojo/_base/unload", // unload.addOnWindowUnload
|
|
"dojo/_base/window", // win.body
|
|
"./main" // dijit._scopeName
|
|
], function(array, has, unload, win, dijit){
|
|
|
|
// module:
|
|
// dijit/registry
|
|
|
|
var _widgetTypeCtr = {}, hash = {};
|
|
|
|
var registry = {
|
|
// summary:
|
|
// Registry of existing widget on page, plus some utility methods.
|
|
|
|
// length: Number
|
|
// Number of registered widgets
|
|
length: 0,
|
|
|
|
add: function(widget){
|
|
// summary:
|
|
// Add a widget to the registry. If a duplicate ID is detected, a error is thrown.
|
|
// widget: dijit/_WidgetBase
|
|
// Any dijit/_WidgetBase subclass.
|
|
if(hash[widget.id]){
|
|
throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered");
|
|
}
|
|
hash[widget.id] = widget;
|
|
this.length++;
|
|
},
|
|
|
|
remove: function(/*String*/ id){
|
|
// summary:
|
|
// Remove a widget from the registry. Does not destroy the widget; simply
|
|
// removes the reference.
|
|
if(hash[id]){
|
|
delete hash[id];
|
|
this.length--;
|
|
}
|
|
},
|
|
|
|
byId: function(/*String|Widget*/ id){
|
|
// summary:
|
|
// Find a widget by it's id.
|
|
// If passed a widget then just returns the widget.
|
|
return typeof id == "string" ? hash[id] : id; // dijit/_WidgetBase
|
|
},
|
|
|
|
byNode: function(/*DOMNode*/ node){
|
|
// summary:
|
|
// Returns the widget corresponding to the given DOMNode
|
|
return hash[node.getAttribute("widgetId")]; // dijit/_WidgetBase
|
|
},
|
|
|
|
toArray: function(){
|
|
// summary:
|
|
// Convert registry into a true Array
|
|
//
|
|
// example:
|
|
// Work with the widget .domNodes in a real Array
|
|
// | array.map(registry.toArray(), function(w){ return w.domNode; });
|
|
|
|
var ar = [];
|
|
for(var id in hash){
|
|
ar.push(hash[id]);
|
|
}
|
|
return ar; // dijit/_WidgetBase[]
|
|
},
|
|
|
|
getUniqueId: function(/*String*/widgetType){
|
|
// summary:
|
|
// Generates a unique id for a given widgetType
|
|
|
|
var id;
|
|
do{
|
|
id = widgetType + "_" +
|
|
(widgetType in _widgetTypeCtr ?
|
|
++_widgetTypeCtr[widgetType] : _widgetTypeCtr[widgetType] = 0);
|
|
}while(hash[id]);
|
|
return dijit._scopeName == "dijit" ? id : dijit._scopeName + "_" + id; // String
|
|
},
|
|
|
|
findWidgets: function(root, skipNode){
|
|
// summary:
|
|
// Search subtree under root returning widgets found.
|
|
// Doesn't search for nested widgets (ie, widgets inside other widgets).
|
|
// root: DOMNode
|
|
// Node to search under.
|
|
// skipNode: DOMNode
|
|
// If specified, don't search beneath this node (usually containerNode).
|
|
|
|
var outAry = [];
|
|
|
|
function getChildrenHelper(root){
|
|
for(var node = root.firstChild; node; node = node.nextSibling){
|
|
if(node.nodeType == 1){
|
|
var widgetId = node.getAttribute("widgetId");
|
|
if(widgetId){
|
|
var widget = hash[widgetId];
|
|
if(widget){ // may be null on page w/multiple dojo's loaded
|
|
outAry.push(widget);
|
|
}
|
|
}else if(node !== skipNode){
|
|
getChildrenHelper(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
getChildrenHelper(root);
|
|
return outAry;
|
|
},
|
|
|
|
_destroyAll: function(){
|
|
// summary:
|
|
// Code to destroy all widgets and do other cleanup on page unload
|
|
|
|
// Clean up focus manager lingering references to widgets and nodes
|
|
dijit._curFocus = null;
|
|
dijit._prevFocus = null;
|
|
dijit._activeStack = [];
|
|
|
|
// Destroy all the widgets, top down
|
|
array.forEach(registry.findWidgets(win.body()), function(widget){
|
|
// Avoid double destroy of widgets like Menu that are attached to <body>
|
|
// even though they are logically children of other widgets.
|
|
if(!widget._destroyed){
|
|
if(widget.destroyRecursive){
|
|
widget.destroyRecursive();
|
|
}else if(widget.destroy){
|
|
widget.destroy();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
getEnclosingWidget: function(/*DOMNode*/ node){
|
|
// summary:
|
|
// Returns the widget whose DOM tree contains the specified DOMNode, or null if
|
|
// the node is not contained within the DOM tree of any widget
|
|
while(node){
|
|
var id = node.nodeType == 1 && node.getAttribute("widgetId");
|
|
if(id){
|
|
return hash[id];
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
// In case someone needs to access hash.
|
|
// Actually, this is accessed from WidgetSet back-compatibility code
|
|
_hash: hash
|
|
};
|
|
|
|
dijit.registry = registry;
|
|
|
|
return registry;
|
|
});
|
|
|
|
},
|
|
'dijit/tree/_dndContainer':function(){
|
|
define("dijit/tree/_dndContainer", [
|
|
"dojo/aspect", // aspect.after
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.add domClass.remove domClass.replace
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/_base/lang", // lang.mixin lang.hitch
|
|
"dojo/on",
|
|
"dojo/touch"
|
|
], function(aspect, declare,domClass, event, lang, on, touch){
|
|
|
|
// module:
|
|
// dijit/tree/_dndContainer
|
|
|
|
/*=====
|
|
var __Args = {
|
|
// summary:
|
|
// A dict of parameters for Tree source configuration.
|
|
// isSource: Boolean?
|
|
// Can be used as a DnD source. Defaults to true.
|
|
// accept: String[]
|
|
// List of accepted types (text strings) for a target; defaults to
|
|
// ["text", "treeNode"]
|
|
// copyOnly: Boolean?
|
|
// Copy items, if true, use a state of Ctrl key otherwise,
|
|
// dragThreshold: Number
|
|
// The move delay in pixels before detecting a drag; 0 by default
|
|
// betweenThreshold: Integer
|
|
// Distance from upper/lower edge of node to allow drop to reorder nodes
|
|
};
|
|
=====*/
|
|
|
|
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: __Args
|
|
// 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 = [
|
|
// Mouse (or touch) enter/leave on Tree itself
|
|
on(this.node, touch.enter, lang.hitch(this, "onOverEvent")),
|
|
on(this.node, touch.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", "");
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'url:dijit/templates/InlineEditBox.html':"<span data-dojo-attach-point=\"editNode\" role=\"presentation\" class=\"dijitReset dijitInline dijitOffScreen\"\n\tdata-dojo-attach-event=\"onkeypress: _onKeyPress\"\n\t><span data-dojo-attach-point=\"editorPlaceholder\"></span\n\t><span data-dojo-attach-point=\"buttonContainer\"\n\t\t><button data-dojo-type=\"dijit/form/Button\" data-dojo-props=\"label: '${buttonSave}', 'class': 'saveButton'\"\n\t\t\tdata-dojo-attach-point=\"saveButton\" data-dojo-attach-event=\"onClick:save\"></button\n\t\t><button data-dojo-type=\"dijit/form/Button\" data-dojo-props=\"label: '${buttonCancel}', 'class': 'cancelButton'\"\n\t\t\tdata-dojo-attach-point=\"cancelButton\" data-dojo-attach-event=\"onClick:cancel\"></button\n\t></span\n></span>\n",
|
|
'dijit/_base/wai':function(){
|
|
define("dijit/_base/wai", [
|
|
"dojo/dom-attr", // domAttr.attr
|
|
"dojo/_base/lang", // lang.mixin
|
|
"../main", // export symbols to dijit
|
|
"../hccss" // not using this module directly, but loading it sets CSS flag on <html>
|
|
], function(domAttr, lang, dijit){
|
|
|
|
// module:
|
|
// dijit/_base/wai
|
|
|
|
var exports = {
|
|
// summary:
|
|
// Deprecated methods for setting/getting wai roles and states.
|
|
// New code should call setAttribute()/getAttribute() directly.
|
|
//
|
|
// Also loads hccss to apply dj_a11y class to root node if machine is in high-contrast mode.
|
|
|
|
hasWaiRole: function(/*Element*/ elem, /*String?*/ role){
|
|
// summary:
|
|
// Determines if an element has a particular role.
|
|
// returns:
|
|
// True if elem has the specific role attribute and false if not.
|
|
// For backwards compatibility if role parameter not provided,
|
|
// returns true if has a role
|
|
var waiRole = this.getWaiRole(elem);
|
|
return role ? (waiRole.indexOf(role) > -1) : (waiRole.length > 0);
|
|
},
|
|
|
|
getWaiRole: function(/*Element*/ elem){
|
|
// summary:
|
|
// Gets the role for an element (which should be a wai role).
|
|
// returns:
|
|
// The role of elem or an empty string if elem
|
|
// does not have a role.
|
|
return lang.trim((domAttr.get(elem, "role") || "").replace("wairole:",""));
|
|
},
|
|
|
|
setWaiRole: function(/*Element*/ elem, /*String*/ role){
|
|
// summary:
|
|
// Sets the role on an element.
|
|
// description:
|
|
// Replace existing role attribute with new role.
|
|
|
|
domAttr.set(elem, "role", role);
|
|
},
|
|
|
|
removeWaiRole: function(/*Element*/ elem, /*String*/ role){
|
|
// summary:
|
|
// Removes the specified role from an element.
|
|
// Removes role attribute if no specific role provided (for backwards compat.)
|
|
|
|
var roleValue = domAttr.get(elem, "role");
|
|
if(!roleValue){ return; }
|
|
if(role){
|
|
var t = lang.trim((" " + roleValue + " ").replace(" " + role + " ", " "));
|
|
domAttr.set(elem, "role", t);
|
|
}else{
|
|
elem.removeAttribute("role");
|
|
}
|
|
},
|
|
|
|
hasWaiState: function(/*Element*/ elem, /*String*/ state){
|
|
// summary:
|
|
// Determines if an element has a given state.
|
|
// description:
|
|
// Checks for an attribute called "aria-"+state.
|
|
// returns:
|
|
// true if elem has a value for the given state and
|
|
// false if it does not.
|
|
|
|
return elem.hasAttribute ? elem.hasAttribute("aria-"+state) : !!elem.getAttribute("aria-"+state);
|
|
},
|
|
|
|
getWaiState: function(/*Element*/ elem, /*String*/ state){
|
|
// summary:
|
|
// Gets the value of a state on an element.
|
|
// description:
|
|
// Checks for an attribute called "aria-"+state.
|
|
// returns:
|
|
// The value of the requested state on elem
|
|
// or an empty string if elem has no value for state.
|
|
|
|
return elem.getAttribute("aria-"+state) || "";
|
|
},
|
|
|
|
setWaiState: function(/*Element*/ elem, /*String*/ state, /*String*/ value){
|
|
// summary:
|
|
// Sets a state on an element.
|
|
// description:
|
|
// Sets an attribute called "aria-"+state.
|
|
|
|
elem.setAttribute("aria-"+state, value);
|
|
},
|
|
|
|
removeWaiState: function(/*Element*/ elem, /*String*/ state){
|
|
// summary:
|
|
// Removes a state from an element.
|
|
// description:
|
|
// Sets an attribute called "aria-"+state.
|
|
|
|
elem.removeAttribute("aria-"+state);
|
|
}
|
|
};
|
|
|
|
lang.mixin(dijit, exports);
|
|
|
|
/*===== return exports; =====*/
|
|
return dijit; // for back compat :-(
|
|
});
|
|
|
|
},
|
|
'dijit/form/_FormSelectWidget':function(){
|
|
define("dijit/form/_FormSelectWidget", [
|
|
"dojo/_base/array", // array.filter array.forEach array.map array.some
|
|
"dojo/_base/Deferred",
|
|
"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
|
|
"dojo/when",
|
|
"dojo/store/util/QueryResults",
|
|
"./_FormValueWidget"
|
|
], function(array, Deferred, aspect, sorter, declare, dom, domClass, kernel, lang, query, when,
|
|
QueryResults, _FormValueWidget){
|
|
|
|
// module:
|
|
// dijit/form/_FormSelectWidget
|
|
|
|
/*=====
|
|
var __SelectOption = {
|
|
// 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
|
|
};
|
|
=====*/
|
|
|
|
var _FormSelectWidget = 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: __SelectOption[]
|
|
// The set of options for our select item. Roughly corresponds to
|
|
// the html `<option>` tag.
|
|
options: null,
|
|
|
|
// store: dojo/store/api/Store
|
|
// A store to use for getting our list of options - rather than reading them
|
|
// from the `<option>` html tags. Should support getIdentity().
|
|
// For back-compat store can also be a dojo/data/api/Identity.
|
|
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,
|
|
|
|
// labelAttr: String?
|
|
// The entries in the drop down list come from this attribute in the dojo.store items.
|
|
// If ``store`` is set, labelAttr must be set too, unless store is an old-style
|
|
// dojo.data store rather than a new dojo/store.
|
|
labelAttr: "",
|
|
|
|
// 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,
|
|
|
|
// onLoadDeferred: [readonly] dojo.Deferred
|
|
// This is the `dojo.Deferred` returned by setStore().
|
|
// Calling onLoadDeferred.then() registers your
|
|
// callback to be called only once, when the prior setStore completes.
|
|
onLoadDeferred: null,
|
|
|
|
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/_FormSelectWidget.__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/_FormSelectWidget.__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/_FormSelectWidget.__SelectOption is passed in which is not a part of the select
|
|
|
|
// NOTE: the compare for passing in a dijit/form/_FormSelectWidget.__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; // __SelectOption[]
|
|
}
|
|
if(lang.isArray(lookupValue)){
|
|
return array.map(lookupValue, "return this.getOptions(item);", this); // __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]; // __SelectOption
|
|
}
|
|
return null; // null
|
|
},
|
|
|
|
addOption: function(/*__SelectOption|__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|__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(/*__SelectOption|__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(store,
|
|
selectedValue,
|
|
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/store/api/Store
|
|
// The dojo.store you would like to use - it MUST implement getIdentity()
|
|
// and MAY implement observe().
|
|
// For backwards-compatibility this can also be a data.data store, in which case
|
|
// 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?
|
|
// Hash of parameters to set filter on store, etc.
|
|
//
|
|
// - query: new value for Select.query,
|
|
// - queryOptions: new value for Select.queryOptions,
|
|
// - onFetch: callback function for each item in data (Deprecated)
|
|
var oStore = this.store;
|
|
fetchArgs = fetchArgs || {};
|
|
|
|
if(oStore !== store){
|
|
// Our store has changed, so cancel any listeners on old store (remove for 2.0)
|
|
var h;
|
|
while((h = this._notifyConnections.pop())){ h.remove(); }
|
|
|
|
// 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(){ if(fetchHandle.abort){ fetchHandle.abort(); } } );
|
|
deferred.total = new Deferred();
|
|
var fetchHandle = this.fetch(lang.mixin({
|
|
query: query,
|
|
onBegin: function(count){
|
|
deferred.total.resolve(count);
|
|
},
|
|
onComplete: function(results){
|
|
deferred.resolve(results);
|
|
},
|
|
onError: function(error){
|
|
deferred.reject(error);
|
|
}
|
|
}, options));
|
|
return new QueryResults(deferred);
|
|
}
|
|
});
|
|
|
|
if(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); // Our store has changed, so update our notifications
|
|
}
|
|
|
|
// Remove existing options (if there are any)
|
|
if(this.options && this.options.length){
|
|
this.removeOption(this.options);
|
|
}
|
|
|
|
// Cancel listener for updates to old store
|
|
if(this._queryRes && this._queryRes.close){
|
|
this._queryRes.close();
|
|
}
|
|
|
|
// If user has specified new query and query options along with this new store, then use them.
|
|
if(fetchArgs.query){
|
|
this._set("query", fetchArgs.query);
|
|
this._set("queryOptions", fetchArgs.queryOptions);
|
|
}
|
|
|
|
// Add our new options
|
|
if(store){
|
|
this._loadingStore = true;
|
|
this.onLoadDeferred = new Deferred();
|
|
|
|
// Run query
|
|
// Save result in this._queryRes so we can cancel the listeners we register below
|
|
this._queryRes = store.query(this.query, this.queryOptions);
|
|
when(this._queryRes, lang.hitch(this, function(items){
|
|
|
|
if(this.sortByLabel && !fetchArgs.sort && items.length){
|
|
if(items[0].getValue){
|
|
// Old dojo.data API to access items, remove for 2.0
|
|
items.sort(sorter.createSortFunction([{
|
|
attribute: store.getLabelAttributes(items[0])[0]
|
|
}], store));
|
|
}else{
|
|
var labelAttr = this.labelAttr;
|
|
items.sort(function(a, b){
|
|
return a[labelAttr] > b[labelAttr] ? 1 : b[labelAttr] > a[labelAttr] ? -1 : 0;
|
|
});
|
|
}
|
|
}
|
|
|
|
if(fetchArgs.onFetch){
|
|
items = fetchArgs.onFetch.call(this, items, fetchArgs);
|
|
}
|
|
|
|
// TODO: Add these guys as a batch, instead of separately
|
|
array.forEach(items, function(i){
|
|
this._addOptionForItem(i);
|
|
}, this);
|
|
|
|
// Register listener for store updates
|
|
if(this._queryRes.observe){
|
|
this._queryRes.observe(lang.hitch(this, function(object, deletedFrom, insertedInto){
|
|
if(deletedFrom == insertedInto){
|
|
this._onSetItem(object);
|
|
}else{
|
|
if(deletedFrom != -1){
|
|
this._onDeleteItem(object);
|
|
}
|
|
if(insertedInto != -1){
|
|
this._onNewItem(object);
|
|
}
|
|
}
|
|
}), true);
|
|
}
|
|
|
|
// 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.onLoadDeferred.resolve(true);
|
|
this.onSetStore();
|
|
}), function(err){
|
|
console.error('dijit.form.Select: ' + err.toString());
|
|
this.onLoadDeferred.reject(err);
|
|
});
|
|
}
|
|
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._onChangeActive){ priorityChange = null; }
|
|
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; });
|
|
|
|
if(typeof val == "undefined" || typeof val[0] == "undefined"){ return; } // not fully initialized yet or a failed value lookup
|
|
this._setDisplay(this.multiple ? disp : disp[0]);
|
|
this.inherited(arguments, [ this.multiple ? val : val[0], priorityChange ]);
|
|
this._updateSelection();
|
|
},
|
|
|
|
_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.replace(/\s+|$/g, "SelectedOption "), isSelected);
|
|
child.domNode.setAttribute("aria-selected", isSelected ? "true" : "false");
|
|
}, 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.
|
|
|
|
// remove getLabel() call for 2.0 (it's to support the old dojo.data API)
|
|
var store = this.store,
|
|
label = (this.labelAttr && this.labelAttr in item) ? item[this.labelAttr] : store.getLabel(item),
|
|
value = (label ? store.getIdentity(item) : null);
|
|
return {value: value, label: label, item: item}; // __SelectOption
|
|
},
|
|
|
|
_addOptionForItem: function(/*item*/ item){
|
|
// summary:
|
|
// Creates (and adds) the option for the given item
|
|
var store = this.store;
|
|
if(store.isItemLoaded && !store.isItemLoaded(item)){
|
|
// We are not loaded - so let's load it and add later.
|
|
// Remove for 2.0 (it's the old dojo.data API)
|
|
store.loadItem({item: item, onItem: function(i){
|
|
this._addOptionForItem(i);
|
|
},
|
|
scope: this});
|
|
return;
|
|
}
|
|
var newOpt = this._getOptionObjForItem(item);
|
|
this.addOption(newOpt);
|
|
},
|
|
|
|
constructor: function(params /*===== , srcNodeRef =====*/){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree
|
|
|
|
// 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 = (params || {}).value || null;
|
|
this._notifyConnections = []; // remove for 2.0
|
|
},
|
|
|
|
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.
|
|
if(!this.options){
|
|
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");
|
|
|
|
// moved from startup
|
|
// Connects in our store, if we have one defined
|
|
var store = this.store;
|
|
if(store && (store.getIdentity || 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);
|
|
}
|
|
},
|
|
|
|
startup: function(){
|
|
// summary:
|
|
this._loadChildren();
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
destroy: function(){
|
|
// summary:
|
|
// Clean up our connections
|
|
|
|
var h;
|
|
while((h = this._notifyConnections.pop())){ h.remove(); }
|
|
|
|
// Cancel listener for store updates
|
|
if(this._queryRes && this._queryRes.close){
|
|
this._queryRes.close();
|
|
}
|
|
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
_addOptionItem: function(/*__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(/*__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
|
|
}
|
|
});
|
|
|
|
/*=====
|
|
_FormSelectWidget.__SelectOption = __SelectOption;
|
|
=====*/
|
|
|
|
return _FormSelectWidget;
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/Select':function(){
|
|
require({cache:{
|
|
'url:dijit/form/templates/Select.html':"<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdata-dojo-attach-point=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\trole=\"listbox\" aria-haspopup=\"true\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents\" role=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitInputField dijitButtonText\" data-dojo-attach-point=\"containerNode,_popupStateNode\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitValidationContainer\"\n\t\t\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t/></div\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} data-dojo-attach-point=\"valueNode\" value=\"${value}\" aria-hidden=\"true\"\n\t\t/></td\n\t\t><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer\"\n\t\t\tdata-dojo-attach-point=\"titleNode\" role=\"presentation\"\n\t\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t\t${_buttonInputDisabled}\n\t\t/></td\n\t></tr></tbody\n></table>\n"}});
|
|
define("dijit/form/Select", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr", // domAttr.set
|
|
"dojo/dom-class", // domClass.add domClass.remove domClass.toggle
|
|
"dojo/dom-geometry", // domGeometry.setMarginBox
|
|
"dojo/_base/event", // event.stop
|
|
"dojo/i18n", // i18n.getLocalization
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/sniff", // has("ie")
|
|
"./_FormSelectWidget",
|
|
"../_HasDropDown",
|
|
"../Menu",
|
|
"../MenuItem",
|
|
"../MenuSeparator",
|
|
"../Tooltip",
|
|
"dojo/text!./templates/Select.html",
|
|
"dojo/i18n!./nls/validate"
|
|
], function(array, declare, domAttr, domClass, domGeometry, event, i18n, lang, has,
|
|
_FormSelectWidget, _HasDropDown, Menu, MenuItem, MenuSeparator, Tooltip, template){
|
|
|
|
// module:
|
|
// dijit/form/Select
|
|
|
|
|
|
var _SelectMenu = declare("dijit.form._SelectMenu", Menu, {
|
|
// summary:
|
|
// An internally-used menu for dropdown that allows us a vertical scrollbar
|
|
|
|
// Override Menu.autoFocus setting so that opening a Select highlights the current value.
|
|
autoFocus: true,
|
|
|
|
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 = this.ownerDocument.createElement("div"));
|
|
n.style.cssText = "overflow-x: hidden; overflow-y: 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, "onselectstart", event.stop);
|
|
},
|
|
|
|
|
|
focus: function(){
|
|
// summary:
|
|
// Overridden so that the previously selected value will be focused instead of only the first item
|
|
var found = false,
|
|
val = this.parentWidget.value;
|
|
if(lang.isArray(val)){
|
|
val = val[val.length-1];
|
|
}
|
|
if(val){ // if focus selected
|
|
array.forEach(this.parentWidget._getChildren(), function(child){
|
|
if(child.option && (val === child.option.value)){ // find menu item widget with this value
|
|
found = true;
|
|
this.focusChild(child, false); // focus previous selection
|
|
}
|
|
}, this);
|
|
}
|
|
if(!found){
|
|
this.inherited(arguments); // focus first item by default
|
|
}
|
|
},
|
|
|
|
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 dijitValidationTextBox",
|
|
|
|
templateString: template,
|
|
|
|
_buttonInputDisabled: has("ie") ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events
|
|
|
|
// 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", parentWidget: this });
|
|
domClass.add(this.dropDown.domNode, this.baseClass.replace(/\s+|$/g, "Menu "));
|
|
},
|
|
|
|
_getMenuItemForOption: function(/*_FormSelectWidget.__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({ownerDocument: this.ownerDocument});
|
|
}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,
|
|
ownerDocument: this.ownerDocument,
|
|
dir: this.dir,
|
|
disabled: option.disabled || false
|
|
});
|
|
item.focusNode.setAttribute("role", "option");
|
|
return item;
|
|
}
|
|
},
|
|
|
|
_addOptionItem: function(/*_FormSelectWidget.__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({
|
|
ownerDocument: this.ownerDocument,
|
|
label: this.emptyLabel
|
|
});
|
|
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, false);
|
|
}
|
|
},
|
|
|
|
_refreshState: function(){
|
|
if(this._started){
|
|
this.validate(this.focused);
|
|
}
|
|
},
|
|
|
|
startup: function(){
|
|
this.inherited(arguments);
|
|
this._refreshState(); // after all _set* methods have run
|
|
},
|
|
|
|
_setValueAttr: function(value){
|
|
this.inherited(arguments);
|
|
domAttr.set(this.valueNode, "value", this.get("value"));
|
|
this._refreshState(); // to update this.state
|
|
},
|
|
|
|
_setDisabledAttr: function(/*Boolean*/ value){
|
|
this.inherited(arguments);
|
|
this._refreshState(); // to update this.state
|
|
},
|
|
|
|
_setRequiredAttr: function(/*Boolean*/ value){
|
|
this._set("required", value);
|
|
this.focusNode.setAttribute("aria-required", value);
|
|
this._refreshState(); // to update this.state
|
|
},
|
|
|
|
_setOptionsAttr: function(/*Array*/ options){
|
|
this._isLoaded = false;
|
|
this._set('options', options);
|
|
},
|
|
|
|
_setDisplay: function(/*String*/ newDisplay){
|
|
// summary:
|
|
// sets the display for the given value (or values)
|
|
var lbl = newDisplay || this.emptyLabel;
|
|
this.containerNode.innerHTML = '<span role="option" class="dijitReset dijitInline ' + this.baseClass.replace(/\s+|$/g, "Label ")+'">' + lbl + '</span>';
|
|
},
|
|
|
|
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 ? "" : (this._hasBeenBlurred ? "Error" : "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._refreshState(); // 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, "onselectstart", event.stop);
|
|
this.domNode.setAttribute("aria-expanded", "false");
|
|
|
|
if(has("ie") < 9){
|
|
// IE INPUT tag fontFamily has to be set directly using STYLE
|
|
// the defer gives IE a chance to render the TextBox and to deal with font inheritance
|
|
this.defer(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.*/}
|
|
});
|
|
}
|
|
},
|
|
|
|
_setStyleAttr: function(/*String||Object*/ value){
|
|
this.inherited(arguments);
|
|
domClass.toggle(this.domNode, this.baseClass.replace(/\s+|$/g, "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 = "";
|
|
}
|
|
},
|
|
|
|
destroy: 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);
|
|
this.validate(false);
|
|
}
|
|
});
|
|
|
|
Select._Menu = _SelectMenu; // for monkey patching
|
|
|
|
return Select;
|
|
});
|
|
|
|
},
|
|
'dojo/store/util/QueryResults':function(){
|
|
define("dojo/store/util/QueryResults", ["../../_base/array", "../../_base/lang", "../../_base/Deferred"
|
|
], function(array, lang, Deferred){
|
|
|
|
// module:
|
|
// dojo/store/util/QueryResults
|
|
|
|
var 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`.
|
|
// results: Array|dojo/promise/Promise
|
|
// The result set as an array, or a promise for an array.
|
|
// returns:
|
|
// 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 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; // Object
|
|
};
|
|
|
|
lang.setObject("dojo.store.util.QueryResults", QueryResults);
|
|
|
|
return QueryResults;
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/_ListBase':function(){
|
|
define("dijit/form/_ListBase", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/on",
|
|
"dojo/window" // winUtils.scrollIntoView
|
|
], function(declare, on, winUtils){
|
|
|
|
// module:
|
|
// dijit/form/_ListBase
|
|
|
|
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,
|
|
|
|
_listConnect: function(/*String|Function*/ eventType, /*String*/ callbackFuncName){
|
|
// summary:
|
|
// Connects 'containerNode' to specified method of this object
|
|
// and automatically registers for 'disconnect' on widget destroy.
|
|
// description:
|
|
// Provide widget-specific analog to 'connect'.
|
|
// The callback function is called with the normal event object,
|
|
// but also a second parameter is passed that indicates which list item
|
|
// actually received the event.
|
|
// returns:
|
|
// A handle that can be passed to `disconnect` in order to disconnect
|
|
// before the widget is destroyed.
|
|
// tags:
|
|
// private
|
|
|
|
var self = this;
|
|
return self.own(on(self.containerNode,
|
|
on.selector(
|
|
function(eventTarget, selector, target){
|
|
return eventTarget.parentNode == target;
|
|
},
|
|
eventType
|
|
),
|
|
function(evt){
|
|
evt.preventDefault();
|
|
self[callbackFuncName](evt, this);
|
|
}
|
|
));
|
|
},
|
|
|
|
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.selected;
|
|
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.selected;
|
|
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.selected;
|
|
if(selectedNode){
|
|
this.onDeselect(selectedNode);
|
|
this.selected = null;
|
|
}
|
|
if(node){
|
|
this.selected = node;
|
|
winUtils.scrollIntoView(node);
|
|
this.onSelect(node);
|
|
}
|
|
}else if(node){
|
|
this.onSelect(node);
|
|
}
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dijit/form/_FormWidget':function(){
|
|
define("dijit/form/_FormWidget", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/has", // has("dijit-legacy-requires")
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/ready",
|
|
"../_Widget",
|
|
"../_CssStateMixin",
|
|
"../_TemplatedMixin",
|
|
"./_FormWidgetMixin"
|
|
], function(declare, has, kernel, ready, _Widget, _CssStateMixin, _TemplatedMixin, _FormWidgetMixin){
|
|
|
|
|
|
// module:
|
|
// dijit/form/_FormWidget
|
|
|
|
// Back compat w/1.6, remove for 2.0
|
|
if(has("dijit-legacy-requires")){
|
|
ready(0, function(){
|
|
var requires = ["dijit/form/_FormValueWidget"];
|
|
require(requires); // use indirection so modules not rolled into a build
|
|
});
|
|
}
|
|
|
|
return declare("dijit.form._FormWidget", [_Widget, _TemplatedMixin, _CssStateMixin, _FormWidgetMixin], {
|
|
// summary:
|
|
// Base class for widgets corresponding to native HTML elements such as `<checkbox>` or `<button>`,
|
|
// which can be children of a `<form>` node or a `dijit/form/Form` widget.
|
|
//
|
|
// description:
|
|
// Represents a single HTML element.
|
|
// All these widgets should have these attributes just like native HTML input elements.
|
|
// You can set them during widget construction or afterwards, via `dijit/_WidgetBase.set()`.
|
|
//
|
|
// They also share some common methods.
|
|
|
|
setDisabled: function(/*Boolean*/ disabled){
|
|
// summary:
|
|
// Deprecated. Use set('disabled', ...) instead.
|
|
kernel.deprecated("setDisabled("+disabled+") is deprecated. Use set('disabled',"+disabled+") instead.", "", "2.0");
|
|
this.set('disabled', disabled);
|
|
},
|
|
|
|
setValue: function(/*String*/ value){
|
|
// summary:
|
|
// Deprecated. Use set('value', ...) instead.
|
|
kernel.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use set('value',"+value+") instead.", "", "2.0");
|
|
this.set('value', value);
|
|
},
|
|
|
|
getValue: function(){
|
|
// summary:
|
|
// Deprecated. Use get('value') instead.
|
|
kernel.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.", "", "2.0");
|
|
return this.get('value');
|
|
},
|
|
|
|
postMixInProperties: function(){
|
|
// Setup name=foo string to be referenced from the template (but only if a name has been specified)
|
|
// Unfortunately we can't use _setNameAttr to set the name due to IE limitations, see #8484, #8660.
|
|
// Regarding escaping, see heading "Attribute values" in
|
|
// http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
|
|
this.nameAttrSetting = this.name ? ('name="' + this.name.replace(/"/g, """) + '"') : '';
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
// Override automatic assigning type --> focusNode, it causes exception on IE.
|
|
// Instead, type must be specified as ${type} in the template, as part of the original DOM
|
|
_setTypeAttr: null
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'dojo/DeferredList':function(){
|
|
define("dojo/DeferredList", ["./_base/kernel", "./_base/Deferred", "./_base/array"], function(dojo, Deferred, darray){
|
|
// module:
|
|
// dojo/DeferredList
|
|
|
|
|
|
dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){
|
|
// summary:
|
|
// Deprecated, use dojo/promise/all instead.
|
|
// 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("dojo/dnd/common", ["../_base/connect", "../_base/kernel", "../_base/lang", "../dom"],
|
|
function(connect, kernel, lang, dom){
|
|
|
|
// module:
|
|
// dojo/dnd/common
|
|
|
|
var exports = lang.getObject("dojo.dnd", true);
|
|
/*=====
|
|
// TODO: for 2.0, replace line above with this code.
|
|
var exports = {
|
|
// summary:
|
|
// TODOC
|
|
};
|
|
=====*/
|
|
|
|
exports.getCopyKeyState = connect.isCopyKey;
|
|
|
|
exports._uniqueId = 0;
|
|
exports.getUniqueId = function(){
|
|
// summary:
|
|
// returns a unique string for use with any DOM element
|
|
var id;
|
|
do{
|
|
id = kernel._scopeName + "Unique" + (++exports._uniqueId);
|
|
}while(dom.byId(id));
|
|
return id;
|
|
};
|
|
|
|
exports._empty = {};
|
|
|
|
exports.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 exports;
|
|
});
|
|
|
|
},
|
|
'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<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){
|
|
|
|
// module:
|
|
// dijit/CheckedMenuItem
|
|
|
|
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 ? "true" : "false");
|
|
this._set("checked", checked);
|
|
},
|
|
|
|
iconClass: "", // override dijitNoIcon
|
|
|
|
onChange: function(/*Boolean*/ /*===== checked =====*/){
|
|
// summary:
|
|
// User defined function to handle check/uncheck events
|
|
// tags:
|
|
// callback
|
|
},
|
|
|
|
_onClick: function(evt){
|
|
// summary:
|
|
// Clicking this item just toggles its state
|
|
// tags:
|
|
// private
|
|
if(!this.disabled){
|
|
this.set("checked", !this.checked);
|
|
this.onChange(this.checked);
|
|
}
|
|
this.onClick(evt);
|
|
}
|
|
});
|
|
});
|
|
|
|
},
|
|
'dijit/Viewport':function(){
|
|
define("dijit/Viewport", [
|
|
"dojo/Evented",
|
|
"dojo/on",
|
|
"dojo/ready",
|
|
"dojo/sniff",
|
|
"dojo/_base/window", // global
|
|
"dojo/window" // getBox()
|
|
], function(Evented, on, ready, has, win, winUtils){
|
|
|
|
// module:
|
|
// dijit/Viewport
|
|
|
|
/*=====
|
|
return {
|
|
// summary:
|
|
// Utility singleton to watch for viewport resizes, avoiding duplicate notifications
|
|
// which can lead to infinite loops.
|
|
// description:
|
|
// Usage: Viewport.on("resize", myCallback).
|
|
//
|
|
// myCallback() is called without arguments in case it's _WidgetBase.resize(),
|
|
// which would interpret the argument as the size to make the widget.
|
|
};
|
|
=====*/
|
|
|
|
var Viewport = new Evented();
|
|
|
|
ready(200, function(){
|
|
var oldBox = winUtils.getBox();
|
|
Viewport._rlh = on(win.global, "resize", function(){
|
|
var newBox = winUtils.getBox();
|
|
if(oldBox.h == newBox.h && oldBox.w == newBox.w){ return; }
|
|
oldBox = newBox;
|
|
Viewport.emit("resize");
|
|
});
|
|
|
|
// Also catch zoom changes on IE8, since they don't naturally generate resize events
|
|
if(has("ie") == 8){
|
|
var deviceXDPI = screen.deviceXDPI;
|
|
setInterval(function(){
|
|
if(screen.deviceXDPI != deviceXDPI){
|
|
deviceXDPI = screen.deviceXDPI;
|
|
Viewport.emit("resize");
|
|
}
|
|
}, 500);
|
|
}
|
|
});
|
|
|
|
return Viewport;
|
|
});
|
|
|
|
},
|
|
'dijit/_base/place':function(){
|
|
define("dijit/_base/place", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/_base/lang", // lang.isArray, lang.mixin
|
|
"dojo/window", // windowUtils.getBox
|
|
"../place",
|
|
"../main" // export to dijit namespace
|
|
], function(array, lang, windowUtils, place, dijit){
|
|
|
|
// module:
|
|
// dijit/_base/place
|
|
|
|
|
|
var exports = {
|
|
// summary:
|
|
// Deprecated back compatibility module, new code should use dijit/place directly instead of using this module.
|
|
};
|
|
|
|
exports.getViewport = function(){
|
|
// summary:
|
|
// Deprecated method to return the dimensions and scroll position of the viewable area of a browser window.
|
|
// New code should use windowUtils.getBox()
|
|
|
|
return windowUtils.getBox();
|
|
};
|
|
|
|
exports.placeOnScreen = place.at;
|
|
|
|
exports.placeOnScreenAroundElement = function(node, aroundNode, aroundCorners, layoutNode){
|
|
// summary:
|
|
// Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object
|
|
// for the "around" argument and finds a proper processor to place a node.
|
|
// Deprecated, new code should use dijit/place.around() instead.
|
|
|
|
// Convert old style {"BL": "TL", "BR": "TR"} type argument
|
|
// to style needed by dijit.place code:
|
|
// [
|
|
// {aroundCorner: "BL", corner: "TL" },
|
|
// {aroundCorner: "BR", corner: "TR" }
|
|
// ]
|
|
var positions;
|
|
if(lang.isArray(aroundCorners)){
|
|
positions = aroundCorners;
|
|
}else{
|
|
positions = [];
|
|
for(var key in aroundCorners){
|
|
positions.push({aroundCorner: key, corner: aroundCorners[key]});
|
|
}
|
|
}
|
|
|
|
return place.around(node, aroundNode, positions, true, layoutNode);
|
|
};
|
|
|
|
exports.placeOnScreenAroundNode = exports.placeOnScreenAroundElement;
|
|
/*=====
|
|
exports.placeOnScreenAroundNode = function(node, aroundNode, aroundCorners, layoutNode){
|
|
// summary:
|
|
// Position node adjacent or kitty-corner to aroundNode
|
|
// such that it's fully visible in viewport.
|
|
// Deprecated, new code should use dijit/place.around() instead.
|
|
};
|
|
=====*/
|
|
|
|
exports.placeOnScreenAroundRectangle = exports.placeOnScreenAroundElement;
|
|
/*=====
|
|
exports.placeOnScreenAroundRectangle = function(node, aroundRect, aroundCorners, layoutNode){
|
|
// summary:
|
|
// Like dijit.placeOnScreenAroundNode(), except that the "around"
|
|
// parameter is an arbitrary rectangle on the screen (x, y, width, height)
|
|
// instead of a dom node.
|
|
// Deprecated, new code should use dijit/place.around() instead.
|
|
};
|
|
=====*/
|
|
|
|
exports.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToRight){
|
|
// summary:
|
|
// Deprecated method, unneeded when using dijit/place directly.
|
|
// Transforms the passed array of preferred positions into a format suitable for
|
|
// passing as the aroundCorners argument to dijit/place.placeOnScreenAroundElement.
|
|
// position: String[]
|
|
// This variable controls the position of the drop down.
|
|
// It's an array of strings with the following values:
|
|
//
|
|
// - before: places drop down to the left of the target node/widget, or to the right in
|
|
// the case of RTL scripts like Hebrew and Arabic
|
|
// - after: places drop down to the right of the target node/widget, or to the left in
|
|
// the case of RTL scripts like Hebrew and Arabic
|
|
// - above: drop down goes above target node
|
|
// - below: drop down goes below target node
|
|
//
|
|
// The list is positions is tried, in order, until a position is found where the drop down fits
|
|
// within the viewport.
|
|
// leftToRight: Boolean
|
|
// Whether the popup will be displaying in leftToRight mode.
|
|
|
|
var align = {};
|
|
array.forEach(position, function(pos){
|
|
var ltr = leftToRight;
|
|
switch(pos){
|
|
case "after":
|
|
align[leftToRight ? "BR" : "BL"] = leftToRight ? "BL" : "BR";
|
|
break;
|
|
case "before":
|
|
align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL";
|
|
break;
|
|
case "below-alt":
|
|
ltr = !ltr;
|
|
// fall through
|
|
case "below":
|
|
// first try to align left borders, next try to align right borders (or reverse for RTL mode)
|
|
align[ltr ? "BL" : "BR"] = ltr ? "TL" : "TR";
|
|
align[ltr ? "BR" : "BL"] = ltr ? "TR" : "TL";
|
|
break;
|
|
case "above-alt":
|
|
ltr = !ltr;
|
|
// fall through
|
|
case "above":
|
|
default:
|
|
// first try to align left borders, next try to align right borders (or reverse for RTL mode)
|
|
align[ltr ? "TL" : "TR"] = ltr ? "BL" : "BR";
|
|
align[ltr ? "TR" : "TL"] = ltr ? "BR" : "BL";
|
|
break;
|
|
}
|
|
});
|
|
return align;
|
|
};
|
|
|
|
lang.mixin(dijit, exports);
|
|
|
|
/*===== return exports; =====*/
|
|
return dijit; // for back compat :-(
|
|
});
|
|
|
|
},
|
|
'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){
|
|
|
|
// module:
|
|
// dijit/MenuSeparator
|
|
|
|
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("dijit/form/_ComboBoxMenu", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.add domClass.remove
|
|
"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, domStyle, keys,
|
|
_WidgetBase, _TemplatedMixin, _ComboBoxMenuMixin, _ListMouseMixin){
|
|
|
|
|
|
// module:
|
|
// dijit/form/_ComboBoxMenu
|
|
|
|
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;' role='listbox'>"
|
|
+"<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(){
|
|
// note: not using domConstruct.create() because need to specify document
|
|
var item = this.ownerDocument.createElement("div");
|
|
item.className = "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl");
|
|
item.setAttribute("role", "option");
|
|
return item;
|
|
},
|
|
|
|
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.keyCode){
|
|
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\t<span data-dojo-attach-point=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"\n\t\t\t\trole=\"heading\" level=\"1\"></span>\n\t\t<span data-dojo-attach-point=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" data-dojo-attach-event=\"ondijitclick: onCancel\" title=\"${buttonCancel}\" role=\"button\" tabIndex=\"-1\">\n\t\t\t<span data-dojo-attach-point=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t\t</span>\n\t</div>\n\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/keys",
|
|
"dojo/_base/lang", // lang.mixin lang.hitch
|
|
"dojo/on",
|
|
"dojo/ready",
|
|
"dojo/sniff", // has("ie") has("opera") has("dijit-legacy-requires")
|
|
"dojo/window", // winUtils.getBox, winUtils.get
|
|
"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",
|
|
"./main", // 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, keys, lang, on, ready, has, winUtils,
|
|
Moveable, TimedMoveable, focus, manager, _Widget, _TemplatedMixin, _CssStateMixin, _FormMixin, _DialogMixin,
|
|
DialogUnderlay, ContentPane, template, dijit){
|
|
|
|
// module:
|
|
// dijit/Dialog
|
|
|
|
/*=====
|
|
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], {
|
|
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,
|
|
|
|
_setDraggableAttr: function(/*Boolean*/ val){
|
|
// Avoid _WidgetBase behavior of copying draggable attribute to this.domNode,
|
|
// as that prevents text select on modern browsers (#14452)
|
|
this._set("draggable", val);
|
|
},
|
|
|
|
// 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": "",
|
|
|
|
// maxRatio: Number
|
|
// Maximum size to allow the dialog to expand to, relative to viewport size
|
|
maxRatio: 0.9,
|
|
|
|
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"
|
|
});
|
|
this.ownerDocumentBody.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);
|
|
},
|
|
|
|
_onBlur: function(by){
|
|
this.inherited(arguments);
|
|
|
|
// If focus was accidentally removed from the dialog, such as if the user clicked a blank
|
|
// area of the screen, or clicked the browser's address bar and then tabbed into the page,
|
|
// then refocus. Won't do anything if focus was removed because the Dialog was closed, or
|
|
// because a new Dialog popped up on top of the old one.
|
|
var refocus = lang.hitch(this, function(){
|
|
if(this.open && !this._destroyed && DialogLevelManager.isTop(this)){
|
|
this._getFocusItems(this.domNode);
|
|
focus.focus(this._firstFocusItem);
|
|
}
|
|
});
|
|
if(by == "mouse"){
|
|
// wait for mouse up, and then refocus dialog; otherwise doesn't work
|
|
on.once(this.ownerDocument, "mouseup", refocus);
|
|
}else{
|
|
refocus();
|
|
}
|
|
},
|
|
|
|
_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(this.ownerDocument);
|
|
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(" "),
|
|
ownerDocument: this.ownerDocument
|
|
};
|
|
},
|
|
|
|
_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(typeof this._singleChildOriginalStyle != "undefined"){
|
|
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);
|
|
|
|
// Get viewport size but then reduce it by a bit; Dialog should always have some space around it
|
|
// to indicate that it's a popup. This will also compensate for possible scrollbars on viewport.
|
|
var viewport = winUtils.getBox(this.ownerDocument);
|
|
viewport.w *= this.maxRatio;
|
|
viewport.h *= this.maxRatio;
|
|
|
|
if(bb.w >= viewport.w || bb.h >= viewport.h){
|
|
// Reduce size of dialog contents so that dialog fits in viewport
|
|
|
|
var containerSize = domGeometry.position(this.containerNode),
|
|
w = Math.min(bb.w, viewport.w) - (bb.w - containerSize.w),
|
|
h = Math.min(bb.h, viewport.h) - (bb.h - containerSize.h);
|
|
|
|
if(this._singleChild && this._singleChild.resize){
|
|
if(typeof this._singleChildOriginalStyle == "undefined"){
|
|
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(this.ownerDocumentBody, "dojoMove")){ // don't do anything if called during auto-scroll
|
|
var node = this.domNode,
|
|
viewport = winUtils.getBox(this.ownerDocument),
|
|
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/_base/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();
|
|
}
|
|
|
|
// Recenter Dialog if user scrolls browser. Connecting to document doesn't work on IE, need to use window.
|
|
var win = winUtils.get(this.ownerDocument);
|
|
this._modalconnects.push(on(win, "scroll", lang.hitch(this, "resize")));
|
|
|
|
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.resolve(true);
|
|
delete this._fadeInDeferred;
|
|
})
|
|
}).play();
|
|
|
|
return this._fadeInDeferred;
|
|
},
|
|
|
|
hide: function(){
|
|
// summary:
|
|
// Hide the dialog
|
|
// returns: dojo/_base/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.
|
|
// Likewise if we are already hidden, or are currently fading out.
|
|
if(!this._alreadyInitialized || !this.open){
|
|
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.resolve(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;
|
|
},
|
|
|
|
resize: function(){
|
|
// summary:
|
|
// Called when viewport scrolled or size changed. Position the Dialog and the underlay.
|
|
// tags:
|
|
// private
|
|
if(this.domNode.style.display != "none"){
|
|
if(DialogUnderlay._singleton){ // avoid race condition during show()
|
|
DialogUnderlay._singleton.layout();
|
|
}
|
|
this._position();
|
|
this._size();
|
|
}
|
|
},
|
|
|
|
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], {
|
|
// 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 Dialog({ title: "test dialog", content: "test content" };
|
|
// | foo.placeAt(win.body());
|
|
// | foo.startup();
|
|
});
|
|
Dialog._DialogBase = _DialogBase; // for monkey patching and dojox/widget/DialogSimple
|
|
|
|
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/_WidgetBase*/ 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
|
|
// TODO: one underlay per document (based on dialog.ownerDocument)
|
|
var underlay = DialogUnderlay._singleton;
|
|
if(!underlay || underlay._destroyed){
|
|
underlay = dijit._underlay = DialogUnderlay._singleton = 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(DialogUnderlay._singleton.domNode, 'zIndex', zIndex - 1);
|
|
|
|
// Dialog
|
|
domStyle.set(dialog.domNode, 'zIndex', zIndex);
|
|
|
|
ds.push({dialog: dialog, underlayAttrs: underlayAttrs, zIndex: zIndex});
|
|
},
|
|
|
|
hide: function(/*dijit/_WidgetBase*/ 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, unless the underlay widget has already been destroyed
|
|
// because we are being called during page unload (when all widgets are destroyed)
|
|
if(!DialogUnderlay._singleton._destroyed){
|
|
if(ds.length == 1){
|
|
// Returning to original page. Hide the underlay.
|
|
DialogUnderlay._singleton.hide();
|
|
}else{
|
|
// Popping back to previous dialog, adjust underlay.
|
|
domStyle.set(DialogUnderlay._singleton.domNode, 'zIndex', pd.zIndex - 1);
|
|
DialogUnderlay._singleton.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){
|
|
// Refocus the button that spawned the Dialog. This will fail in corner cases including
|
|
// page unload on IE, because the dijit/form/Button that launched the Dialog may get destroyed
|
|
// before this code runs. (#15058)
|
|
try{
|
|
focus.focus();
|
|
}catch(e){}
|
|
}
|
|
}
|
|
}else{
|
|
// Removing a dialog out of order (#9944, #10705).
|
|
// Don't need to mess with underlay or z-index or anything.
|
|
var idx = array.indexOf(array.map(ds, function(elem){return elem.dialog}), dialog);
|
|
if(idx != -1){
|
|
ds.splice(idx, 1);
|
|
}
|
|
}
|
|
},
|
|
|
|
isTop: function(/*dijit/_WidgetBase*/ 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(has("dijit-legacy-requires")){
|
|
ready(0, function(){
|
|
var requires = ["dijit/TooltipDialog"];
|
|
require(requires); // use indirection so modules not rolled into a build
|
|
});
|
|
}
|
|
|
|
return Dialog;
|
|
});
|
|
|
|
},
|
|
'dijit/_base/focus':function(){
|
|
define("dijit/_base/focus", [
|
|
"dojo/_base/array", // array.forEach
|
|
"dojo/dom", // dom.isDescendant
|
|
"dojo/_base/lang", // lang.isArray
|
|
"dojo/topic", // publish
|
|
"dojo/_base/window", // win.doc win.doc.selection win.global win.global.getSelection win.withGlobal
|
|
"../focus",
|
|
"../main" // for exporting symbols to dijit
|
|
], function(array, dom, lang, topic, win, focus, dijit){
|
|
|
|
// module:
|
|
// dijit/_base/focus
|
|
|
|
var exports = {
|
|
// summary:
|
|
// Deprecated module to monitor currently focused node and stack of currently focused widgets.
|
|
// New code should access dijit/focus directly.
|
|
|
|
// _curFocus: DomNode
|
|
// Currently focused item on screen
|
|
_curFocus: null,
|
|
|
|
// _prevFocus: DomNode
|
|
// Previously focused item on screen
|
|
_prevFocus: null,
|
|
|
|
isCollapsed: function(){
|
|
// summary:
|
|
// Returns true if there is no text selected
|
|
return dijit.getBookmark().isCollapsed;
|
|
},
|
|
|
|
getBookmark: function(){
|
|
// summary:
|
|
// Retrieves a bookmark that can be used with moveToBookmark to return to the same range
|
|
var bm, rg, tg, sel = win.doc.selection, cf = focus.curNode;
|
|
|
|
if(win.global.getSelection){
|
|
//W3C Range API for selections.
|
|
sel = win.global.getSelection();
|
|
if(sel){
|
|
if(sel.isCollapsed){
|
|
tg = cf? cf.tagName : "";
|
|
if(tg){
|
|
//Create a fake rangelike item to restore selections.
|
|
tg = tg.toLowerCase();
|
|
if(tg == "textarea" ||
|
|
(tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){
|
|
sel = {
|
|
start: cf.selectionStart,
|
|
end: cf.selectionEnd,
|
|
node: cf,
|
|
pRange: true
|
|
};
|
|
return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object.
|
|
}
|
|
}
|
|
bm = {isCollapsed:true};
|
|
if(sel.rangeCount){
|
|
bm.mark = sel.getRangeAt(0).cloneRange();
|
|
}
|
|
}else{
|
|
rg = sel.getRangeAt(0);
|
|
bm = {isCollapsed: false, mark: rg.cloneRange()};
|
|
}
|
|
}
|
|
}else if(sel){
|
|
// If the current focus was a input of some sort and no selection, don't bother saving
|
|
// a native bookmark. This is because it causes issues with dialog/page selection restore.
|
|
// So, we need to create psuedo bookmarks to work with.
|
|
tg = cf ? cf.tagName : "";
|
|
tg = tg.toLowerCase();
|
|
if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){
|
|
if(sel.type && sel.type.toLowerCase() == "none"){
|
|
return {
|
|
isCollapsed: true,
|
|
mark: null
|
|
}
|
|
}else{
|
|
rg = sel.createRange();
|
|
return {
|
|
isCollapsed: rg.text && rg.text.length?false:true,
|
|
mark: {
|
|
range: rg,
|
|
pRange: true
|
|
}
|
|
};
|
|
}
|
|
}
|
|
bm = {};
|
|
|
|
//'IE' way for selections.
|
|
try{
|
|
// createRange() throws exception when dojo in iframe
|
|
//and nothing selected, see #9632
|
|
rg = sel.createRange();
|
|
bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length);
|
|
}catch(e){
|
|
bm.isCollapsed = true;
|
|
return bm;
|
|
}
|
|
if(sel.type.toUpperCase() == 'CONTROL'){
|
|
if(rg.length){
|
|
bm.mark=[];
|
|
var i=0,len=rg.length;
|
|
while(i<len){
|
|
bm.mark.push(rg.item(i++));
|
|
}
|
|
}else{
|
|
bm.isCollapsed = true;
|
|
bm.mark = null;
|
|
}
|
|
}else{
|
|
bm.mark = rg.getBookmark();
|
|
}
|
|
}else{
|
|
console.warn("No idea how to store the current selection for this browser!");
|
|
}
|
|
return bm; // Object
|
|
},
|
|
|
|
moveToBookmark: function(/*Object*/ bookmark){
|
|
// summary:
|
|
// Moves current selection to a bookmark
|
|
// bookmark:
|
|
// This should be a returned object from dijit.getBookmark()
|
|
|
|
var _doc = win.doc,
|
|
mark = bookmark.mark;
|
|
if(mark){
|
|
if(win.global.getSelection){
|
|
//W3C Rangi API (FF, WebKit, Opera, etc)
|
|
var sel = win.global.getSelection();
|
|
if(sel && sel.removeAllRanges){
|
|
if(mark.pRange){
|
|
var n = mark.node;
|
|
n.selectionStart = mark.start;
|
|
n.selectionEnd = mark.end;
|
|
}else{
|
|
sel.removeAllRanges();
|
|
sel.addRange(mark);
|
|
}
|
|
}else{
|
|
console.warn("No idea how to restore selection for this browser!");
|
|
}
|
|
}else if(_doc.selection && mark){
|
|
//'IE' way.
|
|
var rg;
|
|
if(mark.pRange){
|
|
rg = mark.range;
|
|
}else if(lang.isArray(mark)){
|
|
rg = _doc.body.createControlRange();
|
|
//rg.addElement does not have call/apply method, so can not call it directly
|
|
//rg is not available in "range.addElement(item)", so can't use that either
|
|
array.forEach(mark, function(n){
|
|
rg.addElement(n);
|
|
});
|
|
}else{
|
|
rg = _doc.body.createTextRange();
|
|
rg.moveToBookmark(mark);
|
|
}
|
|
rg.select();
|
|
}
|
|
}
|
|
},
|
|
|
|
getFocus: function(/*Widget?*/ menu, /*Window?*/ openedForWindow){
|
|
// summary:
|
|
// Called as getFocus(), this returns an Object showing the current focus
|
|
// and selected text.
|
|
//
|
|
// Called as getFocus(widget), where widget is a (widget representing) a button
|
|
// that was just pressed, it returns where focus was before that button
|
|
// was pressed. (Pressing the button may have either shifted focus to the button,
|
|
// or removed focus altogether.) In this case the selected text is not returned,
|
|
// since it can't be accurately determined.
|
|
//
|
|
// menu: dijit/_WidgetBase|{domNode: DomNode} structure
|
|
// The button that was just pressed. If focus has disappeared or moved
|
|
// to this button, returns the previous focus. In this case the bookmark
|
|
// information is already lost, and null is returned.
|
|
//
|
|
// openedForWindow:
|
|
// iframe in which menu was opened
|
|
//
|
|
// returns:
|
|
// A handle to restore focus/selection, to be passed to `dijit.focus`
|
|
var node = !focus.curNode || (menu && dom.isDescendant(focus.curNode, menu.domNode)) ? dijit._prevFocus : focus.curNode;
|
|
return {
|
|
node: node,
|
|
bookmark: node && (node == focus.curNode) && win.withGlobal(openedForWindow || win.global, dijit.getBookmark),
|
|
openedForWindow: openedForWindow
|
|
}; // Object
|
|
},
|
|
|
|
// _activeStack: dijit/_WidgetBase[]
|
|
// List of currently active widgets (focused widget and it's ancestors)
|
|
_activeStack: [],
|
|
|
|
registerIframe: function(/*DomNode*/ iframe){
|
|
// summary:
|
|
// Registers listeners on the specified iframe so that any click
|
|
// or focus event on that iframe (or anything in it) is reported
|
|
// as a focus/click event on the `<iframe>` itself.
|
|
// description:
|
|
// Currently only used by editor.
|
|
// returns:
|
|
// Handle to pass to unregisterIframe()
|
|
return focus.registerIframe(iframe);
|
|
},
|
|
|
|
unregisterIframe: function(/*Object*/ handle){
|
|
// summary:
|
|
// Unregisters listeners on the specified iframe created by registerIframe.
|
|
// After calling be sure to delete or null out the handle itself.
|
|
// handle:
|
|
// Handle returned by registerIframe()
|
|
|
|
handle && handle.remove();
|
|
},
|
|
|
|
registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){
|
|
// summary:
|
|
// Registers listeners on the specified window (either the main
|
|
// window or an iframe's window) to detect when the user has clicked somewhere
|
|
// or focused somewhere.
|
|
// description:
|
|
// Users should call registerIframe() instead of this method.
|
|
// targetWindow:
|
|
// If specified this is the window associated with the iframe,
|
|
// i.e. iframe.contentWindow.
|
|
// effectiveNode:
|
|
// If specified, report any focus events inside targetWindow as
|
|
// an event on effectiveNode, rather than on evt.target.
|
|
// returns:
|
|
// Handle to pass to unregisterWin()
|
|
|
|
return focus.registerWin(targetWindow, effectiveNode);
|
|
},
|
|
|
|
unregisterWin: function(/*Handle*/ handle){
|
|
// summary:
|
|
// Unregisters listeners on the specified window (either the main
|
|
// window or an iframe's window) according to handle returned from registerWin().
|
|
// After calling be sure to delete or null out the handle itself.
|
|
|
|
handle && handle.remove();
|
|
}
|
|
};
|
|
|
|
// Override focus singleton's focus function so that dijit.focus()
|
|
// has backwards compatible behavior of restoring selection (although
|
|
// probably no one is using that).
|
|
focus.focus = function(/*Object|DomNode */ handle){
|
|
// summary:
|
|
// Sets the focused node and the selection according to argument.
|
|
// To set focus to an iframe's content, pass in the iframe itself.
|
|
// handle:
|
|
// object returned by get(), or a DomNode
|
|
|
|
if(!handle){ return; }
|
|
|
|
var node = "node" in handle ? handle.node : handle, // because handle is either DomNode or a composite object
|
|
bookmark = handle.bookmark,
|
|
openedForWindow = handle.openedForWindow,
|
|
collapsed = bookmark ? bookmark.isCollapsed : false;
|
|
|
|
// Set the focus
|
|
// Note that for iframe's we need to use the <iframe> to follow the parentNode chain,
|
|
// but we need to set focus to iframe.contentWindow
|
|
if(node){
|
|
var focusNode = (node.tagName.toLowerCase() == "iframe") ? node.contentWindow : node;
|
|
if(focusNode && focusNode.focus){
|
|
try{
|
|
// Gecko throws sometimes if setting focus is impossible,
|
|
// node not displayed or something like that
|
|
focusNode.focus();
|
|
}catch(e){/*quiet*/}
|
|
}
|
|
focus._onFocusNode(node);
|
|
}
|
|
|
|
// set the selection
|
|
// do not need to restore if current selection is not empty
|
|
// (use keyboard to select a menu item) or if previous selection was collapsed
|
|
// as it may cause focus shift (Esp in IE).
|
|
if(bookmark && win.withGlobal(openedForWindow || win.global, dijit.isCollapsed) && !collapsed){
|
|
if(openedForWindow){
|
|
openedForWindow.focus();
|
|
}
|
|
try{
|
|
win.withGlobal(openedForWindow || win.global, dijit.moveToBookmark, null, [bookmark]);
|
|
}catch(e2){
|
|
/*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */
|
|
}
|
|
}
|
|
};
|
|
|
|
// For back compatibility, monitor changes to focused node and active widget stack,
|
|
// publishing events and copying changes from focus manager variables into dijit (top level) variables
|
|
focus.watch("curNode", function(name, oldVal, newVal){
|
|
dijit._curFocus = newVal;
|
|
dijit._prevFocus = oldVal;
|
|
if(newVal){
|
|
topic.publish("focusNode", newVal); // publish
|
|
}
|
|
});
|
|
focus.watch("activeStack", function(name, oldVal, newVal){
|
|
dijit._activeStack = newVal;
|
|
});
|
|
|
|
focus.on("widget-blur", function(widget, by){
|
|
topic.publish("widgetBlur", widget, by); // publish
|
|
});
|
|
focus.on("widget-focus", function(widget, by){
|
|
topic.publish("widgetFocus", widget, by); // publish
|
|
});
|
|
|
|
lang.mixin(dijit, exports);
|
|
|
|
/*===== return exports; =====*/
|
|
return dijit; // for back compat :-(
|
|
});
|
|
|
|
},
|
|
'dijit/tree/dndSource':function(){
|
|
define("dijit/tree/dndSource", [
|
|
"dojo/_base/array", // array.forEach array.indexOf array.map
|
|
"dojo/_base/connect", // isCopyKey
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-class", // domClass.add
|
|
"dojo/dom-geometry", // domGeometry.position
|
|
"dojo/_base/lang", // lang.mixin lang.hitch
|
|
"dojo/on", // subscribe
|
|
"dojo/touch",
|
|
"dojo/topic",
|
|
"dojo/dnd/Manager", // DNDManager.manager
|
|
"./_dndSelector"
|
|
], function(array, connect, declare, domClass, domGeometry, lang, on, touch, topic, DNDManager, _dndSelector){
|
|
|
|
// module:
|
|
// dijit/tree/dndSource
|
|
// summary:
|
|
// Handles drag and drop operations (as a source or a target) for `dijit.Tree`
|
|
|
|
/*=====
|
|
var __Item = {
|
|
// summary:
|
|
// New item to be added to the Tree, like:
|
|
// id: Anything
|
|
id: "",
|
|
// name: String
|
|
name: ""
|
|
};
|
|
=====*/
|
|
|
|
var dndSource = declare("dijit.tree.dndSource", _dndSelector, {
|
|
// summary:
|
|
// Handles drag and drop operations (as a source or a target) for `dijit.Tree`
|
|
|
|
// isSource: Boolean
|
|
// Can be used as a DnD source.
|
|
isSource: true,
|
|
|
|
// accept: String[]
|
|
// List of accepted types (text strings) for the Tree; defaults to
|
|
// ["text"]
|
|
accept: ["text", "treeNode"],
|
|
|
|
// copyOnly: [private] Boolean
|
|
// Copy items, if true, use a state of Ctrl key otherwise
|
|
copyOnly: false,
|
|
|
|
// dragThreshold: Number
|
|
// The move delay in pixels before detecting a drag; 5 by default
|
|
dragThreshold: 5,
|
|
|
|
// betweenThreshold: Integer
|
|
// Distance from upper/lower edge of node to allow drop to reorder nodes
|
|
betweenThreshold: 0,
|
|
|
|
// Flag used by Avatar.js to signal to generate text node when dragging
|
|
generateText: true,
|
|
|
|
constructor: function(/*dijit/Tree*/ tree, /*dijit/tree/dndSource*/ params){
|
|
// summary:
|
|
// a constructor of the Tree DnD Source
|
|
// tags:
|
|
// private
|
|
if(!params){ params = {}; }
|
|
lang.mixin(this, params);
|
|
var type = params.accept instanceof Array ? params.accept : ["text", "treeNode"];
|
|
this.accept = null;
|
|
if(type.length){
|
|
this.accept = {};
|
|
for(var i = 0; i < type.length; ++i){
|
|
this.accept[type[i]] = 1;
|
|
}
|
|
}
|
|
|
|
// class-specific variables
|
|
this.isDragging = false;
|
|
this.mouseDown = false;
|
|
this.targetAnchor = null; // DOMNode corresponding to the currently moused over TreeNode
|
|
this.targetBox = null; // coordinates of this.targetAnchor
|
|
this.dropPosition = ""; // whether mouse is over/after/before this.targetAnchor
|
|
this._lastX = 0;
|
|
this._lastY = 0;
|
|
|
|
// states
|
|
this.sourceState = "";
|
|
if(this.isSource){
|
|
domClass.add(this.node, "dojoDndSource");
|
|
}
|
|
this.targetState = "";
|
|
if(this.accept){
|
|
domClass.add(this.node, "dojoDndTarget");
|
|
}
|
|
|
|
// set up events
|
|
this.topics = [
|
|
topic.subscribe("/dnd/source/over", lang.hitch(this, "onDndSourceOver")),
|
|
topic.subscribe("/dnd/start", lang.hitch(this, "onDndStart")),
|
|
topic.subscribe("/dnd/drop", lang.hitch(this, "onDndDrop")),
|
|
topic.subscribe("/dnd/cancel", lang.hitch(this, "onDndCancel"))
|
|
];
|
|
},
|
|
|
|
// methods
|
|
checkAcceptance: function(/*===== source, nodes =====*/){
|
|
// summary:
|
|
// Checks if the target can accept nodes from this source
|
|
// source: dijit/tree/dndSource
|
|
// The source which provides items
|
|
// nodes: DOMNode[]
|
|
// Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
|
|
// source is a dijit/Tree.
|
|
// tags:
|
|
// extension
|
|
return true; // Boolean
|
|
},
|
|
|
|
copyState: function(keyPressed){
|
|
// summary:
|
|
// Returns true, if we need to copy items, false to move.
|
|
// It is separated to be overwritten dynamically, if needed.
|
|
// keyPressed: Boolean
|
|
// The "copy" control key was pressed
|
|
// tags:
|
|
// protected
|
|
return this.copyOnly || keyPressed; // Boolean
|
|
},
|
|
destroy: function(){
|
|
// summary:
|
|
// Prepares the object to be garbage-collected.
|
|
this.inherited(arguments);
|
|
var h;
|
|
while(h = this.topics.pop()){ h.remove(); }
|
|
this.targetAnchor = null;
|
|
},
|
|
|
|
_onDragMouse: function(e, firstTime){
|
|
// summary:
|
|
// Helper method for processing onmousemove/onmouseover events while drag is in progress.
|
|
// Keeps track of current drop target.
|
|
// e: Event
|
|
// The mousemove event.
|
|
// firstTime: Boolean?
|
|
// If this flag is set, this is the first mouse move event of the drag, so call m.canDrop() etc.
|
|
// even if newTarget == null because the user quickly dragged a node in the Tree to a position
|
|
// over Tree.containerNode but not over any TreeNode (#7971)
|
|
|
|
var m = DNDManager.manager(),
|
|
oldTarget = this.targetAnchor, // the TreeNode corresponding to TreeNode mouse was previously over
|
|
newTarget = this.current, // TreeNode corresponding to TreeNode mouse is currently over
|
|
oldDropPosition = this.dropPosition; // the previous drop position (over/before/after)
|
|
|
|
// calculate if user is indicating to drop the dragged node before, after, or over
|
|
// (i.e., to become a child of) the target node
|
|
var newDropPosition = "Over";
|
|
if(newTarget && this.betweenThreshold > 0){
|
|
// If mouse is over a new TreeNode, then get new TreeNode's position and size
|
|
if(!this.targetBox || oldTarget != newTarget){
|
|
this.targetBox = domGeometry.position(newTarget.rowNode, true);
|
|
}
|
|
if((e.pageY - this.targetBox.y) <= this.betweenThreshold){
|
|
newDropPosition = "Before";
|
|
}else if((e.pageY - this.targetBox.y) >= (this.targetBox.h - this.betweenThreshold)){
|
|
newDropPosition = "After";
|
|
}
|
|
}
|
|
|
|
if(firstTime || newTarget != oldTarget || newDropPosition != oldDropPosition){
|
|
if(oldTarget){
|
|
this._removeItemClass(oldTarget.rowNode, oldDropPosition);
|
|
}
|
|
if(newTarget){
|
|
this._addItemClass(newTarget.rowNode, newDropPosition);
|
|
}
|
|
|
|
// Check if it's ok to drop the dragged node on/before/after the target node.
|
|
if(!newTarget){
|
|
m.canDrop(false);
|
|
}else if(newTarget == this.tree.rootNode && newDropPosition != "Over"){
|
|
// Can't drop before or after tree's root node; the dropped node would just disappear (at least visually)
|
|
m.canDrop(false);
|
|
}else{
|
|
// Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140)
|
|
var sameId = false;
|
|
if(m.source == this){
|
|
for(var dragId in this.selection){
|
|
var dragNode = this.selection[dragId];
|
|
if(dragNode.item === newTarget.item){
|
|
sameId = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(sameId){
|
|
m.canDrop(false);
|
|
}else if(this.checkItemAcceptance(newTarget.rowNode, m.source, newDropPosition.toLowerCase())
|
|
&& !this._isParentChildDrop(m.source, newTarget.rowNode)){
|
|
m.canDrop(true);
|
|
}else{
|
|
m.canDrop(false);
|
|
}
|
|
}
|
|
|
|
this.targetAnchor = newTarget;
|
|
this.dropPosition = newDropPosition;
|
|
}
|
|
},
|
|
|
|
onMouseMove: function(e){
|
|
// summary:
|
|
// Called for any onmousemove/ontouchmove events over the Tree
|
|
// e: Event
|
|
// onmousemouse/ontouchmove event
|
|
// tags:
|
|
// private
|
|
if(this.isDragging && this.targetState == "Disabled"){ return; }
|
|
this.inherited(arguments);
|
|
var m = DNDManager.manager();
|
|
if(this.isDragging){
|
|
this._onDragMouse(e);
|
|
}else{
|
|
if(this.mouseDown && this.isSource &&
|
|
(Math.abs(e.pageX-this._lastX)>=this.dragThreshold || Math.abs(e.pageY-this._lastY)>=this.dragThreshold)){
|
|
var nodes = this.getSelectedTreeNodes();
|
|
if(nodes.length){
|
|
if(nodes.length > 1){
|
|
//filter out all selected items which has one of their ancestor selected as well
|
|
var seen = this.selection, i = 0, r = [], n, p;
|
|
nextitem: while((n = nodes[i++])){
|
|
for(p = n.getParent(); p && p !== this.tree; p = p.getParent()){
|
|
if(seen[p.id]){ //parent is already selected, skip this node
|
|
continue nextitem;
|
|
}
|
|
}
|
|
//this node does not have any ancestors selected, add it
|
|
r.push(n);
|
|
}
|
|
nodes = r;
|
|
}
|
|
nodes = array.map(nodes, function(n){return n.domNode});
|
|
m.startDrag(this, nodes, this.copyState(connect.isCopyKey(e)));
|
|
this._onDragMouse(e, true); // because this may be the only mousemove event we get before the drop
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
onMouseDown: function(e){
|
|
// summary:
|
|
// Event processor for onmousedown/ontouchstart
|
|
// e: Event
|
|
// onmousedown/ontouchend event
|
|
// tags:
|
|
// private
|
|
this.mouseDown = true;
|
|
this.mouseButton = e.button;
|
|
this._lastX = e.pageX;
|
|
this._lastY = e.pageY;
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
onMouseUp: function(e){
|
|
// summary:
|
|
// Event processor for onmouseup/ontouchend
|
|
// e: Event
|
|
// onmouseup/ontouchend event
|
|
// tags:
|
|
// private
|
|
if(this.mouseDown){
|
|
this.mouseDown = false;
|
|
this.inherited(arguments);
|
|
}
|
|
},
|
|
|
|
onMouseOut: function(){
|
|
// summary:
|
|
// Event processor for when mouse is moved away from a TreeNode
|
|
// tags:
|
|
// private
|
|
this.inherited(arguments);
|
|
this._unmarkTargetAnchor();
|
|
},
|
|
|
|
checkItemAcceptance: function(/*===== target, source, position =====*/){
|
|
// summary:
|
|
// Stub function to be overridden if one wants to check for the ability to drop at the node/item level
|
|
// description:
|
|
// In the base case, this is called to check if target can become a child of source.
|
|
// When betweenThreshold is set, position="before" or "after" means that we
|
|
// are asking if the source node can be dropped before/after the target node.
|
|
// target: DOMNode
|
|
// The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
|
|
// Use dijit.getEnclosingWidget(target) to get the TreeNode.
|
|
// source: dijit/tree/dndSource
|
|
// The (set of) nodes we are dropping
|
|
// position: String
|
|
// "over", "before", or "after"
|
|
// tags:
|
|
// extension
|
|
return true;
|
|
},
|
|
|
|
// topic event processors
|
|
onDndSourceOver: function(source){
|
|
// summary:
|
|
// Topic event processor for /dnd/source/over, called when detected a current source.
|
|
// source: Object
|
|
// The dijit/tree/dndSource / dojo/dnd/Source which has the mouse over it
|
|
// tags:
|
|
// private
|
|
if(this != source){
|
|
this.mouseDown = false;
|
|
this._unmarkTargetAnchor();
|
|
}else if(this.isDragging){
|
|
var m = DNDManager.manager();
|
|
m.canDrop(false);
|
|
}
|
|
},
|
|
onDndStart: function(source, nodes, copy){
|
|
// summary:
|
|
// Topic event processor for /dnd/start, called to initiate the DnD operation
|
|
// source: Object
|
|
// The dijit/tree/dndSource / dojo/dnd/Source which is providing the items
|
|
// nodes: DomNode[]
|
|
// The list of transferred items, dndTreeNode nodes if dragging from a Tree
|
|
// copy: Boolean
|
|
// Copy items, if true, move items otherwise
|
|
// tags:
|
|
// private
|
|
|
|
if(this.isSource){
|
|
this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
|
|
}
|
|
var accepted = this.checkAcceptance(source, nodes);
|
|
|
|
this._changeState("Target", accepted ? "" : "Disabled");
|
|
|
|
if(this == source){
|
|
DNDManager.manager().overSource(this);
|
|
}
|
|
|
|
this.isDragging = true;
|
|
},
|
|
|
|
itemCreator: function(nodes /*===== , target, source =====*/){
|
|
// summary:
|
|
// Returns objects passed to `Tree.model.newItem()` based on DnD nodes
|
|
// dropped onto the tree. Developer must override this method to enable
|
|
// dropping from external sources onto this Tree, unless the Tree.model's items
|
|
// happen to look like {id: 123, name: "Apple" } with no other attributes.
|
|
// description:
|
|
// For each node in nodes[], which came from source, create a hash of name/value
|
|
// pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
|
|
// nodes: DomNode[]
|
|
// target: DomNode
|
|
// source: dojo/dnd/Source
|
|
// returns: __Item[]
|
|
// Array of name/value hashes for each new item to be added to the Tree
|
|
// tags:
|
|
// extension
|
|
|
|
// TODO: for 2.0 refactor so itemCreator() is called once per drag node, and
|
|
// make signature itemCreator(sourceItem, node, target) (or similar).
|
|
|
|
return array.map(nodes, function(node){
|
|
return {
|
|
"id": node.id,
|
|
"name": node.textContent || node.innerText || ""
|
|
};
|
|
}); // Object[]
|
|
},
|
|
|
|
onDndDrop: function(source, nodes, copy){
|
|
// summary:
|
|
// Topic event processor for /dnd/drop, called to finish the DnD operation.
|
|
// description:
|
|
// Updates data store items according to where node was dragged from and dropped
|
|
// to. The tree will then respond to those data store updates and redraw itself.
|
|
// source: Object
|
|
// The dijit/tree/dndSource / dojo/dnd/Source which is providing the items
|
|
// nodes: DomNode[]
|
|
// The list of transferred items, dndTreeNode nodes if dragging from a Tree
|
|
// copy: Boolean
|
|
// Copy items, if true, move items otherwise
|
|
// tags:
|
|
// protected
|
|
if(this.containerState == "Over"){
|
|
var tree = this.tree,
|
|
model = tree.model,
|
|
target = this.targetAnchor;
|
|
|
|
this.isDragging = false;
|
|
|
|
// Compute the new parent item
|
|
var newParentItem;
|
|
var insertIndex;
|
|
var before; // drop source before (aka previous sibling) of target
|
|
newParentItem = (target && target.item) || tree.item;
|
|
if(this.dropPosition == "Before" || this.dropPosition == "After"){
|
|
// TODO: if there is no parent item then disallow the drop.
|
|
// Actually this should be checked during onMouseMove too, to make the drag icon red.
|
|
newParentItem = (target.getParent() && target.getParent().item) || tree.item;
|
|
// Compute the insert index for reordering
|
|
insertIndex = target.getIndexInParent();
|
|
if(this.dropPosition == "After"){
|
|
insertIndex = target.getIndexInParent() + 1;
|
|
before = target.getNextSibling() && target.getNextSibling().item;
|
|
}else{
|
|
before = target.item;
|
|
}
|
|
}else{
|
|
newParentItem = (target && target.item) || tree.item;
|
|
}
|
|
|
|
// If necessary, use this variable to hold array of hashes to pass to model.newItem()
|
|
// (one entry in the array for each dragged node).
|
|
var newItemsParams;
|
|
|
|
array.forEach(nodes, function(node, idx){
|
|
// dojo/dnd/Item representing the thing being dropped.
|
|
// Don't confuse the use of item here (meaning a DnD item) with the
|
|
// uses below where item means dojo.data item.
|
|
var sourceItem = source.getItem(node.id);
|
|
|
|
// Information that's available if the source is another Tree
|
|
// (possibly but not necessarily this tree, possibly but not
|
|
// necessarily the same model as this Tree)
|
|
if(array.indexOf(sourceItem.type, "treeNode") != -1){
|
|
var childTreeNode = sourceItem.data,
|
|
childItem = childTreeNode.item,
|
|
oldParentItem = childTreeNode.getParent().item;
|
|
}
|
|
|
|
if(source == this){
|
|
// This is a node from my own tree, and we are moving it, not copying.
|
|
// Remove item from old parent's children attribute.
|
|
// TODO: dijit/tree/dndSelector should implement deleteSelectedNodes()
|
|
// and this code should go there.
|
|
|
|
if(typeof insertIndex == "number"){
|
|
if(newParentItem == oldParentItem && childTreeNode.getIndexInParent() < insertIndex){
|
|
insertIndex -= 1;
|
|
}
|
|
}
|
|
model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex, before);
|
|
}else if(model.isItem(childItem)){
|
|
// Item from same model
|
|
// (maybe we should only do this branch if the source is a tree?)
|
|
model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex, before);
|
|
}else{
|
|
// Get the hash to pass to model.newItem(). A single call to
|
|
// itemCreator() returns an array of hashes, one for each drag source node.
|
|
if(!newItemsParams){
|
|
newItemsParams = this.itemCreator(nodes, target.rowNode, source);
|
|
}
|
|
|
|
// Create new item in the tree, based on the drag source.
|
|
model.newItem(newItemsParams[idx], newParentItem, insertIndex, before);
|
|
}
|
|
}, this);
|
|
|
|
// Expand the target node (if it's currently collapsed) so the user can see
|
|
// where their node was dropped. In particular since that node is still selected.
|
|
this.tree._expandNode(target);
|
|
}
|
|
this.onDndCancel();
|
|
},
|
|
|
|
onDndCancel: function(){
|
|
// summary:
|
|
// Topic event processor for /dnd/cancel, called to cancel the DnD operation
|
|
// tags:
|
|
// private
|
|
this._unmarkTargetAnchor();
|
|
this.isDragging = false;
|
|
this.mouseDown = false;
|
|
delete this.mouseButton;
|
|
this._changeState("Source", "");
|
|
this._changeState("Target", "");
|
|
},
|
|
|
|
// When focus moves in/out of the entire Tree
|
|
onOverEvent: function(){
|
|
// summary:
|
|
// This method is called when mouse is moved over our container (like onmouseenter)
|
|
// tags:
|
|
// private
|
|
this.inherited(arguments);
|
|
DNDManager.manager().overSource(this);
|
|
},
|
|
onOutEvent: function(){
|
|
// summary:
|
|
// This method is called when mouse is moved out of our container (like onmouseleave)
|
|
// tags:
|
|
// private
|
|
this._unmarkTargetAnchor();
|
|
var m = DNDManager.manager();
|
|
if(this.isDragging){
|
|
m.canDrop(false);
|
|
}
|
|
m.outSource(this);
|
|
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
_isParentChildDrop: function(source, targetRow){
|
|
// summary:
|
|
// Checks whether the dragged items are parent rows in the tree which are being
|
|
// dragged into their own children.
|
|
//
|
|
// source:
|
|
// The DragSource object.
|
|
//
|
|
// targetRow:
|
|
// The tree row onto which the dragged nodes are being dropped.
|
|
//
|
|
// tags:
|
|
// private
|
|
|
|
// If the dragged object is not coming from the tree this widget belongs to,
|
|
// it cannot be invalid.
|
|
if(!source.tree || source.tree != this.tree){
|
|
return false;
|
|
}
|
|
|
|
|
|
var root = source.tree.domNode;
|
|
var ids = source.selection;
|
|
|
|
var node = targetRow.parentNode;
|
|
|
|
// Iterate up the DOM hierarchy from the target drop row,
|
|
// checking of any of the dragged nodes have the same ID.
|
|
while(node != root && !ids[node.id]){
|
|
node = node.parentNode;
|
|
}
|
|
|
|
return node.id && ids[node.id];
|
|
},
|
|
|
|
_unmarkTargetAnchor: function(){
|
|
// summary:
|
|
// Removes hover class of the current target anchor
|
|
// tags:
|
|
// private
|
|
if(!this.targetAnchor){ return; }
|
|
this._removeItemClass(this.targetAnchor.rowNode, this.dropPosition);
|
|
this.targetAnchor = null;
|
|
this.targetBox = null;
|
|
this.dropPosition = null;
|
|
},
|
|
|
|
_markDndStatus: function(copy){
|
|
// summary:
|
|
// Changes source's state based on "copy" status
|
|
this._changeState("Source", copy ? "Copied" : "Moved");
|
|
}
|
|
});
|
|
|
|
/*=====
|
|
dndSource.__Item = __Item;
|
|
=====*/
|
|
|
|
return dndSource;
|
|
});
|
|
|
|
},
|
|
'dijit/a11y':function(){
|
|
define("dijit/a11y", [
|
|
"dojo/_base/array", // array.forEach array.map
|
|
"dojo/_base/config", // defaultDuration
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom", // dom.byId
|
|
"dojo/dom-attr", // domAttr.attr domAttr.has
|
|
"dojo/dom-style", // style.style
|
|
"dojo/sniff", // has("ie")
|
|
"./main" // for exporting methods to dijit namespace
|
|
], function(array, config, declare, dom, domAttr, domStyle, has, dijit){
|
|
|
|
// module:
|
|
// dijit/a11y
|
|
|
|
var shown = (dijit._isElementShown = function(/*Element*/ elem){
|
|
var s = domStyle.get(elem);
|
|
return (s.visibility != "hidden")
|
|
&& (s.visibility != "collapsed")
|
|
&& (s.display != "none")
|
|
&& (domAttr.get(elem, "type") != "hidden");
|
|
});
|
|
|
|
dijit.hasDefaultTabStop = function(/*Element*/ elem){
|
|
// summary:
|
|
// Tests if element is tab-navigable even without an explicit tabIndex setting
|
|
|
|
// No explicit tabIndex setting, need to investigate node type
|
|
switch(elem.nodeName.toLowerCase()){
|
|
case "a":
|
|
// An <a> w/out a tabindex is only navigable if it has an href
|
|
return domAttr.has(elem, "href");
|
|
case "area":
|
|
case "button":
|
|
case "input":
|
|
case "object":
|
|
case "select":
|
|
case "textarea":
|
|
// These are navigable by default
|
|
return true;
|
|
case "iframe":
|
|
// If it's an editor <iframe> then it's tab navigable.
|
|
var body;
|
|
try{
|
|
// non-IE
|
|
var contentDocument = elem.contentDocument;
|
|
if("designMode" in contentDocument && contentDocument.designMode == "on"){
|
|
return true;
|
|
}
|
|
body = contentDocument.body;
|
|
}catch(e1){
|
|
// contentWindow.document isn't accessible within IE7/8
|
|
// if the iframe.src points to a foreign url and this
|
|
// page contains an element, that could get focus
|
|
try{
|
|
body = elem.contentWindow.document.body;
|
|
}catch(e2){
|
|
return false;
|
|
}
|
|
}
|
|
return body && (body.contentEditable == 'true' ||
|
|
(body.firstChild && body.firstChild.contentEditable == 'true'));
|
|
default:
|
|
return elem.contentEditable == 'true';
|
|
}
|
|
};
|
|
|
|
var isTabNavigable = (dijit.isTabNavigable = function(/*Element*/ elem){
|
|
// summary:
|
|
// Tests if an element is tab-navigable
|
|
|
|
// TODO: convert (and rename method) to return effective tabIndex; will save time in _getTabNavigable()
|
|
if(domAttr.get(elem, "disabled")){
|
|
return false;
|
|
}else if(domAttr.has(elem, "tabIndex")){
|
|
// Explicit tab index setting
|
|
return domAttr.get(elem, "tabIndex") >= 0; // boolean
|
|
}else{
|
|
// No explicit tabIndex setting, so depends on node type
|
|
return dijit.hasDefaultTabStop(elem);
|
|
}
|
|
});
|
|
|
|
dijit._getTabNavigable = function(/*DOMNode*/ root){
|
|
// summary:
|
|
// Finds descendants of the specified root node.
|
|
// description:
|
|
// Finds the following descendants of the specified root node:
|
|
//
|
|
// - the first tab-navigable element in document order
|
|
// without a tabIndex or with tabIndex="0"
|
|
// - the last tab-navigable element in document order
|
|
// without a tabIndex or with tabIndex="0"
|
|
// - the first element in document order with the lowest
|
|
// positive tabIndex value
|
|
// - the last element in document order with the highest
|
|
// positive tabIndex value
|
|
var first, last, lowest, lowestTabindex, highest, highestTabindex, radioSelected = {};
|
|
|
|
function radioName(node){
|
|
// If this element is part of a radio button group, return the name for that group.
|
|
return node && node.tagName.toLowerCase() == "input" &&
|
|
node.type && node.type.toLowerCase() == "radio" &&
|
|
node.name && node.name.toLowerCase();
|
|
}
|
|
|
|
var walkTree = function(/*DOMNode*/ parent){
|
|
for(var child = parent.firstChild; child; child = child.nextSibling){
|
|
// Skip text elements, hidden elements, and also non-HTML elements (those in custom namespaces) in IE,
|
|
// since show() invokes getAttribute("type"), which crash on VML nodes in IE.
|
|
if(child.nodeType != 1 || (has("ie") <= 9 && child.scopeName !== "HTML") || !shown(child)){
|
|
continue;
|
|
}
|
|
|
|
if(isTabNavigable(child)){
|
|
var tabindex = +domAttr.get(child, "tabIndex"); // + to convert string --> number
|
|
if(!domAttr.has(child, "tabIndex") || tabindex == 0){
|
|
if(!first){
|
|
first = child;
|
|
}
|
|
last = child;
|
|
}else if(tabindex > 0){
|
|
if(!lowest || tabindex < lowestTabindex){
|
|
lowestTabindex = tabindex;
|
|
lowest = child;
|
|
}
|
|
if(!highest || tabindex >= highestTabindex){
|
|
highestTabindex = tabindex;
|
|
highest = child;
|
|
}
|
|
}
|
|
var rn = radioName(child);
|
|
if(domAttr.get(child, "checked") && rn){
|
|
radioSelected[rn] = child;
|
|
}
|
|
}
|
|
if(child.nodeName.toUpperCase() != 'SELECT'){
|
|
walkTree(child);
|
|
}
|
|
}
|
|
};
|
|
if(shown(root)){
|
|
walkTree(root);
|
|
}
|
|
function rs(node){
|
|
// substitute checked radio button for unchecked one, if there is a checked one with the same name.
|
|
return radioSelected[radioName(node)] || node;
|
|
}
|
|
|
|
return { first: rs(first), last: rs(last), lowest: rs(lowest), highest: rs(highest) };
|
|
};
|
|
dijit.getFirstInTabbingOrder = function(/*String|DOMNode*/ root, /*Document?*/ doc){
|
|
// summary:
|
|
// Finds the descendant of the specified root node
|
|
// that is first in the tabbing order
|
|
var elems = dijit._getTabNavigable(dom.byId(root, doc));
|
|
return elems.lowest ? elems.lowest : elems.first; // DomNode
|
|
};
|
|
|
|
dijit.getLastInTabbingOrder = function(/*String|DOMNode*/ root, /*Document?*/ doc){
|
|
// summary:
|
|
// Finds the descendant of the specified root node
|
|
// that is last in the tabbing order
|
|
var elems = dijit._getTabNavigable(dom.byId(root, doc));
|
|
return elems.last ? elems.last : elems.highest; // DomNode
|
|
};
|
|
|
|
return {
|
|
// summary:
|
|
// Accessibility utility functions (keyboard, tab stops, etc.)
|
|
|
|
hasDefaultTabStop: dijit.hasDefaultTabStop,
|
|
isTabNavigable: dijit.isTabNavigable,
|
|
_getTabNavigable: dijit._getTabNavigable,
|
|
getFirstInTabbingOrder: dijit.getFirstInTabbingOrder,
|
|
getLastInTabbingOrder: dijit.getLastInTabbingOrder
|
|
};
|
|
});
|
|
|
|
},
|
|
'dijit/form/_ToggleButtonMixin':function(){
|
|
define("dijit/form/_ToggleButtonMixin", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/dom-attr" // domAttr.set
|
|
], function(declare, domAttr){
|
|
|
|
// module:
|
|
// dijit/form/_ToggleButtonMixin
|
|
|
|
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);
|
|
var node = this.focusNode || this.domNode;
|
|
domAttr.set(node, "checked", !!value); // "mixed" -> true
|
|
if(value){
|
|
node.setAttribute("checked", "");
|
|
}else{
|
|
node.removeAttribute("checked");
|
|
}
|
|
node.setAttribute(this._aria_attr, String(value)); // 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/_Widget':function(){
|
|
define("dijit/_Widget", [
|
|
"dojo/aspect", // aspect.around
|
|
"dojo/_base/config", // config.isDebug
|
|
"dojo/_base/connect", // connect.connect
|
|
"dojo/_base/declare", // declare
|
|
"dojo/has",
|
|
"dojo/_base/kernel", // kernel.deprecated
|
|
"dojo/_base/lang", // lang.hitch
|
|
"dojo/query",
|
|
"dojo/ready",
|
|
"./registry", // registry.byNode
|
|
"./_WidgetBase",
|
|
"./_OnDijitClickMixin",
|
|
"./_FocusMixin",
|
|
"dojo/uacss", // browser sniffing (included for back-compat; subclasses may be using)
|
|
"./hccss" // high contrast mode sniffing (included to set CSS classes on <body>, module ret value unused)
|
|
], function(aspect, config, connect, declare, has, kernel, lang, query, ready,
|
|
registry, _WidgetBase, _OnDijitClickMixin, _FocusMixin){
|
|
|
|
|
|
// module:
|
|
// dijit/_Widget
|
|
|
|
|
|
function connectToDomNode(){
|
|
// summary:
|
|
// If user connects to a widget method === this function, then they will
|
|
// instead actually be connecting the equivalent event on this.domNode
|
|
}
|
|
|
|
// Trap dojo.connect() calls to connectToDomNode methods, and redirect to _Widget.on()
|
|
function aroundAdvice(originalConnect){
|
|
return function(obj, event, scope, method){
|
|
if(obj && typeof event == "string" && obj[event] == connectToDomNode){
|
|
return obj.on(event.substring(2).toLowerCase(), lang.hitch(scope, method));
|
|
}
|
|
return originalConnect.apply(connect, arguments);
|
|
};
|
|
}
|
|
aspect.around(connect, "connect", aroundAdvice);
|
|
if(kernel.connect){
|
|
aspect.around(kernel, "connect", aroundAdvice);
|
|
}
|
|
|
|
var _Widget = declare("dijit._Widget", [_WidgetBase, _OnDijitClickMixin, _FocusMixin], {
|
|
// summary:
|
|
// Old base class for widgets. New widgets should extend `dijit/_WidgetBase` instead
|
|
// description:
|
|
// Old Base class for Dijit widgets.
|
|
//
|
|
// Extends _WidgetBase, adding support for:
|
|
//
|
|
// - declaratively/programatically specifying widget initialization parameters like
|
|
// onMouseMove="foo" that call foo when this.domNode gets a mousemove event
|
|
// - ondijitclick:
|
|
// Support new data-dojo-attach-event="ondijitclick: ..." that is triggered by a mouse click or a SPACE/ENTER keypress
|
|
// - focus related functions:
|
|
// In particular, the onFocus()/onBlur() callbacks. Driven internally by
|
|
// dijit/_base/focus.js.
|
|
// - deprecated methods
|
|
// - onShow(), onHide(), onClose()
|
|
//
|
|
// Also, by loading code in dijit/_base, turns on:
|
|
//
|
|
// - browser sniffing (putting browser class like `dj_ie` on `<html>` node)
|
|
// - high contrast mode sniffing (add `dijit_a11y` class to `<body>` if machine is in high contrast mode)
|
|
|
|
|
|
////////////////// DEFERRED CONNECTS ///////////////////
|
|
|
|
onClick: connectToDomNode,
|
|
/*=====
|
|
onClick: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of mouse click events.
|
|
// event:
|
|
// mouse Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onDblClick: connectToDomNode,
|
|
/*=====
|
|
onDblClick: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of mouse double click events.
|
|
// event:
|
|
// mouse Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onKeyDown: connectToDomNode,
|
|
/*=====
|
|
onKeyDown: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of keys being pressed down.
|
|
// event:
|
|
// key Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onKeyPress: connectToDomNode,
|
|
/*=====
|
|
onKeyPress: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of printable keys being typed.
|
|
// event:
|
|
// key Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onKeyUp: connectToDomNode,
|
|
/*=====
|
|
onKeyUp: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of keys being released.
|
|
// event:
|
|
// key Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onMouseDown: connectToDomNode,
|
|
/*=====
|
|
onMouseDown: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of when the mouse button is pressed down.
|
|
// event:
|
|
// mouse Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onMouseMove: connectToDomNode,
|
|
/*=====
|
|
onMouseMove: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget.
|
|
// event:
|
|
// mouse Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onMouseOut: connectToDomNode,
|
|
/*=====
|
|
onMouseOut: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget.
|
|
// event:
|
|
// mouse Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onMouseOver: connectToDomNode,
|
|
/*=====
|
|
onMouseOver: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget.
|
|
// event:
|
|
// mouse Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onMouseLeave: connectToDomNode,
|
|
/*=====
|
|
onMouseLeave: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of when the mouse moves off of this widget.
|
|
// event:
|
|
// mouse Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onMouseEnter: connectToDomNode,
|
|
/*=====
|
|
onMouseEnter: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of when the mouse moves onto this widget.
|
|
// event:
|
|
// mouse Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
onMouseUp: connectToDomNode,
|
|
/*=====
|
|
onMouseUp: function(event){
|
|
// summary:
|
|
// Connect to this function to receive notifications of when the mouse button is released.
|
|
// event:
|
|
// mouse Event
|
|
// tags:
|
|
// callback
|
|
},
|
|
=====*/
|
|
|
|
constructor: function(params /*===== ,srcNodeRef =====*/){
|
|
// summary:
|
|
// Create the widget.
|
|
// params: Object|null
|
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
|
|
// and functions, typically callbacks like onClick.
|
|
// The hash can contain any of the widget's properties, excluding read-only properties.
|
|
// srcNodeRef: DOMNode|String?
|
|
// If a srcNodeRef (DOM node) is specified:
|
|
//
|
|
// - use srcNodeRef.innerHTML as my contents
|
|
// - if this is a behavioral widget then apply behavior to that srcNodeRef
|
|
// - otherwise, replace srcNodeRef with my generated DOM tree
|
|
|
|
// extract parameters like onMouseMove that should connect directly to this.domNode
|
|
this._toConnect = {};
|
|
for(var name in params){
|
|
if(this[name] === connectToDomNode){
|
|
this._toConnect[name.replace(/^on/, "").toLowerCase()] = params[name];
|
|
delete params[name];
|
|
}
|
|
}
|
|
},
|
|
|
|
postCreate: function(){
|
|
this.inherited(arguments);
|
|
|
|
// perform connection from this.domNode to user specified handlers (ex: onMouseMove)
|
|
for(var name in this._toConnect){
|
|
this.on(name, this._toConnect[name]);
|
|
}
|
|
delete this._toConnect;
|
|
},
|
|
|
|
on: function(/*String|Function*/ type, /*Function*/ func){
|
|
if(this[this._onMap(type)] === connectToDomNode){
|
|
// Use connect.connect() rather than on() to get handling for "onmouseenter" on non-IE,
|
|
// normalization of onkeypress/onkeydown to behave like firefox, etc.
|
|
// Also, need to specify context as "this" rather than the default context of the DOMNode
|
|
// Remove in 2.0.
|
|
return connect.connect(this.domNode, type.toLowerCase(), this, func);
|
|
}
|
|
return this.inherited(arguments);
|
|
},
|
|
|
|
_setFocusedAttr: function(val){
|
|
// Remove this method in 2.0 (or sooner), just here to set _focused == focused, for back compat
|
|
// (but since it's a private variable we aren't required to keep supporting it).
|
|
this._focused = val;
|
|
this._set("focused", val);
|
|
},
|
|
|
|
////////////////// DEPRECATED METHODS ///////////////////
|
|
|
|
setAttribute: function(/*String*/ attr, /*anything*/ value){
|
|
// summary:
|
|
// Deprecated. Use set() instead.
|
|
// tags:
|
|
// deprecated
|
|
kernel.deprecated(this.declaredClass+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0");
|
|
this.set(attr, value);
|
|
},
|
|
|
|
attr: function(/*String|Object*/name, /*Object?*/value){
|
|
// summary:
|
|
// Set or get properties on a widget instance.
|
|
// name:
|
|
// The property to get or set. If an object is passed here and not
|
|
// a string, its keys are used as names of attributes to be set
|
|
// and the value of the object as values to set in the widget.
|
|
// value:
|
|
// Optional. If provided, attr() operates as a setter. If omitted,
|
|
// the current value of the named property is returned.
|
|
// description:
|
|
// This method is deprecated, use get() or set() directly.
|
|
|
|
// Print deprecation warning but only once per calling function
|
|
if(config.isDebug){
|
|
var alreadyCalledHash = arguments.callee._ach || (arguments.callee._ach = {}),
|
|
caller = (arguments.callee.caller || "unknown caller").toString();
|
|
if(!alreadyCalledHash[caller]){
|
|
kernel.deprecated(this.declaredClass + "::attr() is deprecated. Use get() or set() instead, called from " +
|
|
caller, "", "2.0");
|
|
alreadyCalledHash[caller] = true;
|
|
}
|
|
}
|
|
|
|
var args = arguments.length;
|
|
if(args >= 2 || typeof name === "object"){ // setter
|
|
return this.set.apply(this, arguments);
|
|
}else{ // getter
|
|
return this.get(name);
|
|
}
|
|
},
|
|
|
|
getDescendants: function(){
|
|
// summary:
|
|
// Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
|
|
// This method should generally be avoided as it returns widgets declared in templates, which are
|
|
// supposed to be internal/hidden, but it's left here for back-compat reasons.
|
|
|
|
kernel.deprecated(this.declaredClass+"::getDescendants() is deprecated. Use getChildren() instead.", "", "2.0");
|
|
return this.containerNode ? query('[widgetId]', this.containerNode).map(registry.byNode) : []; // dijit/_WidgetBase[]
|
|
},
|
|
|
|
////////////////// MISCELLANEOUS METHODS ///////////////////
|
|
|
|
_onShow: function(){
|
|
// summary:
|
|
// Internal method called when this widget is made visible.
|
|
// See `onShow` for details.
|
|
this.onShow();
|
|
},
|
|
|
|
onShow: function(){
|
|
// summary:
|
|
// Called when this widget becomes the selected pane in a
|
|
// `dijit/layout/TabContainer`, `dijit/layout/StackContainer`,
|
|
// `dijit/layout/AccordionContainer`, etc.
|
|
//
|
|
// Also called to indicate display of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
|
|
// tags:
|
|
// callback
|
|
},
|
|
|
|
onHide: function(){
|
|
// summary:
|
|
// Called when another widget becomes the selected pane in a
|
|
// `dijit/layout/TabContainer`, `dijit/layout/StackContainer`,
|
|
// `dijit/layout/AccordionContainer`, etc.
|
|
//
|
|
// Also called to indicate hide of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
|
|
// tags:
|
|
// callback
|
|
},
|
|
|
|
onClose: function(){
|
|
// summary:
|
|
// Called when this widget is being displayed as a popup (ex: a Calendar popped
|
|
// up from a DateTextBox), and it is hidden.
|
|
// This is called from the dijit.popup code, and should not be called directly.
|
|
//
|
|
// Also used as a parameter for children of `dijit/layout/StackContainer` or subclasses.
|
|
// Callback if a user tries to close the child. Child will be closed if this function returns true.
|
|
// tags:
|
|
// extension
|
|
|
|
return true; // Boolean
|
|
}
|
|
});
|
|
|
|
// For back-compat, remove in 2.0.
|
|
if(has("dijit-legacy-requires")){
|
|
ready(0, function(){
|
|
var requires = ["dijit/_base"];
|
|
require(requires); // use indirection so modules not rolled into a build
|
|
});
|
|
}
|
|
return _Widget;
|
|
});
|
|
|
|
},
|
|
'dojo/touch':function(){
|
|
define("dojo/touch", ["./_base/kernel", "./aspect", "./dom", "./on", "./has", "./mouse", "./ready", "./_base/window"],
|
|
function(dojo, aspect, dom, on, has, mouse, ready, win){
|
|
|
|
// module:
|
|
// dojo/touch
|
|
|
|
var hasTouch = has("touch");
|
|
|
|
// TODO: get iOS version from dojo/sniff after #15827 is fixed
|
|
var ios4 = false;
|
|
if(has("ios")){
|
|
var ua = navigator.userAgent;
|
|
var v = ua.match(/OS ([\d_]+)/) ? RegExp.$1 : "1";
|
|
var os = parseFloat(v.replace(/_/, '.').replace(/_/g, ''));
|
|
ios4 = os < 5;
|
|
}
|
|
|
|
var touchmove, hoveredNode;
|
|
|
|
if(hasTouch){
|
|
ready(function(){
|
|
// Keep track of currently hovered node
|
|
hoveredNode = win.body(); // currently hovered node
|
|
|
|
win.doc.addEventListener("touchstart", function(evt){
|
|
// Precede touchstart event with touch.over event. DnD depends on this.
|
|
// Use addEventListener(cb, true) to run cb before any touchstart handlers on node run,
|
|
// and to ensure this code runs even if the listener on the node does event.stop().
|
|
var oldNode = hoveredNode;
|
|
hoveredNode = evt.target;
|
|
on.emit(oldNode, "dojotouchout", {
|
|
target: oldNode,
|
|
relatedTarget: hoveredNode,
|
|
bubbles: true
|
|
});
|
|
on.emit(hoveredNode, "dojotouchover", {
|
|
target: hoveredNode,
|
|
relatedTarget: oldNode,
|
|
bubbles: true
|
|
});
|
|
}, true);
|
|
|
|
// Fire synthetic touchover and touchout events on nodes since the browser won't do it natively.
|
|
on(win.doc, "touchmove", function(evt){
|
|
var newNode = win.doc.elementFromPoint(
|
|
evt.pageX - (ios4 ? 0 : win.global.pageXOffset), // iOS 4 expects page coords
|
|
evt.pageY - (ios4 ? 0 : win.global.pageYOffset)
|
|
);
|
|
if(newNode && hoveredNode !== newNode){
|
|
// touch out on the old node
|
|
on.emit(hoveredNode, "dojotouchout", {
|
|
target: hoveredNode,
|
|
relatedTarget: newNode,
|
|
bubbles: true
|
|
});
|
|
|
|
// touchover on the new node
|
|
on.emit(newNode, "dojotouchover", {
|
|
target: newNode,
|
|
relatedTarget: hoveredNode,
|
|
bubbles: true
|
|
});
|
|
|
|
hoveredNode = newNode;
|
|
}
|
|
});
|
|
});
|
|
|
|
// Define synthetic touch.move event that unlike the native touchmove, fires for the node the finger is
|
|
// currently dragging over rather than the node where the touch started.
|
|
touchmove = function(node, listener){
|
|
return on(win.doc, "touchmove", function(evt){
|
|
if(node === win.doc || dom.isDescendant(hoveredNode, node)){
|
|
evt.target = hoveredNode;
|
|
listener.call(this, evt);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
|
|
function _handle(type){
|
|
// type: String
|
|
// press | move | release | cancel
|
|
|
|
return function(node, listener){//called by on(), see dojo.on
|
|
return on(node, type, listener);
|
|
};
|
|
}
|
|
|
|
//device neutral events - touch.press|move|release|cancel/over/out
|
|
var touch = {
|
|
press: _handle(hasTouch ? "touchstart": "mousedown"),
|
|
move: hasTouch ? touchmove :_handle("mousemove"),
|
|
release: _handle(hasTouch ? "touchend": "mouseup"),
|
|
cancel: hasTouch ? _handle("touchcancel") : mouse.leave,
|
|
over: _handle(hasTouch ? "dojotouchover": "mouseover"),
|
|
out: _handle(hasTouch ? "dojotouchout": "mouseout"),
|
|
enter: mouse._eventHandler(hasTouch ? "dojotouchover" : "mouseover"),
|
|
leave: mouse._eventHandler(hasTouch ? "dojotouchout" : "mouseout")
|
|
};
|
|
/*=====
|
|
touch = {
|
|
// summary:
|
|
// This module provides unified touch event handlers by exporting
|
|
// press, move, release and cancel which can also run well on desktop.
|
|
// Based on http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html
|
|
//
|
|
// example:
|
|
// Used with dojo.on
|
|
// | define(["dojo/on", "dojo/touch"], function(on, touch){
|
|
// | on(node, touch.press, function(e){});
|
|
// | on(node, touch.move, function(e){});
|
|
// | on(node, touch.release, function(e){});
|
|
// | on(node, touch.cancel, function(e){});
|
|
// example:
|
|
// Used with touch.* directly
|
|
// | touch.press(node, function(e){});
|
|
// | touch.move(node, function(e){});
|
|
// | touch.release(node, function(e){});
|
|
// | touch.cancel(node, function(e){});
|
|
|
|
press: function(node, listener){
|
|
// summary:
|
|
// Register a listener to 'touchstart'|'mousedown' for the given node
|
|
// node: Dom
|
|
// Target node to listen to
|
|
// listener: Function
|
|
// Callback function
|
|
// returns:
|
|
// A handle which will be used to remove the listener by handle.remove()
|
|
},
|
|
move: function(node, listener){
|
|
// summary:
|
|
// Register a listener to 'touchmove'|'mousemove' for the given node
|
|
// node: Dom
|
|
// Target node to listen to
|
|
// listener: Function
|
|
// Callback function
|
|
// returns:
|
|
// A handle which will be used to remove the listener by handle.remove()
|
|
},
|
|
release: function(node, listener){
|
|
// summary:
|
|
// Register a listener to 'touchend'|'mouseup' for the given node
|
|
// node: Dom
|
|
// Target node to listen to
|
|
// listener: Function
|
|
// Callback function
|
|
// returns:
|
|
// A handle which will be used to remove the listener by handle.remove()
|
|
},
|
|
cancel: function(node, listener){
|
|
// summary:
|
|
// Register a listener to 'touchcancel'|'mouseleave' for the given node
|
|
// node: Dom
|
|
// Target node to listen to
|
|
// listener: Function
|
|
// Callback function
|
|
// returns:
|
|
// A handle which will be used to remove the listener by handle.remove()
|
|
},
|
|
over: function(node, listener){
|
|
// summary:
|
|
// Register a listener to 'mouseover' or touch equivalent for the given node
|
|
// node: Dom
|
|
// Target node to listen to
|
|
// listener: Function
|
|
// Callback function
|
|
// returns:
|
|
// A handle which will be used to remove the listener by handle.remove()
|
|
},
|
|
out: function(node, listener){
|
|
// summary:
|
|
// Register a listener to 'mouseout' or touch equivalent for the given node
|
|
// node: Dom
|
|
// Target node to listen to
|
|
// listener: Function
|
|
// Callback function
|
|
// returns:
|
|
// A handle which will be used to remove the listener by handle.remove()
|
|
},
|
|
enter: function(node, listener){
|
|
// summary:
|
|
// Register a listener to mouse.enter or touch equivalent for the given node
|
|
// node: Dom
|
|
// Target node to listen to
|
|
// listener: Function
|
|
// Callback function
|
|
// returns:
|
|
// A handle which will be used to remove the listener by handle.remove()
|
|
},
|
|
leave: function(node, listener){
|
|
// summary:
|
|
// Register a listener to mouse.leave or touch equivalent for the given node
|
|
// node: Dom
|
|
// Target node to listen to
|
|
// listener: Function
|
|
// Callback function
|
|
// returns:
|
|
// A handle which will be used to remove the listener by handle.remove()
|
|
}
|
|
};
|
|
=====*/
|
|
|
|
1 && (dojo.touch = touch);
|
|
|
|
return touch;
|
|
});
|
|
|
|
},
|
|
'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=\"listbox\" aria-haspopup=\"true\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents\" role=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitInputField dijitButtonText\" data-dojo-attach-point=\"containerNode,_popupStateNode\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitValidationContainer\"\n\t\t\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t/></div\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} data-dojo-attach-point=\"valueNode\" value=\"${value}\" aria-hidden=\"true\"\n\t\t/></td\n\t\t><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer\"\n\t\t\tdata-dojo-attach-point=\"titleNode\" role=\"presentation\"\n\t\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t\t${_buttonInputDisabled}\n\t\t/></td\n\t></tr></tbody\n></table>\n",
|
|
'dojo/fx':function(){
|
|
define("dojo/fx", [
|
|
"./_base/lang",
|
|
"./Evented",
|
|
"./_base/kernel",
|
|
"./_base/array",
|
|
"./_base/connect",
|
|
"./_base/fx",
|
|
"./dom",
|
|
"./dom-style",
|
|
"./dom-geometry",
|
|
"./ready",
|
|
"require" // for context sensitive loading of Toggler
|
|
], function(lang, Evented, dojo, arrayUtil, connect, baseFx, dom, domStyle, geom, ready, require){
|
|
|
|
// module:
|
|
// dojo/fx
|
|
|
|
// 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 = {
|
|
// summary:
|
|
// Effects library on top of Base animations
|
|
};
|
|
|
|
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 = function(/*dojo/_base/fx.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`
|
|
// | fx.chain([
|
|
// | dojo.fadeIn({ node:node }),
|
|
// | dojo.fadeOut({ node:otherNode })
|
|
// | ]).play();
|
|
//
|
|
return new _chain(animations); // dojo/_base/fx.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 = function(/*dojo/_base/fx.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
|
|
// | fx.combine([
|
|
// | dojo.fadeIn({ node:node }),
|
|
// | dojo.fadeOut({ node:otherNode })
|
|
// | ]).play();
|
|
//
|
|
// example:
|
|
// When the longest animation ends, execute a function:
|
|
// | var anim = 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/_base/fx.Animation
|
|
};
|
|
|
|
coreFx.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:
|
|
// | 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/_base/fx.Animation
|
|
};
|
|
|
|
coreFx.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:
|
|
// | 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/_base/fx.Animation
|
|
};
|
|
|
|
coreFx.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/_base/fx.Animation
|
|
};
|
|
|
|
return coreFx;
|
|
});
|
|
|
|
},
|
|
'dijit/_DialogMixin':function(){
|
|
define("dijit/_DialogMixin", [
|
|
"dojo/_base/declare", // declare
|
|
"./a11y" // _getTabNavigable
|
|
], function(declare, a11y){
|
|
|
|
// module:
|
|
// dijit/_DialogMixin
|
|
|
|
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/Tree':function(){
|
|
require({cache:{
|
|
'url:dijit/templates/TreeNode.html':"<div class=\"dijitTreeNode\" role=\"presentation\"\n\t><div data-dojo-attach-point=\"rowNode\" class=\"dijitTreeRow dijitInline\" role=\"presentation\"\n\t\t><div data-dojo-attach-point=\"indentNode\" class=\"dijitInline\"></div\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\"></span>\n\t\t</span\n\t></div>\n\t<div data-dojo-attach-point=\"containerNode\" class=\"dijitTreeContainer\" role=\"presentation\" style=\"display: none;\"></div>\n</div>\n",
|
|
'url:dijit/templates/Tree.html':"<div class=\"dijitTree dijitTreeContainer\" role=\"tree\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" data-dojo-attach-point=\"indentDetector\"></div>\n</div>\n"}});
|
|
define("dijit/Tree", [
|
|
"dojo/_base/array", // array.filter array.forEach array.map
|
|
"dojo/_base/connect", // connect.isCopyKey()
|
|
"dojo/cookie", // cookie
|
|
"dojo/_base/declare", // declare
|
|
"dojo/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/errors/create", // createError
|
|
"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/on", // on(), on.selector()
|
|
"dojo/topic",
|
|
"dojo/touch",
|
|
"dojo/when",
|
|
"./focus",
|
|
"./registry", // registry.byNode(), registry.getEnclosingWidget()
|
|
"./_base/manager", // 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, createError, fxUtils, kernel, keys, lang, on, topic, touch, when,
|
|
focus, registry, manager, _Widget, _TemplatedMixin, _Container, _Contained, _CssStateMixin,
|
|
treeNodeTemplate, treeTemplate, TreeStoreModel, ForestStoreModel, _dndSelector){
|
|
|
|
// module:
|
|
// dijit/Tree
|
|
|
|
// Back-compat shim
|
|
Deferred = declare(Deferred, {
|
|
addCallback: function(callback){ this.then(callback); },
|
|
addErrback: function(errback){ this.then(null, errback); }
|
|
});
|
|
|
|
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"
|
|
},
|
|
|
|
// 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"); // TODOC: what is this for???
|
|
domStyle.set(this.indentNode, 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");
|
|
|
|
this.tree._startPaint(true); // signifies paint started and finished (synchronously)
|
|
},
|
|
|
|
_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/_base/Deferred
|
|
}
|
|
|
|
// cancel in progress collapse operation
|
|
if(this._collapseDeferred){
|
|
this._collapseDeferred.cancel();
|
|
delete this._collapseDeferred;
|
|
}
|
|
|
|
// 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.showRoot){
|
|
this.tree.domNode.setAttribute("aria-expanded", "true");
|
|
}
|
|
|
|
var def,
|
|
wipeIn = fxUtils.wipeIn({
|
|
node: this.containerNode,
|
|
duration: manager.defaultDuration,
|
|
onEnd: function(){
|
|
def.resolve(true);
|
|
}
|
|
});
|
|
|
|
// Deferred that fires when expand is complete
|
|
def = (this._expandDeferred = new Deferred(function(){
|
|
// Canceller
|
|
wipeIn.stop();
|
|
}));
|
|
|
|
wipeIn.play();
|
|
|
|
return def; // dojo/_base/Deferred
|
|
},
|
|
|
|
collapse: function(){
|
|
// summary:
|
|
// Collapse this node (if it's expanded)
|
|
|
|
if(this._collapseDeferred){
|
|
// Node is already collapsed, or there's a collapse in progress, just return that Deferred
|
|
return this._collapseDeferred;
|
|
}
|
|
|
|
// 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.showRoot){
|
|
this.tree.domNode.setAttribute("aria-expanded", "false");
|
|
}
|
|
domClass.remove(this.contentNode,'dijitTreeContentExpanded');
|
|
this._setExpando();
|
|
this._updateItemClasses(this.item);
|
|
|
|
var def,
|
|
wipeOut = fxUtils.wipeOut({
|
|
node: this.containerNode,
|
|
duration: manager.defaultDuration,
|
|
onEnd: function(){
|
|
def.resolve(true);
|
|
}
|
|
});
|
|
|
|
// Deferred that fires when expand is complete
|
|
def = (this._collapseDeferred = new Deferred(function(){
|
|
// Canceller
|
|
wipeOut.stop();
|
|
}));
|
|
|
|
wipeOut.play();
|
|
|
|
return def; // dojo/_base/Deferred
|
|
},
|
|
|
|
// 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.
|
|
var oldChildren = this.getChildren();
|
|
array.forEach(oldChildren, function(child){
|
|
_Container.prototype.removeChild.call(this, child);
|
|
}, this);
|
|
|
|
// All the old children of this TreeNode are subject for destruction if
|
|
// 1) they aren't listed in the new children array (items)
|
|
// 2) they aren't immediately adopted by another node (DnD)
|
|
this.defer(function(){
|
|
array.forEach(oldChildren, function(node){
|
|
if(!node._destroyed && !node.getParent()){
|
|
// If node is in selection then remove it.
|
|
tree.dndController.removeTreeNode(node);
|
|
|
|
// Deregister mapping from item id --> this node
|
|
var id = model.getIdentity(node.item),
|
|
ary = tree._itemNodesMap[id];
|
|
if(ary.length == 1){
|
|
delete tree._itemNodesMap[id];
|
|
}else{
|
|
var index = array.indexOf(ary, node);
|
|
if(index != -1){
|
|
ary.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
// And finally we can destroy the node
|
|
node.destroyRecursive();
|
|
}
|
|
});
|
|
});
|
|
|
|
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){ // MARKER: REUSE NODE
|
|
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),
|
|
ownerDocument: tree.ownerDocument,
|
|
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");
|
|
}
|
|
}
|
|
|
|
var def = new DeferredList(defs);
|
|
this.tree._startPaint(def); // to reset TreeNode widths after an item is added/removed from the Tree
|
|
return def; // dojo/_base/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);
|
|
},
|
|
|
|
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 ? "true" : "false");
|
|
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");
|
|
},
|
|
|
|
|
|
_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 of waiting 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: 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.
|
|
//
|
|
// 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 [{}];
|
|
},
|
|
=====*/
|
|
|
|
// 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 registry.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";
|
|
}
|
|
|
|
// Deferred that fires when all the children have loaded.
|
|
this.expandChildrenDeferred = new Deferred();
|
|
|
|
// Deferred that fires when all pending operations complete.
|
|
this.pendingCommandsDeferred = this.expandChildrenDeferred;
|
|
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
postCreate: function(){
|
|
this._initState();
|
|
|
|
// Catch events on TreeNodes
|
|
var self = this;
|
|
this.own(
|
|
on(this.domNode, on.selector(".dijitTreeNode", touch.enter), function(evt){
|
|
self._onNodeMouseEnter(registry.byNode(this), evt);
|
|
}),
|
|
on(this.domNode, on.selector(".dijitTreeNode", touch.leave), function(evt){
|
|
self._onNodeMouseLeave(registry.byNode(this), evt);
|
|
}),
|
|
on(this.domNode, on.selector(".dijitTreeNode", "click"), function(evt){
|
|
self._onClick(registry.byNode(this), evt);
|
|
}),
|
|
on(this.domNode, on.selector(".dijitTreeNode", "dblclick"), function(evt){
|
|
self._onDblClick(registry.byNode(this), evt);
|
|
}),
|
|
on(this.domNode, on.selector(".dijitTreeNode", "keypress"), function(evt){
|
|
self._onKeyPress(registry.byNode(this), evt);
|
|
}),
|
|
on(this.domNode, on.selector(".dijitTreeNode", "keydown"), function(evt){
|
|
self._onKeyDown(registry.byNode(this), evt);
|
|
}),
|
|
on(this.domNode, on.selector(".dijitTreeRow", "focusin"), function(evt){
|
|
self._onNodeFocus(registry.getEnclosingWidget(this), evt);
|
|
})
|
|
);
|
|
|
|
// 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.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);
|
|
}
|
|
|
|
this._load();
|
|
|
|
// If no path was specified to the constructor, use path saved in cookie
|
|
if(!this.params.path && !this.params.paths && this.persist){
|
|
this.set("paths", this.dndController._getSavedPaths());
|
|
}
|
|
|
|
// onLoadDeferred should fire when all commands that are part of initialization have completed.
|
|
// It will include all the set("paths", ...) commands that happen during initialization.
|
|
this.onLoadDeferred = this.pendingCommandsDeferred;
|
|
|
|
this.onLoadDeferred.then(lang.hitch(this, "onLoad"));
|
|
},
|
|
|
|
_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");
|
|
this.domNode.removeAttribute("aria-expanded");
|
|
this.domNode.removeAttribute("aria-multiselectable");
|
|
|
|
rn.labelNode.setAttribute("role", "presentation");
|
|
rn.containerNode.setAttribute("role", "tree");
|
|
rn.containerNode.setAttribute("aria-expanded","true");
|
|
rn.containerNode.setAttribute("aria-multiselectable", !this.dndController.singular);
|
|
}else{
|
|
this.domNode.setAttribute("aria-multiselectable", !this.dndController.singular);
|
|
}
|
|
|
|
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 if persist==true, all nodes that were previously opened
|
|
this._expandNode(rn).then(lang.hitch(this, function(){
|
|
// Then, select the nodes that were selected last time, or
|
|
// the ones specified by params.paths[].
|
|
|
|
this.expandChildrenDeferred.resolve(true);
|
|
}));
|
|
}),
|
|
lang.hitch(this, 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;
|
|
return this.pendingCommandsDeferred = this.pendingCommandsDeferred.then( 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;
|
|
|
|
// Let any previous set("path", ...) commands complete before this one starts.
|
|
return this.pendingCommandsDeferred = this.pendingCommandsDeferred.then(function(){
|
|
// We may need to wait for some nodes to expand, so setting
|
|
// each path will involve a Deferred. We bring those deferreds
|
|
// together with a 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.
|
|
selectPath(path, [tree.rootNode], d);
|
|
}else{
|
|
d.reject(new Tree.PathError("Empty path"));
|
|
}
|
|
return d;
|
|
}));
|
|
}).then(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).then(function(){ selectPath(path, nextNode.getChildren(), def); });
|
|
}else{
|
|
// Successfully reached the end of this path
|
|
def.resolve(nextNode);
|
|
}
|
|
}else{
|
|
def.reject(new Tree.PathError("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){
|
|
// summary:
|
|
// Marks the specified TreeNodes as selected.
|
|
// nodes: TreeNode[]
|
|
// TreeNodes to mark.
|
|
this.dndController.setSelection(nodes);
|
|
},
|
|
|
|
|
|
expandAll: function(){
|
|
// summary:
|
|
// Expand all nodes in the tree
|
|
// returns:
|
|
// Deferred that fires when all nodes have expanded
|
|
|
|
var _this = this;
|
|
|
|
function expand(node){
|
|
var def = new dojo.Deferred();
|
|
|
|
// Expand the node
|
|
_this._expandNode(node).then(function(){
|
|
// When node has expanded, call expand() recursively on each non-leaf child
|
|
var childBranches = array.filter(node.getChildren() || [], function(node){
|
|
return node.isExpandable;
|
|
}),
|
|
defs = array.map(childBranches, expand);
|
|
|
|
// And when all those recursive calls finish, signal that I'm finished
|
|
new dojo.DeferredList(defs).then(function(){
|
|
def.resolve(true);
|
|
});
|
|
});
|
|
|
|
return def;
|
|
}
|
|
|
|
return expand(this.rootNode);
|
|
},
|
|
|
|
collapseAll: function(){
|
|
// summary:
|
|
// Collapse all nodes in the tree
|
|
// returns:
|
|
// Deferred that fires when all nodes have collapsed
|
|
|
|
var _this = this;
|
|
|
|
function collapse(node){
|
|
var def = new dojo.Deferred();
|
|
def.label = "collapseAllDeferred";
|
|
|
|
// Collapse children first
|
|
var childBranches = array.filter(node.getChildren() || [], function(node){
|
|
return node.isExpandable;
|
|
}),
|
|
defs = array.map(childBranches, collapse);
|
|
|
|
// And when all those recursive calls finish, collapse myself, unless I'm the invisible root node,
|
|
// in which case collapseAll() is finished
|
|
new dojo.DeferredList(defs).then(function(){
|
|
if(!node.isExpanded || (node == _this.rootNode && !_this.showRoot)){
|
|
def.resolve(true);
|
|
}else{
|
|
_this._collapseNode(node).then(function(){
|
|
// When node has collapsed, signal that call is finished
|
|
def.resolve(true);
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
return def;
|
|
}
|
|
|
|
return collapse(this.rootNode);
|
|
},
|
|
|
|
////////////// 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(/*TreeNode*/ treeNode, /*Event*/ e){
|
|
// summary:
|
|
// Handles keystrokes for printable keys, doing search navigation
|
|
|
|
if(e.charCode <= 32){
|
|
// Avoid duplicate events on firefox (this is an arrow key that will be handled by keydown handler)
|
|
return;
|
|
}
|
|
|
|
if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){
|
|
var c = String.fromCharCode(e.charCode);
|
|
this._onLetterKeyNav( { node: treeNode, key: c.toLowerCase() } );
|
|
event.stop(e);
|
|
}
|
|
},
|
|
|
|
_onKeyDown: function(/*TreeNode*/ treeNode, /*Event*/ e){
|
|
// summary:
|
|
// Handles arrow, space, and enter keys
|
|
|
|
var key = e.keyCode;
|
|
|
|
var map = this._keyHandlerMap;
|
|
if(!map){
|
|
// Setup table mapping keys to events.
|
|
// 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.
|
|
// Therefore, also allow space key for keyboard "click" operation.
|
|
map = {};
|
|
map[keys.ENTER] = 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]){
|
|
// 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){
|
|
this._curSearch.timer.remove();
|
|
delete this._curSearch;
|
|
}
|
|
|
|
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;
|
|
cs.timer.remove();
|
|
}else{
|
|
// We are starting a new search
|
|
cs = this._curSearch = {
|
|
pattern: message.key,
|
|
startNode: message.node
|
|
};
|
|
}
|
|
|
|
// set/reset timer to forget recent keystrokes
|
|
cs.timer = this.defer(function(){
|
|
delete this._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) || dom.isDescendant(node, widget.expandoNodeText);
|
|
},
|
|
|
|
_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: Object
|
|
// Object from the dojo/store corresponding to this TreeNode
|
|
// node: TreeNode
|
|
// The TreeNode itself
|
|
// evt: Event
|
|
// The event
|
|
// tags:
|
|
// callback
|
|
},
|
|
onDblClick: function(/*===== item, node, evt =====*/){
|
|
// summary:
|
|
// Callback when a tree node is double-clicked
|
|
// item: Object
|
|
// Object from the dojo/store corresponding to this TreeNode
|
|
// node: TreeNode
|
|
// The TreeNode itself
|
|
// evt: Event
|
|
// The 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: Object
|
|
// Object from the dojo/store corresponding to this TreeNode
|
|
// node: TreeNode
|
|
// The TreeNode itself
|
|
// 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
|
|
// returns:
|
|
// Deferred that fires when the node is closed
|
|
|
|
if(node._expandNodeDeferred){
|
|
delete node._expandNodeDeferred;
|
|
}
|
|
|
|
if(node.state == "LOADING"){
|
|
// ignore clicks while we are in the process of loading data
|
|
return;
|
|
}
|
|
|
|
if(node.isExpanded){
|
|
var ret = node.collapse();
|
|
|
|
this.onClose(node.item, node);
|
|
this._state(node, false);
|
|
|
|
this._startPaint(ret); // after this finishes, need to reset widths of TreeNodes
|
|
|
|
return ret;
|
|
}
|
|
},
|
|
|
|
_expandNode: function(/*TreeNode*/ node){
|
|
// summary:
|
|
// Called when the user has requested to expand the node
|
|
// returns:
|
|
// Deferred that fires when the node is loaded and opened and (if persist=true) all it's descendants
|
|
// that were previously opened too
|
|
|
|
// Signal that this call is complete
|
|
var def = new Deferred();
|
|
|
|
if(node._expandNodeDeferred){
|
|
// there's already an expand in progress, or completed, so just return
|
|
return node._expandNodeDeferred; // dojo/_base/Deferred
|
|
}
|
|
|
|
var model = this.model,
|
|
item = node.item,
|
|
_this = this;
|
|
|
|
// Load data if it's not already loaded
|
|
if(!node._loadDeferred){
|
|
// need to load all the children before expanding
|
|
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.
|
|
node._loadDeferred = 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.
|
|
node.setChildItems(items).then(function(){
|
|
node._loadDeferred.resolve(items);
|
|
});
|
|
},
|
|
function(err){
|
|
console.error(_this, ": error loading " + node.label + " children: ", err);
|
|
node._loadDeferred.reject(err);
|
|
}
|
|
);
|
|
}
|
|
|
|
// Expand the node after data has loaded
|
|
node._loadDeferred.then(lang.hitch(this, function(){
|
|
node.expand().then(function(){
|
|
def.resolve(true); // signal that this _expandNode() call is complete
|
|
});
|
|
|
|
// seems like these should be inside of then(), but left here for back-compat about
|
|
// when this.isOpen flag gets set (ie, at the beginning of the animation)
|
|
this.onOpen(node.item, node);
|
|
this._state(node, true);
|
|
}));
|
|
|
|
this._startPaint(def); // after this finishes, need to reset widths of TreeNodes
|
|
|
|
return def; // dojo/_base/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/_WidgetBase*/ 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/_WidgetBase*/ /*===== node =====*/){
|
|
// summary:
|
|
// Called when mouse is over a node (onmouseenter event),
|
|
// this is monitored by the DND code
|
|
},
|
|
|
|
_onNodeMouseLeave: function(/*dijit/_WidgetBase*/ /*===== 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.
|
|
// Not called from new dojo.store interface but there's cleanup code in setChildItems() instead.
|
|
|
|
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];
|
|
}
|
|
if(this.persist && this.cookieName){
|
|
var ary = [];
|
|
for(var id in this._openedNodes){
|
|
ary.push(id);
|
|
}
|
|
cookie(this.cookieName, ary.join(","), {expires:365});
|
|
}
|
|
}
|
|
},
|
|
|
|
destroy: function(){
|
|
if(this._curSearch){
|
|
this._curSearch.timer.remove();
|
|
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 main 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).
|
|
// If the Tree is hidden domGeometry.position(this.tree.indentDetector).w will return 0, in which case just
|
|
// keep the default value.
|
|
this._nodePixelIndent = domGeometry.position(this.tree.indentDetector).w || this._nodePixelIndent;
|
|
|
|
// resize() may be called before this.rootNode is created, so wait until it's available
|
|
this.expandChildrenDeferred.then(lang.hitch(this, function(){
|
|
// If tree has already loaded, then reset indent for all the nodes
|
|
this.rootNode.set('indent', this.showRoot ? 0 : -1);
|
|
|
|
// Also, adjust widths of all rows to match width of Tree
|
|
this._adjustWidths();
|
|
}));
|
|
},
|
|
|
|
_outstandingPaintOperations: 0,
|
|
_startPaint: function(/*Promise|Boolean*/ p){
|
|
// summary:
|
|
// Called at the start of an operation that will change what's displayed.
|
|
// p:
|
|
// Promise that tells when the operation will complete. Alternately, if it's just a Boolean, it signifies
|
|
// that the operation was synchronous, and already completed.
|
|
|
|
this._outstandingPaintOperations++;
|
|
if(this._adjustWidthsTimer){
|
|
this._adjustWidthsTimer.remove();
|
|
delete this._adjustWidthsTimer;
|
|
}
|
|
|
|
var oc = lang.hitch(this, function(){
|
|
this._outstandingPaintOperations--;
|
|
|
|
if(this._outstandingPaintOperations <= 0 && !this._adjustWidthsTimer && this._started){
|
|
// Use defer() to avoid a width adjustment when another operation will immediately follow,
|
|
// such as a sequence of opening a node, then it's children, then it's grandchildren, etc.
|
|
this._adjustWidthsTimer = this.defer("_adjustWidths");
|
|
}
|
|
});
|
|
when(p, oc, oc);
|
|
},
|
|
|
|
_adjustWidths: function(){
|
|
// summary:
|
|
// Get width of widest TreeNode, or the width of the Tree itself, whichever is greater,
|
|
// and then set all TreeNodes to that width, so that selection/hover highlighting
|
|
// extends to the edge of the Tree (#13141)
|
|
|
|
if(this._adjustWidthsTimer){
|
|
this._adjustWidthsTimer.remove();
|
|
delete this._adjustWidthsTimer;
|
|
}
|
|
|
|
var maxWidth = 0,
|
|
nodes = [];
|
|
function collect(/*TreeNode*/ parent){
|
|
var node = parent.rowNode;
|
|
node.style.width = "auto"; // erase setting from previous run
|
|
maxWidth = Math.max(maxWidth, node.clientWidth);
|
|
nodes.push(node);
|
|
if(parent.isExpanded){
|
|
array.forEach(parent.getChildren(), collect);
|
|
}
|
|
}
|
|
collect(this.rootNode);
|
|
maxWidth = Math.max(maxWidth, domGeometry.getContentBox(this.domNode).w); // do after node.style.width="auto"
|
|
array.forEach(nodes, function(node){
|
|
node.style.width = maxWidth + "px"; // assumes no horizontal padding, border, or margin on rowNode
|
|
});
|
|
},
|
|
|
|
_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.PathError = createError("TreePathError");
|
|
Tree._TreeNode = TreeNode; // for monkey patching or creating subclasses of TreeNode
|
|
|
|
return Tree;
|
|
});
|
|
|
|
},
|
|
'dijit/form/_FormValueWidget':function(){
|
|
define("dijit/form/_FormValueWidget", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/sniff", // has("ie")
|
|
"./_FormWidget",
|
|
"./_FormValueMixin"
|
|
], function(declare, has, _FormWidget, _FormValueMixin){
|
|
|
|
// module:
|
|
// dijit/form/_FormValueWidget
|
|
|
|
return declare("dijit.form._FormValueWidget", [_FormWidget, _FormValueMixin],
|
|
{
|
|
// summary:
|
|
// Base class for widgets corresponding to native HTML elements such as `<input>` or `<select>`
|
|
// that have user changeable values.
|
|
// description:
|
|
// Each _FormValueWidget represents a single input value, and has a (possibly hidden) `<input>` element,
|
|
// to which it serializes it's input value, so that form submission (either normal submission or via FormBind?)
|
|
// works as expected.
|
|
|
|
// Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared
|
|
// directly in the template as read by the parser in order to function. IE is known to specifically
|
|
// require the 'name' attribute at element creation time. See #8484, #8660.
|
|
|
|
_layoutHackIE7: function(){
|
|
// summary:
|
|
// Work around table sizing bugs on IE7 by forcing redraw
|
|
|
|
if(has("ie") == 7){ // fix IE7 layout bug when the widget is scrolled out of sight
|
|
var domNode = this.domNode;
|
|
var parent = domNode.parentNode;
|
|
var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter
|
|
var origFilter = pingNode.style.filter; // save custom filter, most likely nothing
|
|
var _this = this;
|
|
while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet
|
|
(function ping(){
|
|
var disconnectHandle = _this.connect(parent, "onscroll",
|
|
function(){
|
|
_this.disconnect(disconnectHandle); // only call once
|
|
pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique
|
|
_this.defer(function(){ pingNode.style.filter = origFilter; }); // restore custom filter, if any
|
|
}
|
|
);
|
|
})();
|
|
parent = parent.parentNode;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
'*now':function(r){r(['dojo/i18n!*preload*dojo/nls/tt-rss-layer*["ar","ca","cs","da","de","el","en-gb","en-us","es-es","fi-fi","fr-fr","he-il","hu","it-it","ja-jp","ko-kr","nl-nl","nb","pl","pt-br","pt-pt","ru","sk","sl","sv","th","tr","zh-tw","zh-cn","ROOT"]']);}
|
|
}});
|
|
define("dojo/tt-rss-layer", [], 1);
|