1 // ==========================================================================
  2 // Project:   The M-Project - Mobile HTML5 Application Framework
  3 // Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
  4 //            (c) 2011 panacoda GmbH. All rights reserved.
  5 // Creator:   Sebastian
  6 // Date:      04.11.2010
  7 // License:   Dual licensed under the MIT or GPL Version 2 licenses.
  8 //            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
  9 //            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
 10 // ==========================================================================
 11 
 12 /**
 13  * A constant value for input type: text
 14  *
 15  * @type String
 16  */
 17 M.INPUT_TEXT = 'text';
 18 
 19 /**
 20  * A constant value for input type: password
 21  *
 22  * @type String
 23  */
 24 M.INPUT_PASSWORD = 'password';
 25 
 26 /**
 27  * A constant value for input type: number
 28  *
 29  * @type String
 30  */
 31 M.INPUT_NUMBER = 'number';
 32 
 33 /**
 34  * A constant value for input type: tel
 35  *
 36  * @type String
 37  */
 38 M.INPUT_TELEPHONE = 'tel';
 39 
 40 /**
 41  * A constant value for input type: url
 42  *
 43  * @type String
 44  */
 45 M.INPUT_URL = 'url';
 46 
 47 /**
 48  * A constant value for input type: email
 49  *
 50  * @type String
 51  */
 52 M.INPUT_EMAIL = 'email';
 53 
 54 /**
 55  * A constant value for input type: time
 56  *
 57  * @type String
 58  */
 59 M.INPUT_TIME = 'time';
 60 
 61 /**
 62  * A constant value for input type: date
 63  *
 64  * @type String
 65  */
 66 M.INPUT_DATE = 'date';
 67 
 68 /**
 69  * A constant value for input type: month
 70  *
 71  * @type String
 72  */
 73 M.INPUT_MONTH = 'month';
 74 
 75 /**
 76  * A constant value for input type: week
 77  *
 78  * @type String
 79  */
 80 M.INPUT_WEEK = 'week';
 81 
 82 /**
 83  * A constant value for input type: datetime
 84  *
 85  * @type String
 86  */
 87 M.INPUT_DATETIME = 'datetime';
 88 
 89 /**
 90  * A constant value for input type: datetime-local
 91  *
 92  * @type String
 93  */
 94 M.INPUT_DATETIME_LOCAL = 'datetime-local';
 95 
 96 /**
 97  * @class
 98  *
 99  * M.TextFieldView is the prototype of any text field input view. It can be rendered as both
100  * a single line text field and a multiple line text field. If it is styled as a multiple
101  * line text field, is has a built-in autogrow mechanism so the textfield is getting larger
102  * depending on the number of lines of text a user enters.
103  *
104  * @extends M.View
105  */
106 M.TextFieldView = M.View.extend(
107 /** @scope M.TextFieldView.prototype */ {
108 
109    /**
110      * The type of this object.
111      *
112      * @type String
113      */
114     type: 'M.TextFieldView',
115 
116    /**
117     * The name of the text field. During the rendering, this property gets assigned to the name
118     * property of the text field's html representation. This can be used to manually access the
119     * text field's DOM representation later on.
120     *
121     * @type String
122     */
123     name: null,
124 
125     /**
126      * The label proeprty defines a text that is shown above or next to the textfield as a 'title'
127      * for the textfield. e.g. "Name:". If no label is specified, no label will be displayed.
128      *
129      * @type String
130      */
131     label: null,
132 
133     /**
134      * The initial text shown inside the text field describing the input or making a suggestion for input
135      * e.g. "Please enter your Name."
136      *
137      * @type String
138      */
139     initialText: '',
140 
141     /**
142      * Determines whether to display the textfield grouped with the label specified with the label property.
143      * If set to YES, the textfield and its label are wrapped in a container and styled as a unit 'out of
144      * the box'. If set to NO, custom styling could be necessary.
145      *
146      * If there is no label specified, this property is ignored by default.
147      *
148      * @type Boolean
149      */
150     isGrouped: NO,
151 
152     /**
153      * Defines whether the text field has multiple lines respectively is a text area.
154      *
155      * @type Boolean
156      */
157     hasMultipleLines: NO,
158 
159     /**
160      * This property specifies the input type of this input field. Possible values are:
161      *
162      *   - M.INPUT_TEXT --> text input (default)
163      *   - M.INPUT_PASSWORD --> password
164      *   - M.INPUT_NUMBER --> number
165      *   - M.INPUT_TELEPHONE --> tel
166      *   - M.INPUT_URL --> url
167      *   - M.INPUT_EMAIL --> email
168      *
169      * Note, that these types are not yet supported by all browsers!
170      *
171      * @type String
172      */
173     inputType: M.INPUT_TEXT,
174 
175     /**
176      * This property is used internally to determine all the possible input types for a
177      * date textfield.
178      *
179      * @private
180      * @type Array
181      */
182     dateInputTypes: [M.INPUT_DATETIME, M.INPUT_DATE, M.INPUT_MONTH, M.INPUT_WEEK, M.INPUT_TIME, M.INPUT_DATETIME_LOCAL],
183 
184     /**
185      * This property can be used to specify the allowed number if chars for this text field
186      * view. If nothing is specified, the corresponding 'maxlength' HTML property will not
187      * be set.
188      *
189      * @type Number
190      */
191     numberOfChars: null,
192 
193     /**
194      * This property can be used to specify whether to use the native implementation
195      * of one of the HTML5 input types if it is available. If set to YES, e.g. iOS5
196      * will render its own date/time picker controls to the corresponding input
197      * type. If set to no, the native implementation will be disabled.
198      *
199      * @type Boolean
200      */
201     useNativeImplementationIfAvailable: YES,
202 
203     /**
204      * This property specifies the recommended events for this type of view.
205      *
206      * @type Array
207      */
208     recommendedEvents: ['focus', 'blur', 'enter', 'keyup', 'tap'],
209 
210     /**
211      * Renders a TextFieldView
212      * 
213      * @private
214      * @returns {String} The text field view's html representation.
215      */
216     render: function() {
217         this.computeValue();
218         this.html += '<div';
219 
220         if(this.label && this.isGrouped) {
221             this.html += ' data-role="fieldcontain"';
222         }
223 
224         if(this.cssClass) {
225             this.html += ' class="' + this.cssClass + '_container"';
226         }
227 
228         this.html += '>';
229 
230         if(this.label) {
231             this.html += '<label for="' + (this.name ? this.name : this.id) + '">' + this.label + '</label>';
232         }
233 
234         if(this.hasMultipleLines) {
235             this.html += '<textarea cols="40" rows="8" name="' + (this.name ? this.name : this.id) + '" id="' + this.id + '"' + this.style() + '>' + (this.value ? this.value : this.initialText) + '</textarea>';
236             
237         } else {
238             var type = this.inputType;
239             if(_.include(this.dateInputTypes, this.inputType) && !this.useNativeImplementationIfAvailable || (this.initialText && this.inputType == M.INPUT_PASSWORD)) {
240                 type = 'text';
241             }
242             
243             this.html += '<input ' + (this.numberOfChars ? 'maxlength="' + this.numberOfChars + '"' : '') + 'type="' + type + '" name="' + (this.name ? this.name : this.id) + '" id="' + this.id + '"' + this.style() + ' value="' + (this.value ? this.value : this.initialText) + '" />';
244         }
245 
246         this.html += '</div>';
247 
248         return this.html;
249     },
250 
251     /**
252      * This method is responsible for registering events for view elements and its child views. It
253      * basically passes the view's event-property to M.EventDispatcher to bind the appropriate
254      * events.
255      *
256      * It extend M.View's registerEvents method with some special stuff for text field views and
257      * their internal events.
258      */
259     registerEvents: function() {
260         this.internalEvents = {
261             focus: {
262                 target: this,
263                 action: 'gotFocus'
264             },
265             blur: {
266                 target: this,
267                 action: 'lostFocus'
268             },
269             keyup: {
270                 target: this,
271                 action: 'setValueFromDOM'
272             },
273             tap: {
274                 target: this,
275                 action: 'handleTap'
276             }
277         }
278         this.bindToCaller(this, M.View.registerEvents)();
279     },
280 
281     /**
282      * The contentDidChange method is automatically called by the observable when the
283      * observable's state did change. It then updates the view's value property based
284      * on the specified content binding.
285      *
286      * This is a special implementation for M.TextFieldView.
287      */
288     contentDidChange: function(){
289         /* if the text field has the focus, we do not apply the content binding */
290         if(this.hasFocus) {
291             return;
292         }
293 
294         /* let M.View do the real job */
295         this.bindToCaller(this, M.View.contentDidChange)();
296 
297         this.renderUpdate();
298         this.delegateValueUpdate();
299     },
300 
301     /**
302      * Updates a TextFieldView with DOM access by jQuery.
303      *
304      * @param {Boolean} preventValueComputing Determines whether to execute computeValue() or not.
305      * @private
306      */
307     renderUpdate: function(preventValueComputing) {
308         if(!preventValueComputing) {
309             this.computeValue();
310         }
311         $('#' + this.id).val(this.value);
312         this.styleUpdate();
313     },
314 
315     /**
316      * This method is called whenever the view is taped/clicked. Typically a text
317      * field view would not use a tap event. But since a tap is called before the
318      * focus event, we use this to do some input type depending stuff, e.g. show
319      * a date picker.
320      *
321      * @param {String} id The DOM id of the event target.
322      * @param {Object} event The DOM event.
323      * @param {Object} nextEvent The next event (external event), if specified.
324      */
325     handleTap: function(id, event, nextEvent) {
326         if(_.include(this.dateInputTypes, this.inputType) && (!M.Environment.supportsInputType(this.inputType) || !this.useNativeImplementationIfAvailable)) {
327             M.DatePickerView.show({
328                 source: this,
329                 useSourceDateAsInitialDate: YES,
330                 showDatePicker: (this.inputType !== M.INPUT_TIME),
331                 showTimePicker: (this.inputType === M.INPUT_TIME || this.inputType === M.INPUT_DATETIME || this.inputType === M.INPUT_DATETIME_LOCAL),
332                 dateOrder: (this.inputType === M.INPUT_MONTH ? M.DatePickerView.dateOrderMonthOnly : M.DatePickerView.dateOrder),
333                 dateFormat: (this.inputType === M.INPUT_MONTH ? M.DatePickerView.dateFormatMonthOnly : M.DatePickerView.dateFormat)
334             });
335         }
336 
337         if(nextEvent) {
338             M.EventDispatcher.callHandler(nextEvent, event, YES);
339         }
340     },
341 
342     /**
343      * This method is called whenever the view gets the focus.
344      * If there is a initial text specified and the value of this text field
345      * still equals this initial text, the value is emptied.
346      *
347      * @param {String} id The DOM id of the event target.
348      * @param {Object} event The DOM event.
349      * @param {Object} nextEvent The next event (external event), if specified.
350      */
351     gotFocus: function(id, event, nextEvent) {
352         if(this.initialText && (!this.value || this.initialText === this.value)) {
353             this.setValue('');
354         }
355         this.hasFocus = YES;
356 
357         if(nextEvent) {
358             M.EventDispatcher.callHandler(nextEvent, event, YES);
359         }
360     },
361 
362     /**
363      * This method is called whenever the view lost the focus.
364      * If there is a initial text specified and the value of this text field
365      * is empty, the value is set to the initial text.
366      *
367      * @param {String} id The DOM id of the event target.
368      * @param {Object} event The DOM event.
369      * @param {Object} nextEvent The next event (external event), if specified.
370      */
371     lostFocus: function(id, event, nextEvent) {
372         /* if this is a native date field, get the value from dom */
373         if(_.include(this.dateInputTypes, this.inputType) && M.Environment.supportsInputType(this.inputType) && this.useNativeImplementationIfAvailable) {
374             this.setValueFromDOM();
375         }
376 
377         if(this.initialText && !this.value) {
378             this.setValue(this.initialText, NO);
379             this.value = '';
380         }
381         this.hasFocus = NO;
382 
383         if(nextEvent) {
384             M.EventDispatcher.callHandler(nextEvent, event, YES);
385         }
386     },
387 
388     /**
389      * Method to append css styles inline to the rendered text field.
390      *
391      * @private
392      * @returns {String} The text field's styling as html representation.
393      */
394     style: function() {
395         var html = ' style="';
396         if(this.isInline) {
397             html += 'display:inline;';
398         }
399         html += '"';
400 
401         if(!this.isEnabled) {
402             html += ' disabled="disabled"';
403         }
404         
405         if(this.cssClass) {
406             html += ' class="' + this.cssClass + '"';
407         }
408 
409         return html;
410     },
411 
412     /**
413      * Triggers the rendering engine, jQuery mobile, to style the text field.
414      *
415      * @private
416      */
417     theme: function() {
418         if(this.initialText && !this.value && this.cssClassOnInit) {
419             this.addCssClass(this.cssClassOnInit);
420         }
421 
422         /* trigger keyup event to make the text field autogrow */
423         if(this.value) {
424             $('#'  + this.id).trigger('keyup');
425         }
426     },
427 
428     /**
429      * Method to append css styles inline to the rendered view on the fly.
430      *
431      * @private
432      */
433     styleUpdate: function() {
434         /* trigger keyup event to make the text field autogrow (enable fist, if necessary) */
435         if(this.value) {
436             $('#' + this.id).removeAttr('disabled');
437             $('#'  + this.id).trigger('keyup');
438         }
439 
440         if(this.isInline) {
441             $('#' + this.id).attr('display', 'inline');
442         } else {
443             $('#' + this.id).removeAttr('display');
444         }
445 
446         if(!this.isEnabled) {
447             $('#' + this.id).attr('disabled', 'disabled');
448         } else {
449             $('#' + this.id).removeAttr('disabled');
450         }
451     },
452 
453     /**
454      * This method sets its value to the value it has in its DOM representation
455      * and then delegates these changes to a controller property if the
456      * contentBindingReverse property is set.
457      *
458      * Additionally call target / action if set.
459      *
460      * @param {String} id The DOM id of the event target.
461      * @param {Object} event The DOM event.
462      * @param {Object} nextEvent The next event (external event), if specified.
463      */
464     setValueFromDOM: function(id, event, nextEvent) {
465         this.value = this.secure($('#' + this.id).val());
466         this.delegateValueUpdate();
467 
468         if(nextEvent) {
469             M.EventDispatcher.callHandler(nextEvent, event, YES);
470         }
471     },
472 
473     /**
474      * This method sets the text field's value, initiates its re-rendering
475      * and call the delegateValueUpdate().
476      *
477      * @param {String} value The value to be applied to the text field view.
478      * @param {Boolean} delegateUpdate Determines whether to delegate this value update to any observer or not.
479      * @param {Boolean} preventValueComputing Determines whether to execute computeValue() or not.
480      */
481     setValue: function(value, delegateUpdate, preventValueComputing) {
482         this.value = value;
483 
484 		// Handle the classOnInit for initial text
485 		if(value != this.initialText) {
486 			if(this.cssClassOnInit) {
487 				this.removeCssClass(this.cssClassOnInit);
488 			}
489 			if(this.inputType == M.INPUT_PASSWORD) {
490 				// Set the field type to password
491 				$('#' + this.id).prop('type','password');
492 			}
493 		}
494 		else {
495             if(this.cssClassOnInit) {
496                 this.addCssClass(this.cssClassOnInit);
497             }
498 
499 			if(this.inputType == M.INPUT_PASSWORD) {
500 				// Set the field type to text
501 				$('#' + this.id).prop('type','text');
502 			}
503 		}
504 
505         this.renderUpdate(preventValueComputing);
506 
507         if(delegateUpdate) {
508             this.delegateValueUpdate();
509         }
510     },
511 
512     /**
513      * This method disables the text field by setting the disabled property of its
514      * html representation to true.
515      */
516     disable: function() {
517         this.isEnabled = NO;
518         this.renderUpdate();
519     },
520 
521     /**
522      * This method enables the text field by setting the disabled property of its
523      * html representation to false.
524      */
525     enable: function() {
526         this.isEnabled = YES;
527         this.renderUpdate();
528     },
529 
530     /**
531      * This method clears the text field's value, both in the DOM and within the JS object.
532      */
533     clearValue: function() {
534         this.setValue('');
535 
536         /* call lostFocus() to get the initial text displayed */
537         this.lostFocus();
538     },
539 
540     /**
541      * This method returns the text field view's value.
542      *
543      * @returns {String} The text field view's value.
544      */
545     getValue: function() {
546         return this.value;
547     }
548 
549 });
550