source: Dev/trunk/src/client/dijit/form/_FormWidgetMixin.js

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 8.2 KB
Line 
1define([
2        "dojo/_base/array", // array.forEach
3        "dojo/_base/declare", // declare
4        "dojo/dom-attr", // domAttr.set
5        "dojo/dom-style", // domStyle.get
6        "dojo/_base/lang", // lang.hitch lang.isArray
7        "dojo/mouse", // mouse.isLeft
8        "dojo/on",
9        "dojo/sniff", // has("webkit")
10        "dojo/window", // winUtils.scrollIntoView
11        "../a11y"    // a11y.hasDefaultTabStop
12], function(array, declare, domAttr, domStyle, lang, mouse, on, has, winUtils, a11y){
13
14        // module:
15        //              dijit/form/_FormWidgetMixin
16
17        return declare("dijit.form._FormWidgetMixin", null, {
18                // summary:
19                //              Mixin for widgets corresponding to native HTML elements such as `<checkbox>` or `<button>`,
20                //              which can be children of a `<form>` node or a `dijit/form/Form` widget.
21                //
22                // description:
23                //              Represents a single HTML element.
24                //              All these widgets should have these attributes just like native HTML input elements.
25                //              You can set them during widget construction or afterwards, via `dijit/_WidgetBase.set()`.
26                //
27                //              They also share some common methods.
28
29                // name: [const] String
30                //              Name used when submitting form; same as "name" attribute or plain HTML elements
31                name: "",
32
33                // alt: String
34                //              Corresponds to the native HTML `<input>` element's attribute.
35                alt: "",
36
37                // value: String
38                //              Corresponds to the native HTML `<input>` element's attribute.
39                value: "",
40
41                // type: [const] String
42                //              Corresponds to the native HTML `<input>` element's attribute.
43                type: "text",
44
45                // type: String
46                //              Apply aria-label in markup to the widget's focusNode
47                "aria-label": "focusNode",
48
49                // tabIndex: String
50                //              Order fields are traversed when user hits the tab key
51                tabIndex: "0",
52                _setTabIndexAttr: "focusNode", // force copy even when tabIndex default value, needed since Button is <span>
53
54                // disabled: Boolean
55                //              Should this widget respond to user input?
56                //              In markup, this is specified as "disabled='disabled'", or just "disabled".
57                disabled: false,
58
59                // intermediateChanges: Boolean
60                //              Fires onChange for each value change or only on demand
61                intermediateChanges: false,
62
63                // scrollOnFocus: Boolean
64                //              On focus, should this widget scroll into view?
65                scrollOnFocus: true,
66
67                // Override _WidgetBase mapping id to this.domNode, needs to be on focusNode so <label> etc.
68                // works with screen reader
69                _setIdAttr: "focusNode",
70
71                _setDisabledAttr: function(/*Boolean*/ value){
72                        this._set("disabled", value);
73                        domAttr.set(this.focusNode, 'disabled', value);
74                        if(this.valueNode){
75                                domAttr.set(this.valueNode, 'disabled', value);
76                        }
77                        this.focusNode.setAttribute("aria-disabled", value ? "true" : "false");
78
79                        if(value){
80                                // reset these, because after the domNode is disabled, we can no longer receive
81                                // mouse related events, see #4200
82                                this._set("hovering", false);
83                                this._set("active", false);
84
85                                // clear tab stop(s) on this widget's focusable node(s)  (ComboBox has two focusable nodes)
86                                var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex :
87                                        ("_setTabIndexAttr" in this) ? this._setTabIndexAttr : "focusNode";
88                                array.forEach(lang.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
89                                        var node = this[attachPointName];
90                                        // complex code because tabIndex=-1 on a <div> doesn't work on FF
91                                        if(has("webkit") || a11y.hasDefaultTabStop(node)){    // see #11064 about webkit bug
92                                                node.setAttribute('tabIndex', "-1");
93                                        }else{
94                                                node.removeAttribute('tabIndex');
95                                        }
96                                }, this);
97                        }else{
98                                if(this.tabIndex != ""){
99                                        this.set('tabIndex', this.tabIndex);
100                                }
101                        }
102                },
103
104                _onFocus: function(/*String*/ by){
105                        // If user clicks on the widget, even if the mouse is released outside of it,
106                        // this widget's focusNode should get focus (to mimic native browser behavior).
107                        // Browsers often need help to make sure the focus via mouse actually gets to the focusNode.
108                        // TODO: consider removing all of this for 2.0 or sooner, see #16622 etc.
109                        if(by == "mouse" && this.isFocusable()){
110                                // IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused.
111                                var focusHandle = this.own(on(this.focusNode, "focus", function(){
112                                        mouseUpHandle.remove();
113                                        focusHandle.remove();
114                                }))[0];
115                                // Set a global event to handle mouseup, so it fires properly
116                                // even if the cursor leaves this.domNode before the mouse up event.
117                                var mouseUpHandle = this.own(on(this.ownerDocumentBody, "mouseup, touchend", lang.hitch(this, function(evt){
118                                        mouseUpHandle.remove();
119                                        focusHandle.remove();
120                                        // if here, then the mousedown did not focus the focusNode as the default action
121                                        if(this.focused){
122                                                if(evt.type == "touchend"){
123                                                        this.defer("focus"); // native focus hasn't occurred yet
124                                                }else{
125                                                        this.focus(); // native focus already occurred on mousedown
126                                                }
127                                        }
128                                })))[0];
129                        }
130                        if(this.scrollOnFocus){
131                                this.defer(function(){
132                                        winUtils.scrollIntoView(this.domNode);
133                                }); // without defer, the input caret position can change on mouse click
134                        }
135                        this.inherited(arguments);
136                },
137
138                isFocusable: function(){
139                        // summary:
140                        //              Tells if this widget is focusable or not.  Used internally by dijit.
141                        // tags:
142                        //              protected
143                        return !this.disabled && this.focusNode && (domStyle.get(this.domNode, "display") != "none");
144                },
145
146                focus: function(){
147                        // summary:
148                        //              Put focus on this widget
149                        if(!this.disabled && this.focusNode.focus){
150                                try{
151                                        this.focusNode.focus();
152                                }catch(e){
153                                }
154                                /*squelch errors from hidden nodes*/
155                        }
156                },
157
158                compare: function(/*anything*/ val1, /*anything*/ val2){
159                        // summary:
160                        //              Compare 2 values (as returned by get('value') for this widget).
161                        // tags:
162                        //              protected
163                        if(typeof val1 == "number" && typeof val2 == "number"){
164                                return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
165                        }else if(val1 > val2){
166                                return 1;
167                        }else if(val1 < val2){
168                                return -1;
169                        }else{
170                                return 0;
171                        }
172                },
173
174                onChange: function(/*===== newValue =====*/){
175                        // summary:
176                        //              Callback when this widget's value is changed.
177                        // tags:
178                        //              callback
179                },
180
181                // _onChangeActive: [private] Boolean
182                //              Indicates that changes to the value should call onChange() callback.
183                //              This is false during widget initialization, to avoid calling onChange()
184                //              when the initial value is set.
185                _onChangeActive: false,
186
187                _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
188                        // summary:
189                        //              Called when the value of the widget is set.  Calls onChange() if appropriate
190                        // newValue:
191                        //              the new value
192                        // priorityChange:
193                        //              For a slider, for example, dragging the slider is priorityChange==false,
194                        //              but on mouse up, it's priorityChange==true.  If intermediateChanges==false,
195                        //              onChange is only called form priorityChange=true events.
196                        // tags:
197                        //              private
198                        if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
199                                // this block executes not for a change, but during initialization,
200                                // and is used to store away the original value (or for ToggleButton, the original checked state)
201                                this._resetValue = this._lastValueReported = newValue;
202                        }
203                        this._pendingOnChange = this._pendingOnChange
204                                || (typeof newValue != typeof this._lastValueReported)
205                                || (this.compare(newValue, this._lastValueReported) != 0);
206                        if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
207                                this._lastValueReported = newValue;
208                                this._pendingOnChange = false;
209                                if(this._onChangeActive){
210                                        if(this._onChangeHandle){
211                                                this._onChangeHandle.remove();
212                                        }
213                                        // defer allows hidden value processing to run and
214                                        // also the onChange handler can safely adjust focus, etc
215                                        this._onChangeHandle = this.defer(
216                                                function(){
217                                                        this._onChangeHandle = null;
218                                                        this.onChange(newValue);
219                                                }); // try to collapse multiple onChange's fired faster than can be processed
220                                }
221                        }
222                },
223
224                create: function(){
225                        // Overrides _Widget.create()
226                        this.inherited(arguments);
227                        this._onChangeActive = true;
228                },
229
230                destroy: function(){
231                        if(this._onChangeHandle){ // destroy called before last onChange has fired
232                                this._onChangeHandle.remove();
233                                this.onChange(this._lastValueReported);
234                        }
235                        this.inherited(arguments);
236                }
237        });
238});
Note: See TracBrowser for help on using the repository browser.