228 lines
7.9 KiB
JavaScript
228 lines
7.9 KiB
JavaScript
|
define("dijit/form/_FormWidgetMixin", [
|
||
|
"dojo/_base/array", // array.forEach
|
||
|
"dojo/_base/declare", // declare
|
||
|
"dojo/dom-attr", // domAttr.set
|
||
|
"dojo/dom-style", // domStyle.get
|
||
|
"dojo/_base/lang", // lang.hitch lang.isArray
|
||
|
"dojo/mouse", // mouse.isLeft
|
||
|
"dojo/_base/sniff", // has("webkit")
|
||
|
"dojo/_base/window", // win.body
|
||
|
"dojo/window", // winUtils.scrollIntoView
|
||
|
"../a11y" // a11y.hasDefaultTabStop
|
||
|
], function(array, declare, domAttr, domStyle, lang, mouse, has, win, winUtils, a11y){
|
||
|
|
||
|
// module:
|
||
|
// dijit/form/_FormWidgetMixin
|
||
|
// summary:
|
||
|
// Mixin for widgets corresponding to native HTML elements such as <checkbox> or <button>,
|
||
|
// which can be children of a <form> node or a `dijit.form.Form` widget.
|
||
|
|
||
|
return declare("dijit.form._FormWidgetMixin", null, {
|
||
|
// summary:
|
||
|
// Mixin for widgets corresponding to native HTML elements such as <checkbox> or <button>,
|
||
|
// which can be children of a <form> node or a `dijit.form.Form` widget.
|
||
|
//
|
||
|
// description:
|
||
|
// Represents a single HTML element.
|
||
|
// All these widgets should have these attributes just like native HTML input elements.
|
||
|
// You can set them during widget construction or afterwards, via `dijit._Widget.attr`.
|
||
|
//
|
||
|
// They also share some common methods.
|
||
|
|
||
|
// name: [const] String
|
||
|
// Name used when submitting form; same as "name" attribute or plain HTML elements
|
||
|
name: "",
|
||
|
|
||
|
// alt: String
|
||
|
// Corresponds to the native HTML <input> element's attribute.
|
||
|
alt: "",
|
||
|
|
||
|
// value: String
|
||
|
// Corresponds to the native HTML <input> element's attribute.
|
||
|
value: "",
|
||
|
|
||
|
// type: [const] String
|
||
|
// Corresponds to the native HTML <input> element's attribute.
|
||
|
type: "text",
|
||
|
|
||
|
// tabIndex: Integer
|
||
|
// Order fields are traversed when user hits the tab key
|
||
|
tabIndex: "0",
|
||
|
_setTabIndexAttr: "focusNode", // force copy even when tabIndex default value, needed since Button is <span>
|
||
|
|
||
|
// disabled: Boolean
|
||
|
// Should this widget respond to user input?
|
||
|
// In markup, this is specified as "disabled='disabled'", or just "disabled".
|
||
|
disabled: false,
|
||
|
|
||
|
// intermediateChanges: Boolean
|
||
|
// Fires onChange for each value change or only on demand
|
||
|
intermediateChanges: false,
|
||
|
|
||
|
// scrollOnFocus: Boolean
|
||
|
// On focus, should this widget scroll into view?
|
||
|
scrollOnFocus: true,
|
||
|
|
||
|
// Override _WidgetBase mapping id to this.domNode, needs to be on focusNode so <label> etc.
|
||
|
// works with screen reader
|
||
|
_setIdAttr: "focusNode",
|
||
|
|
||
|
_setDisabledAttr: function(/*Boolean*/ value){
|
||
|
this._set("disabled", value);
|
||
|
domAttr.set(this.focusNode, 'disabled', value);
|
||
|
if(this.valueNode){
|
||
|
domAttr.set(this.valueNode, 'disabled', value);
|
||
|
}
|
||
|
this.focusNode.setAttribute("aria-disabled", value ? "true" : "false");
|
||
|
|
||
|
if(value){
|
||
|
// reset these, because after the domNode is disabled, we can no longer receive
|
||
|
// mouse related events, see #4200
|
||
|
this._set("hovering", false);
|
||
|
this._set("active", false);
|
||
|
|
||
|
// clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes)
|
||
|
var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex :
|
||
|
("_setTabIndexAttr" in this) ? this._setTabIndexAttr : "focusNode";
|
||
|
array.forEach(lang.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
|
||
|
var node = this[attachPointName];
|
||
|
// complex code because tabIndex=-1 on a <div> doesn't work on FF
|
||
|
if(has("webkit") || a11y.hasDefaultTabStop(node)){ // see #11064 about webkit bug
|
||
|
node.setAttribute('tabIndex', "-1");
|
||
|
}else{
|
||
|
node.removeAttribute('tabIndex');
|
||
|
}
|
||
|
}, this);
|
||
|
}else{
|
||
|
if(this.tabIndex != ""){
|
||
|
this.set('tabIndex', this.tabIndex);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_onFocus: function(/*String*/ by){
|
||
|
// If user clicks on the widget, even if the mouse is released outside of it,
|
||
|
// this widget's focusNode should get focus (to mimic native browser hehavior).
|
||
|
// Browsers often need help to make sure the focus via mouse actually gets to the focusNode.
|
||
|
if(by == "mouse" && this.isFocusable()){
|
||
|
// IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused.
|
||
|
var focusConnector = this.connect(this.focusNode, "onfocus", function(){
|
||
|
this.disconnect(mouseUpConnector);
|
||
|
this.disconnect(focusConnector);
|
||
|
});
|
||
|
// Set a global event to handle mouseup, so it fires properly
|
||
|
// even if the cursor leaves this.domNode before the mouse up event.
|
||
|
var mouseUpConnector = this.connect(win.body(), "onmouseup", function(){
|
||
|
this.disconnect(mouseUpConnector);
|
||
|
this.disconnect(focusConnector);
|
||
|
// if here, then the mousedown did not focus the focusNode as the default action
|
||
|
if(this.focused){
|
||
|
this.focus();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if(this.scrollOnFocus){
|
||
|
this.defer(function(){ winUtils.scrollIntoView(this.domNode); }); // without defer, the input caret position can change on mouse click
|
||
|
}
|
||
|
this.inherited(arguments);
|
||
|
},
|
||
|
|
||
|
isFocusable: function(){
|
||
|
// summary:
|
||
|
// Tells if this widget is focusable or not. Used internally by dijit.
|
||
|
// tags:
|
||
|
// protected
|
||
|
return !this.disabled && this.focusNode && (domStyle.get(this.domNode, "display") != "none");
|
||
|
},
|
||
|
|
||
|
focus: function(){
|
||
|
// summary:
|
||
|
// Put focus on this widget
|
||
|
if(!this.disabled && this.focusNode.focus){
|
||
|
try{ this.focusNode.focus(); }catch(e){}/*squelch errors from hidden nodes*/
|
||
|
}
|
||
|
},
|
||
|
|
||
|
compare: function(/*anything*/ val1, /*anything*/ val2){
|
||
|
// summary:
|
||
|
// Compare 2 values (as returned by get('value') for this widget).
|
||
|
// tags:
|
||
|
// protected
|
||
|
if(typeof val1 == "number" && typeof val2 == "number"){
|
||
|
return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
|
||
|
}else if(val1 > val2){
|
||
|
return 1;
|
||
|
}else if(val1 < val2){
|
||
|
return -1;
|
||
|
}else{
|
||
|
return 0;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onChange: function(/*===== newValue =====*/){
|
||
|
// summary:
|
||
|
// Callback when this widget's value is changed.
|
||
|
// tags:
|
||
|
// callback
|
||
|
},
|
||
|
|
||
|
// _onChangeActive: [private] Boolean
|
||
|
// Indicates that changes to the value should call onChange() callback.
|
||
|
// This is false during widget initialization, to avoid calling onChange()
|
||
|
// when the initial value is set.
|
||
|
_onChangeActive: false,
|
||
|
|
||
|
_handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
|
||
|
// summary:
|
||
|
// Called when the value of the widget is set. Calls onChange() if appropriate
|
||
|
// newValue:
|
||
|
// the new value
|
||
|
// priorityChange:
|
||
|
// For a slider, for example, dragging the slider is priorityChange==false,
|
||
|
// but on mouse up, it's priorityChange==true. If intermediateChanges==false,
|
||
|
// onChange is only called form priorityChange=true events.
|
||
|
// tags:
|
||
|
// private
|
||
|
if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
|
||
|
// this block executes not for a change, but during initialization,
|
||
|
// and is used to store away the original value (or for ToggleButton, the original checked state)
|
||
|
this._resetValue = this._lastValueReported = newValue;
|
||
|
}
|
||
|
this._pendingOnChange = this._pendingOnChange
|
||
|
|| (typeof newValue != typeof this._lastValueReported)
|
||
|
|| (this.compare(newValue, this._lastValueReported) != 0);
|
||
|
if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
|
||
|
this._lastValueReported = newValue;
|
||
|
this._pendingOnChange = false;
|
||
|
if(this._onChangeActive){
|
||
|
if(this._onChangeHandle){
|
||
|
this._onChangeHandle.remove();
|
||
|
}
|
||
|
// 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);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
});
|