/* This file contains proprietary software owned by Motorola Mobility, Inc.
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved.
*/ /** @module montage/ui/text-input @requires montage/ui/component @requires montage/ui/native-control @requires montage/core/core */ var Montage = require("montage").Montage, Component = require("ui/component").Component, NativeControl = require("ui/native-control").NativeControl; /** The base class for all text-based input components. You typically won't create instances of this prototype. @class module:montage/ui/text-input.TextInput @extends module:montage/ui/native-control.NativeControl @see module:"montage/ui/input-date.reel".DateInput @see module:"montage/ui/input-text.reel".InputText @see module:"montage/ui/input-number.reel".InputNumber @see module:"montage/ui/input-range.reel".RangeInput @see module:"montage/ui/textarea.reel".TextArea */ var TextInput = exports.TextInput = Montage.create(NativeControl, /** @lends module:montage/ui/text-input.TextInput# */ { _hasFocus: { enumerable: false, value: false }, _value: { enumerable: false, value: null }, _valueSyncedWithInputField: { enumerable: false, value: false }, /** The "typed" data value associated with the input element. When this property is set, if the component's converter property is non-null then its revert() method is invoked, passing it the newly assigned value. The revert() function is responsible for validating and converting the user-supplied value to its typed format. For example, in the case of a DateInput component (which extends TextInput) a user enters a string for the date (for example, "10-12-2005"). A DateConverter object is assigned to the component's converter property. If the comopnent doesn't specify a converter object then the raw value is assigned to value. @type {string} @default null */ value: { get: function() { return this._value; }, set: function(value, fromInput) { if(value !== this._value) { if(this.converter) { var convertedValue; try { convertedValue = this.converter.revert(value); if (this.error) { this.error = null; } this._value = convertedValue; } catch(e) { // unable to convert - maybe error this.error = e; this._valueSyncedWithInputField = false; } } else { this._value = value; } if(fromInput) { this._valueSyncedWithInputField = true; //this.needsDraw = true; } else { this._valueSyncedWithInputField = false; this.needsDraw = true; } } }, serializable: true }, // set value from user input /** @private */ _setValue: { value: function() { var newValue = this.element.value; Object.getPropertyDescriptor(this, "value").set.call(this, newValue, true); } }, /** A reference to a Converter object whose revert() function is invoked when a new value is assigned to the TextInput object's value property. The revert() function attempts to transform the newly assigned value into a "typed" data property. For instance, a DateInput component could assign a DateConverter object to this property to convert a user-supplied date string into a standard date format. @type {Converter} @default null @see {@link module:montage/core/converter.Converter} */ converter:{ value: null, serializable: true }, _error: { value: false }, /** If an error is thrown by the converter object during a new value assignment, this property is set to true, and schedules a new draw cycle so the the UI can be updated to indicate the error state. the montage-text-invalid CSS class is assigned to the component's DOM element during the next draw cycle. @type {boolean} @default false */ error: { get: function() { return this._error; }, set: function(v) { this._error = v; this.errorMessage = this._error ? this._error.message : null; this.needsDraw = true; } }, _errorMessage: {value: null}, /** The message to display when the component is in an error state. @type {string} @default null */ errorMessage: { get: function() { return this._errorMessage; }, set: function(v) { this._errorMessage = v; } }, _updateOnInput: { value: true }, /** When this property and the converter's allowPartialConversion are both true, as the user enters text in the input element each new character is added to the component's value property, which triggers the conversion. Depending on the type of input element being used, this behavior may not be desirable. For instance, you likely would not want to convert a date string as a user is entering it, only when they've completed their input. Specifies whether @type {boolean} @default true */ updateOnInput: { get: function() { return !!this._updateOnInput; }, set: function(v) { this._updateOnInput = v; }, serializable: true }, // HTMLInputElement methods blur: { value: function() { this._element.blur(); } }, focus: { value: function() { this._element.focus(); } }, // select() defined where it's allowed // click() deliberately omitted, use focus() instead // Callbacks prepareForDraw: { enumerable: false, value: function() { var el = this.element; el.addEventListener("focus", this); el.addEventListener('input', this); el.addEventListener('change', this); el.addEventListener('blur', this); } }, _setElementValue: { value: function(value) { this.element.value = (value == null ? '' : value); } }, draw: { enumerable: false, value: function() { Object.getPrototypeOf(TextInput).draw.call(this); var el = this.element; if (!this._valueSyncedWithInputField) { this._setElementValue(this.converter ? this.converter.convert(this._value) : this._value); } if (this.error) { el.classList.add('montage-text-invalid'); el.title = this.error.message || ''; } else { el.classList.remove("montage-text-invalid"); el.title = ''; } } }, didDraw: { enumerable: false, value: function() { if (this._hasFocus && this._value != null) { var length = this._value.toString().length; this.element.setSelectionRange(length, length); } this._valueSyncedWithInputField = true; } }, // Event handlers handleInput: { enumerable: false, value: function() { if (this.converter) { if (this.converter.allowPartialConversion === true && this.updateOnInput === true) { this._setValue(); } } else { this._setValue(); } } }, /** Description TODO @function @param {Event Handler} event TODO */ handleChange: { enumerable: false, value: function(event) { this._setValue(); this._hasFocus = false; } }, /** Description TODO @function @param {Event Handler} event TODO */ handleBlur: { enumerable: false, value: function(event) { this._hasFocus = false; } }, /** Description TODO @function @param {Event Handler} event TODO */ handleFocus: { enumerable: false, value: function(event) { this._hasFocus = true; } } }); // Standard tag attributes - http://www.w3.org/TR/html5/the-input-element.html#the-input-element TextInput.addAttributes({ accept: null, alt: null, autocomplete: null, autofocus: {dataType: "boolean"}, checked: {dataType: "boolean"}, dirname: null, disabled: {dataType: 'boolean'}, form: null, formaction: null, formenctype: null, formmethod: null, formnovalidate: {dataType: 'boolean'}, formtarget: null, height: null, list: null, maxlength: null, multiple: {dataType: 'boolean'}, name: null, pattern: null, placeholder: null, readonly: {dataType: 'boolean'}, required: {dataType: 'boolean'}, size: null, src: null, width: null // "type" is not bindable and "value" is handled as a special attribute });