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

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

Added Dojo 1.9.3 release.

File size: 16.6 KB
Line 
1define([
2        "dojo/_base/array", // array.forEach
3        "dojo/_base/declare", // declare
4        "dojo/dom", // dom.byId
5        "dojo/has",
6        "dojo/keys", // keys.ALT keys.CAPS_LOCK keys.CTRL keys.META keys.SHIFT
7        "dojo/_base/lang", // lang.mixin
8        "dojo/on", // on
9        "../main"    // for exporting dijit._setSelectionRange, dijit.selectInputText
10], function(array, declare, dom, has, keys, lang, on, dijit){
11
12        // module:
13        //              dijit/form/_TextBoxMixin
14
15        var _TextBoxMixin = declare("dijit.form._TextBoxMixin" + (has("dojo-bidi") ? "_NoBidi" : ""), null, {
16                // summary:
17                //              A mixin for textbox form input widgets
18
19                // trim: Boolean
20                //              Removes leading and trailing whitespace if true.  Default is false.
21                trim: false,
22
23                // uppercase: Boolean
24                //              Converts all characters to uppercase if true.  Default is false.
25                uppercase: false,
26
27                // lowercase: Boolean
28                //              Converts all characters to lowercase if true.  Default is false.
29                lowercase: false,
30
31                // propercase: Boolean
32                //              Converts the first character of each word to uppercase if true.
33                propercase: false,
34
35                // maxLength: String
36                //              HTML INPUT tag maxLength declaration.
37                maxLength: "",
38
39                // selectOnClick: [const] Boolean
40                //              If true, all text will be selected when focused with mouse
41                selectOnClick: false,
42
43                // placeHolder: String
44                //              Defines a hint to help users fill out the input field (as defined in HTML 5).
45                //              This should only contain plain text (no html markup).
46                placeHolder: "",
47
48                _getValueAttr: function(){
49                        // summary:
50                        //              Hook so get('value') works as we like.
51                        // description:
52                        //              For `dijit/form/TextBox` this basically returns the value of the `<input>`.
53                        //
54                        //              For `dijit/form/MappedTextBox` subclasses, which have both
55                        //              a "displayed value" and a separate "submit value",
56                        //              This treats the "displayed value" as the master value, computing the
57                        //              submit value from it via this.parse().
58                        return this.parse(this.get('displayedValue'), this.constraints);
59                },
60
61                _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
62                        // summary:
63                        //              Hook so set('value', ...) works.
64                        //
65                        // description:
66                        //              Sets the value of the widget to "value" which can be of
67                        //              any type as determined by the widget.
68                        //
69                        // value:
70                        //              The visual element value is also set to a corresponding,
71                        //              but not necessarily the same, value.
72                        //
73                        // formattedValue:
74                        //              If specified, used to set the visual element value,
75                        //              otherwise a computed visual value is used.
76                        //
77                        // priorityChange:
78                        //              If true, an onChange event is fired immediately instead of
79                        //              waiting for the next blur event.
80
81                        var filteredValue;
82                        if(value !== undefined){
83                                // TODO: this is calling filter() on both the display value and the actual value.
84                                // I added a comment to the filter() definition about this, but it should be changed.
85                                filteredValue = this.filter(value);
86                                if(typeof formattedValue != "string"){
87                                        if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){
88                                                formattedValue = this.filter(this.format(filteredValue, this.constraints));
89                                        }else{
90                                                formattedValue = '';
91                                        }
92                                }
93                        }
94                        if(formattedValue != null /* and !undefined */ && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){
95                                this.textbox.value = formattedValue;
96                                this._set("displayedValue", this.get("displayedValue"));
97                        }
98
99                        this.inherited(arguments, [filteredValue, priorityChange]);
100                },
101
102                // displayedValue: String
103                //              For subclasses like ComboBox where the displayed value
104                //              (ex: Kentucky) and the serialized value (ex: KY) are different,
105                //              this represents the displayed value.
106                //
107                //              Setting 'displayedValue' through set('displayedValue', ...)
108                //              updates 'value', and vice-versa.  Otherwise 'value' is updated
109                //              from 'displayedValue' periodically, like onBlur etc.
110                //
111                //              TODO: move declaration to MappedTextBox?
112                //              Problem is that ComboBox references displayedValue,
113                //              for benefit of FilteringSelect.
114                displayedValue: "",
115
116                _getDisplayedValueAttr: function(){
117                        // summary:
118                        //              Hook so get('displayedValue') works.
119                        // description:
120                        //              Returns the displayed value (what the user sees on the screen),
121                        //              after filtering (ie, trimming spaces etc.).
122                        //
123                        //              For some subclasses of TextBox (like ComboBox), the displayed value
124                        //              is different from the serialized value that's actually
125                        //              sent to the server (see `dijit/form/ValidationTextBox.serialize()`)
126
127                        // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need
128                        // this method
129                        // TODO: this isn't really the displayed value when the user is typing
130                        return this.filter(this.textbox.value);
131                },
132
133                _setDisplayedValueAttr: function(/*String*/ value){
134                        // summary:
135                        //              Hook so set('displayedValue', ...) works.
136                        // description:
137                        //              Sets the value of the visual element to the string "value".
138                        //              The widget value is also set to a corresponding,
139                        //              but not necessarily the same, value.
140
141                        if(value == null /* or undefined */){
142                                value = ''
143                        }
144                        else if(typeof value != "string"){
145                                value = String(value)
146                        }
147
148                        this.textbox.value = value;
149
150                        // sets the serialized value to something corresponding to specified displayedValue
151                        // (if possible), and also updates the textbox.value, for example converting "123"
152                        // to "123.00"
153                        this._setValueAttr(this.get('value'), undefined);
154
155                        this._set("displayedValue", this.get('displayedValue'));
156                },
157
158                format: function(value /*=====, constraints =====*/){
159                        // summary:
160                        //              Replaceable function to convert a value to a properly formatted string.
161                        // value: String
162                        // constraints: Object
163                        // tags:
164                        //              protected extension
165                        return value == null /* or undefined */ ? "" : (value.toString ? value.toString() : value);
166                },
167
168                parse: function(value /*=====, constraints =====*/){
169                        // summary:
170                        //              Replaceable function to convert a formatted string to a value
171                        // value: String
172                        // constraints: Object
173                        // tags:
174                        //              protected extension
175
176                        return value;   // String
177                },
178
179                _refreshState: function(){
180                        // summary:
181                        //              After the user types some characters, etc., this method is
182                        //              called to check the field for validity etc.  The base method
183                        //              in `dijit/form/TextBox` does nothing, but subclasses override.
184                        // tags:
185                        //              protected
186                },
187
188                 onInput: function(/*===== event =====*/){
189                         // summary:
190                         //             Connect to this function to receive notifications of various user data-input events.
191                         //             Return false to cancel the event and prevent it from being processed.
192                         // event:
193                         //             keydown | keypress | cut | paste | input
194                         // tags:
195                         //             callback
196                 },
197
198                __skipInputEvent: false,
199                _onInput: function(/*Event*/ evt){
200                        // summary:
201                        //              Called AFTER the input event has happened
202
203                        this._processInput(evt);
204
205                        if(this.intermediateChanges){
206                                // allow the key to post to the widget input box
207                                this.defer(function(){
208                                        this._handleOnChange(this.get('value'), false);
209                                });
210                        }
211                },
212
213                _processInput: function(/*Event*/ evt){
214                        // summary:
215                        //              Default action handler for user input events
216
217                        this._refreshState();
218
219                        // In case someone is watch()'ing for changes to displayedValue
220                        this._set("displayedValue", this.get("displayedValue"));
221                },
222
223                postCreate: function(){
224                        // setting the value here is needed since value="" in the template causes "undefined"
225                        // and setting in the DOM (instead of the JS object) helps with form reset actions
226                        this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same
227
228                        this.inherited(arguments);
229
230                        // normalize input events to reduce spurious event processing
231                        //      onkeydown: do not forward modifier keys
232                        //                     set charOrCode to numeric keycode
233                        //      onkeypress: do not forward numeric charOrCode keys (already sent through onkeydown)
234                        //      onpaste & oncut: set charOrCode to 229 (IME)
235                        //      oninput: if primary event not already processed, set charOrCode to 229 (IME), else do not forward
236                        var handleEvent = function(e){
237                                var charOrCode;
238                                if(e.type == "keydown"){
239                                        charOrCode = e.keyCode;
240                                        switch(charOrCode){ // ignore state keys
241                                                case keys.SHIFT:
242                                                case keys.ALT:
243                                                case keys.CTRL:
244                                                case keys.META:
245                                                case keys.CAPS_LOCK:
246                                                case keys.NUM_LOCK:
247                                                case keys.SCROLL_LOCK:
248                                                        return;
249                                        }
250                                        if(!e.ctrlKey && !e.metaKey && !e.altKey){ // no modifiers
251                                                switch(charOrCode){ // ignore location keys
252                                                        case keys.NUMPAD_0:
253                                                        case keys.NUMPAD_1:
254                                                        case keys.NUMPAD_2:
255                                                        case keys.NUMPAD_3:
256                                                        case keys.NUMPAD_4:
257                                                        case keys.NUMPAD_5:
258                                                        case keys.NUMPAD_6:
259                                                        case keys.NUMPAD_7:
260                                                        case keys.NUMPAD_8:
261                                                        case keys.NUMPAD_9:
262                                                        case keys.NUMPAD_MULTIPLY:
263                                                        case keys.NUMPAD_PLUS:
264                                                        case keys.NUMPAD_ENTER:
265                                                        case keys.NUMPAD_MINUS:
266                                                        case keys.NUMPAD_PERIOD:
267                                                        case keys.NUMPAD_DIVIDE:
268                                                                return;
269                                                }
270                                                if((charOrCode >= 65 && charOrCode <= 90) || (charOrCode >= 48 && charOrCode <= 57) || charOrCode == keys.SPACE){
271                                                        return; // keypress will handle simple non-modified printable keys
272                                                }
273                                                var named = false;
274                                                for(var i in keys){
275                                                        if(keys[i] === e.keyCode){
276                                                                named = true;
277                                                                break;
278                                                        }
279                                                }
280                                                if(!named){
281                                                        return;
282                                                } // only allow named ones through
283                                        }
284                                }
285                                charOrCode = e.charCode >= 32 ? String.fromCharCode(e.charCode) : e.charCode;
286                                if(!charOrCode){
287                                        charOrCode = (e.keyCode >= 65 && e.keyCode <= 90) || (e.keyCode >= 48 && e.keyCode <= 57) || e.keyCode == keys.SPACE ? String.fromCharCode(e.keyCode) : e.keyCode;
288                                }
289                                if(!charOrCode){
290                                        charOrCode = 229; // IME
291                                }
292                                if(e.type == "keypress"){
293                                        if(typeof charOrCode != "string"){
294                                                return;
295                                        }
296                                        if((charOrCode >= 'a' && charOrCode <= 'z') || (charOrCode >= 'A' && charOrCode <= 'Z') || (charOrCode >= '0' && charOrCode <= '9') || (charOrCode === ' ')){
297                                                if(e.ctrlKey || e.metaKey || e.altKey){
298                                                        return;
299                                                } // can only be stopped reliably in keydown
300                                        }
301                                }
302                                if(e.type == "input"){
303                                        if(this.__skipInputEvent){ // duplicate event
304                                                this.__skipInputEvent = false;
305                                                return;
306                                        }
307                                }else{
308                                        this.__skipInputEvent = true;
309                                }
310                                // create fake event to set charOrCode and to know if preventDefault() was called
311                                var faux = { faux: true }, attr;
312                                for(attr in e){
313                                        if(attr != "layerX" && attr != "layerY"){ // prevent WebKit warnings
314                                                var v = e[attr];
315                                                if(typeof v != "function" && typeof v != "undefined"){
316                                                        faux[attr] = v;
317                                                }
318                                        }
319                                }
320                                lang.mixin(faux, {
321                                        charOrCode: charOrCode,
322                                        _wasConsumed: false,
323                                        preventDefault: function(){
324                                                faux._wasConsumed = true;
325                                                e.preventDefault();
326                                        },
327                                        stopPropagation: function(){
328                                                e.stopPropagation();
329                                        }
330                                });
331                                // give web page author a chance to consume the event
332                                //console.log(faux.type + ', charOrCode = (' + (typeof charOrCode) + ') ' + charOrCode + ', ctrl ' + !!faux.ctrlKey + ', alt ' + !!faux.altKey + ', meta ' + !!faux.metaKey + ', shift ' + !!faux.shiftKey);
333                                if(this.onInput(faux) === false){ // return false means stop
334                                        faux.preventDefault();
335                                        faux.stopPropagation();
336                                }
337                                if(faux._wasConsumed){
338                                        return;
339                                } // if preventDefault was called
340                                this.defer(function(){
341                                        this._onInput(faux);
342                                }); // widget notification after key has posted
343                                if(e.type == "keypress"){
344                                        e.stopPropagation(); // don't allow parents to stop printables from being typed
345                                }
346                        };
347                        this.own(on(this.textbox, "keydown, keypress, paste, cut, input, compositionend", lang.hitch(this, handleEvent)));
348                },
349
350                _blankValue: '', // if the textbox is blank, what value should be reported
351                filter: function(val){
352                        // summary:
353                        //              Auto-corrections (such as trimming) that are applied to textbox
354                        //              value on blur or form submit.
355                        // description:
356                        //              For MappedTextBox subclasses, this is called twice
357                        //
358                        //              - once with the display value
359                        //              - once the value as set/returned by set('value', ...)
360                        //
361                        //              and get('value'), ex: a Number for NumberTextBox.
362                        //
363                        //              In the latter case it does corrections like converting null to NaN.  In
364                        //              the former case the NumberTextBox.filter() method calls this.inherited()
365                        //              to execute standard trimming code in TextBox.filter().
366                        //
367                        //              TODO: break this into two methods in 2.0
368                        //
369                        // tags:
370                        //              protected extension
371                        if(val === null){
372                                return this._blankValue;
373                        }
374                        if(typeof val != "string"){
375                                return val;
376                        }
377                        if(this.trim){
378                                val = lang.trim(val);
379                        }
380                        if(this.uppercase){
381                                val = val.toUpperCase();
382                        }
383                        if(this.lowercase){
384                                val = val.toLowerCase();
385                        }
386                        if(this.propercase){
387                                val = val.replace(/[^\s]+/g, function(word){
388                                        return word.substring(0, 1).toUpperCase() + word.substring(1);
389                                });
390                        }
391                        return val;
392                },
393
394                _setBlurValue: function(){
395                        // Format the displayed value, for example (for NumberTextBox) convert 1.4 to 1.400,
396                        // or (for CurrencyTextBox) 2.50 to $2.50
397
398                        this._setValueAttr(this.get('value'), true);
399                },
400
401                _onBlur: function(e){
402                        if(this.disabled){
403                                return;
404                        }
405                        this._setBlurValue();
406                        this.inherited(arguments);
407                },
408
409                _isTextSelected: function(){
410                        return this.textbox.selectionStart != this.textbox.selectionEnd;
411                },
412
413                _onFocus: function(/*String*/ by){
414                        if(this.disabled || this.readOnly){
415                                return;
416                        }
417
418                        // Select all text on focus via click if nothing already selected.
419                        // Since mouse-up will clear the selection, need to defer selection until after mouse-up.
420                        // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event.
421                        if(this.selectOnClick && by == "mouse"){
422                                // Use on.once() to only select all text on first click only; otherwise users would have no way to clear
423                                // the selection.
424                                this._selectOnClickHandle = on.once(this.domNode, "mouseup, touchend", lang.hitch(this, function(evt){
425                                        // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up)
426                                        // and if not, then select all the text
427                                        if(!this._isTextSelected()){
428                                                _TextBoxMixin.selectInputText(this.textbox);
429                                        }
430                                }));
431                                this.own(this._selectOnClickHandle);
432
433                                // in case the mouseup never comes
434                                this.defer(function(){
435                                        if(this._selectOnClickHandle){
436                                                this._selectOnClickHandle.remove();
437                                                this._selectOnClickHandle = null;
438                                        }
439                                }, 500); // if mouseup not received soon, then treat it as some gesture
440                        }
441                        // call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport
442                        // (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip
443                        this.inherited(arguments);
444
445                        this._refreshState();
446                },
447
448                reset: function(){
449                        // Overrides `dijit/_FormWidget/reset()`.
450                        // Additionally resets the displayed textbox value to ''
451                        this.textbox.value = '';
452                        this.inherited(arguments);
453                }
454        });
455
456        if(has("dojo-bidi")){
457                _TextBoxMixin = declare("dijit.form._TextBoxMixin", _TextBoxMixin, {
458                        _setValueAttr: function(){
459                                this.inherited(arguments);
460                                this.applyTextDir(this.focusNode);
461                        },
462                        _setDisplayedValueAttr: function(){
463                                this.inherited(arguments);
464                                this.applyTextDir(this.focusNode);
465                        },
466                        _onInput: function(){
467                                this.applyTextDir(this.focusNode);
468                                this.inherited(arguments);
469                        }
470                });
471        }
472
473        _TextBoxMixin._setSelectionRange = dijit._setSelectionRange = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
474                if(element.setSelectionRange){
475                        element.setSelectionRange(start, stop);
476                }
477        };
478
479        _TextBoxMixin.selectInputText = dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
480                // summary:
481                //              Select text in the input element argument, from start (default 0), to stop (default end).
482
483                // TODO: use functions in _editor/selection.js?
484                element = dom.byId(element);
485                if(isNaN(start)){
486                        start = 0;
487                }
488                if(isNaN(stop)){
489                        stop = element.value ? element.value.length : 0;
490                }
491                try{
492                        element.focus();
493                        _TextBoxMixin._setSelectionRange(element, start, stop);
494                }catch(e){ /* squelch random errors (esp. on IE) from unexpected focus changes or DOM nodes being hidden */
495                }
496        };
497
498        return _TextBoxMixin;
499});
Note: See TracBrowser for help on using the repository browser.