You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
9.0 KiB
254 lines
9.0 KiB
define([ |
|
"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/on", |
|
"dojo/sniff", // has("webkit") |
|
"dojo/window", // winUtils.scrollIntoView |
|
"../a11y" // a11y.hasDefaultTabStop |
|
], function(array, declare, domAttr, domStyle, lang, mouse, on, 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); |
|
|
|
// Set disabled property if focusNode is an <input>, but aria-disabled attribute if focusNode is a <span>. |
|
// Can't use "disabled" in this.focusNode as a test because on IE, that's true for all nodes. |
|
if(/^(button|input|select|textarea|optgroup|option|fieldset)$/i.test(this.focusNode.tagName)){ |
|
domAttr.set(this.focusNode, 'disabled', value); |
|
// IE has a Caret Browsing mode (hit F7 to activate) where disabled textboxes can be modified |
|
// textboxes marked readonly if disabled to avoid this issue. |
|
if (has('trident') && 'readOnly' in this) { |
|
domAttr.set(this.focusNode, 'readonly', value || this.readOnly); |
|
} |
|
}else{ |
|
this.focusNode.setAttribute("aria-disabled", value ? "true" : "false"); |
|
} |
|
|
|
// And also set disabled on the hidden <input> node |
|
if(this.valueNode){ |
|
domAttr.set(this.valueNode, 'disabled', value); |
|
} |
|
|
|
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 behavior). |
|
// Browsers often need help to make sure the focus via mouse actually gets to the focusNode. |
|
// TODO: consider removing all of this for 2.0 or sooner, see #16622 etc. |
|
if(by == "mouse" && this.isFocusable()){ |
|
// IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused. |
|
var focusHandle = this.own(on(this.focusNode, "focus", function(){ |
|
mouseUpHandle.remove(); |
|
focusHandle.remove(); |
|
}))[0]; |
|
// Set a global event to handle mouseup, so it fires properly |
|
// even if the cursor leaves this.domNode before the mouse up event. |
|
var event = has("pointer-events") ? "pointerup" : has("MSPointer") ? "MSPointerUp" : |
|
has("touch-events") ? "touchend, mouseup" : // seems like overkill but see #16622, #16725 |
|
"mouseup"; |
|
var mouseUpHandle = this.own(on(this.ownerDocumentBody, event, lang.hitch(this, function(evt){ |
|
mouseUpHandle.remove(); |
|
focusHandle.remove(); |
|
// if here, then the mousedown did not focus the focusNode as the default action |
|
if(this.focused){ |
|
if(evt.type == "touchend"){ |
|
this.defer("focus"); // native focus hasn't occurred yet |
|
}else{ |
|
this.focus(); // native focus already occurred on mousedown |
|
} |
|
} |
|
})))[0]; |
|
} |
|
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); |
|
} |
|
}); |
|
});
|
|
|