/**
* Copyright (c) 2014, Oracle and/or its affiliates.
* All rights reserved.
*/
/**
* @ojcomponent oj.inputBase
* @augments oj.editableValue
* @abstract
* @since 0.6
*
* @classdesc
* <h3 id="inputBaseOverview-section">
* Abstract inputBase component
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#inputBaseOverview-section"></a>
* </h3>
*
* <p>Description: The inputBase component takes care of general needs of other input components [i.e. text + password]
*
* @param {Object=} options a map of option-value pairs to set on the component
*/
oj.__registerWidget("oj.inputBase", $['oj']['editableValue'],
{
version : "1.0.0",
widgetEventPrefix : "oj",
/**
* Convenience Array which one can extend to set the attribute to a mandatory value if it doesn't exist or is set to something else
* [
* {
* "attr" : string - attribute associated to the task
* "setMandatory" : value it must be set to [i.e. type="text"]
* }
* ]
*
* Examples:
* 1) [{"attr": "type", "setMandatory": "text"}]
*
* @expose
* @private
*/
_ATTR_CHECK : [],
/**
* Class names to be applied to this.element()
*
* @expose
* @private
*/
_CLASS_NAMES : "",
/**
* Class names to be applied to this.widget()
*
* Note that if this value is defined then the this.element will be wrapped with a root dom element
*
* @expose
* @private
*/
_WIDGET_CLASS_NAMES : "",
/**
* Class names to be applied to the dom that wraps the input and trigger elements. It is a child
* of the root dom element - this.widget().
*
* An element can be wrapped by a root dom element OR by both a root dom element and this extra
* wrapper dom. the time and date pickers have two wrappers since we want to do an extra
* wrapping when an input has trigger elements (date icon, for example). This is needed so we
* can add extra dom to the root dom element for inline messaging.
*
* @expose
* @private
*/
_ELEMENT_TRIGGER_WRAPPER_CLASS_NAMES : "",
/**
* Array to be used for oj.EditableValueUtils.initializeOptionsFromDom
*
* @expose
* @private
*/
_GET_INIT_OPTIONS_PROPS: [{attribute: 'disabled', defaultOptionValue: false, validateOption: true},
{attribute: 'pattern', defaultOptionValue: ""},
{attribute: 'placeholder', defaultOptionValue: ""},
{attribute: 'value', defaultOptionValue: null},
{attribute: 'readonly', option: 'readOnly', defaultOptionValue: false,
validateOption: true},
{attribute: 'required', defaultOptionValue: false, coerceDomValue: true, validateOption: true},
{attribute: 'title', defaultOptionValue: ""}],
/**
* If defined will append a div element containing text to be read out by Jaws when focus is placed on the input element
* and the value will be used to locate the translated text to be read out by Jaws.
*
* Note the component must also be wrapped
* Used in conjunction with the above variable. Used to locate the translated text to be read out by Jaws
*
* @expose
* @private
*/
_INPUT_HELPER_KEY: "",
_BLUR_HANDLER_KEY: "blur",
_KEYDOWN_HANDLER_KEY: "keydown",
_INPUT_HANDLER_KEY: "input",
_DROP_HANDLER_KEY: "drop",
options :
{
/**
* a converter instance that duck types {@link oj.Converter}. Or an object literal containing
* the following properties.
* <p>
* When <code class="prettyprint">converter</code> option changes due to programmatic
* intervention, the component performs various tasks based on the current state it is in. </br>
*
* <h4>Steps Performed Always</h4>
* <ul>
* <li>Any cached converter instance is cleared and new converter created. The converter hint is
* pushed to messaging. E.g., notewindow displays the new hint(s).
* </li>
* </ul>
*
* <h4>Running Validation</h4>
* <ul>
* <li>if component is valid when <code class="prettyprint">converter</code> option changes, the
* display value is refreshed.</li>
* <li>if component is invalid and is showing messages -
* <code class="prettyprint">messagesShown</code> option is non-empty, when
* <code class="prettyprint">converter</code> option changes, then all messages generated by the
* component are cleared and full validation run using its current display value.
* <ul>
* <li>if there are validation errors, then <code class="prettyprint">value</code>
* option is not updated, and the errors pushed to <code class="prettyprint">messagesShown</code>
* option. The display value is not refreshed in this case. </li>
* <li>if no errors result from the validation, <code class="prettyprint">value</code>
* option is updated; page author can listen to the <code class="prettyprint">optionChange</code>
* event on the <code class="prettyprint">value</code> option to clear custom errors. The
* display value is refreshed with the formatted value provided by converter.</li>
* </ul>
* </li>
* <li>if component is invalid and has deferred messages -
* <code class="prettyprint">messagesHidden</code> option is non-empty, when
* <code class="prettyprint">converter</code> option changes, then the display value is
* refreshed with the formatted value provided by converter.</li>
* </ul>
* </p>
*
* <h4>Clearing Messages</h4>
* <ul>
* <li>When component messages are cleared in the cases described above, messages created by
* the component that are present in both <code class="prettyprint">messagesHidden</code> and
* <code class="prettyprint">messagesShown</code> options are cleared.</li>
* <li><code class="prettyprint">messagesCustom</code> option is not cleared. Page authors can
* choose to clear it explicitly when setting the converter option.</li>
* </ul>
* </p>
*
* @property {string} type - the converter type registered with the oj.ConverterFactory.
* Supported types are 'number' and 'datetime'. See {@link oj.ConverterFactory} for details. <br/>
* E.g., <code class="prettyprint">{converter: {type: 'number'}</code>
* @property {Object=} options - optional Object literal of options that the converter expects.
* See {@link oj.IntlNumberConverter} for options supported by the number converter. See
* {@link oj.IntlDateTimeConverter} for options supported by the date time converter. <br/>
* E.g., <code class="prettyprint">{converter: {type: 'number', options: {style: 'decimal'}}</code>
*
* @example <caption>Initialize the component with a number converter instance:</caption>
* // Initialize converter instance using currency options
* var options = {style: 'currency', 'currency': 'USD', maximumFractionDigits: 0};
* var numberConverterFactory = oj.Validation.converterFactory("number");
* var salaryConverter = numberConverterFactory.createConverter(options);<br/>
* // set converter instance using converter option
* $(".selector").ojFoo({ // Foo is InputText, InputPassword, TextArea
* value: 25000,
* converter: salaryConverter
* });
*
* @example <caption>Initialize the component with converter object literal:</caption>
* $(".selector").ojFoo({ // Foo is InputText, InputPassword, TextArea
* value: new Date(),
* converter: {
* type: 'datetime',
* options : {
* formatType: 'date',
* dateFormat: 'long'
* }
* }
* });
*
* @expose
* @access public
* @instance
* @memberof! oj.inputBase
* @type {Object|undefined}
*/
converter: undefined,
/**
* The placeholder text to set on the element. Though it is possible to set placeholder
* attribute on the element itself, the component will only read the value when the component
* is created. Subsequent changes to the element's placeholder attribute will not be picked up
* and page authors should update the option directly.
*
* @example <caption>Initialize the component with the <code class="prettyprint">placeholder</code> option:</caption>
* <!-- Foo is InputText, InputPassword, TextArea -->
* <input id="userId" data-bind="ojComponent: {component: 'ojFoo', placeholder: 'User Name'}" />
*
* @example <caption>Initialize <code class="prettyprint">placeholder</code> option from html attribute:</caption>
* <!-- Foo is InputText, InputPassword, TextArea -->
* <input id="userId" data-bind="ojComponent: {component: 'ojFoo'}" placeholder="User Name" />
*
* // reading the placeholder option will return "User Name"
* $(".selector").ojFoo("option", "placeholder"); // Foo is InputText, InputPassword, TextArea<br/>
*
* @default when the option is not set, the element's placeholder attribute is used if it exists.
* If the attribute is not set and if a datetime converter is set then the converter hint is
* used. See displayOptions for details.
*
*
* @expose
* @access public
* @instance
* @memberof! oj.inputBase
* @type {string|null|undefined}
*/
placeholder: undefined,
/**
* <p>The <code class="prettyprint">rawValue</code> is the read-only option for retrieving
* the current value from the input field in text form.</p>
* <p>
* The <code class="prettyprint">rawValue</code> updates on the 'input' javascript event,
* so the <code class="prettyprint">rawValue</code> changes as the value of the input is changed.
* If the user types in '1,200' into the field, the rawValue will be '1', then '1,', then '1,2',
* ..., and finally '1,200'. Then when the user blurs or presses
* Enter the <code class="prettyprint">value</code> option gets updated.
* </p>
* <p>This is a read-only option so page authors cannot set or change it directly.</p>
* @expose
* @access public
* @instance
* @default n/a
* @memberof! oj.inputBase
* @type {string|undefined}
* @since 1.2
* @readonly
*/
rawValue: undefined,
/**
* Dictates component's readOnly state. Note that option value
* always supercedes element's attribute value and it is best practice to pass the value as an option than to
* set it as an element's attribute.
*
* @example <caption>Initialize component with <code class="prettyprint">readOnly</code> option:</caption>
* $(".selector").ojInputNumber({"readOnly": true});
*
* @expose
* @type {boolean|undefined}
* @default <code class="prettyprint">false</code>
* @instance
* @memberof! oj.inputBase
*/
readOnly: undefined
},
/**
* The base method needs to be overriden so that one can perform attribute check/set [i.e. ojInputText can only have type="text"]
*
* @protected
* @override
* @param {Object} element - jQuery selection to save attributes for
* @instance
* @memberof! oj.inputBase
*/
_SaveAttributes : function (element)
{
var ret = this._superApply(arguments);
this._processAttrCheck();
return ret;
},
/**
* @protected
* @override
* @instance
* @memberof! oj.inputBase
*/
_InitOptions: function(originalDefaults, constructorOptions)
{
this._super(originalDefaults, constructorOptions);
oj.EditableValueUtils.initializeOptionsFromDom(this._GET_INIT_OPTIONS_PROPS, constructorOptions, this);
},
/**
* 1) Initializes the options
* 2) If needed wraps the input element,
*
* @protected
* @override
* @instance
* @memberof! oj.inputBase
*/
_ComponentCreate : function()
{
var node = this.element,
ret = this._superApply(arguments),
savedAttributes = this._GetSavedAttributes(node),
readOnly = this.options['readOnly'];
this._rtl = this._GetReadingDirection() === "rtl";
// update element state using options
if (typeof readOnly === "boolean")
{
this.element.prop("readonly", readOnly);
}
if (this._DoWrapElement())
{
this._wrapElementInRootDomElement();
// may need to do an extra wrapping if the element has triggers
if (this._DoWrapElementAndTriggers())
{
this._wrapElement();
}
}
// remove pattern attribute to not trigger html5 validation + inline bubble
if ('pattern' in savedAttributes)
{
node.removeAttr('pattern');
}
this._defaultRegExpValidator = {};
this._eventHandlers = null;
return ret;
},
/**
* 1) Updates component state based on the option values
* 2) Adds the classname to the element [intentionally postponing till this process since the component might need to
* reset this.element for some reason]
* 3) Hooks up the blur handler
* 4) If necessary appends an input helper to be read out by Jaws accessibility reader
*
* @protected
* @override
* @instance
* @memberof! oj.inputBase
*/
_AfterCreate : function ()
{
var ret = this._superApply(arguments),
options = ["disabled", "readOnly"],
self = this;
if(this._CLASS_NAMES)
{
this.element.addClass(this._CLASS_NAMES);
}
//attach handlers such as blur, keydown, and drop. placed in a function so to detach the handlers as well
//when the options change
this._attachDetachEventHandlers();
this._AppendInputHelper();
$.each(options, function(index, ele)
{
if(self.options[ele])
{
self._processDisabledReadOnly(ele, self.options[ele]);
}
});
return ret;
},
_processDisabledReadOnly : function __processDisabledReadOnly(key, value)
{
if (key === "disabled")
{
this.element.prop("disabled", value);
}
if (key === "readOnly")
{
this.element.prop("readonly", value);
this._refreshStateTheming("readOnly", value);
}
if(key === "disabled" || key === "readOnly")
{
this._attachDetachEventHandlers();
}
},
/**
* @ignore
* @protected
* @override
*/
_setOption : function __setOption(key, value)
{
var retVal = this._superApply(arguments);
if(key === "disabled" || key === "readOnly")
{
this._processDisabledReadOnly(key, value);
}
if (key === "pattern")
{
this._defaultRegExpValidator[oj.ValidatorFactory.VALIDATOR_TYPE_REGEXP] = this._getImplicitRegExpValidator();
this._AfterSetOptionValidators();
}
return retVal;
},
/**
* @ignore
* @protected
* @override
*/
_destroy : function __destroy()
{
var ret = this._superApply(arguments);
this.element.off("blur drop keydown input");
if(this._inputHelper)
{
this._inputHelper.remove();
}
if(this._DoWrapElement())
{
// Bug #19639907 - DomUtils.unwrap() will avoid unwrapping if the node is being destroyed by Knockout
if (this._DoWrapElementAndTriggers())
oj.DomUtils.unwrap(this.element, this._wrapper);
else
oj.DomUtils.unwrap(this.element);
}
return ret;
},
_attachDetachEventHandlers: function __attachDetachEventHandlers()
{
if(!this.options["readOnly"] && !this.options["disabled"]) {
this._eventHandlers = {};
var blurHandler = $.proxy(this._onBlurHandler, this),
keyDownHandler = $.proxy(this._onKeyDownHandler, this),
inputHandler = $.proxy(this._onInputHandler, this),
dropHandler = function()
{
this.focus();
};
this.element.on("blur", blurHandler);
this.element.on("keydown", keyDownHandler);
this.element.on("input", inputHandler);
//other than FF when a drop is dispatched focus is placed back on the element
//this would cause difference in behavior of the observable change [as set within blur], so in order to provide
//consisteny placing the focus on the element after the drop
this.element.on("drop", dropHandler);
this._eventHandlers[this._BLUR_HANDLER_KEY] = blurHandler;
this._eventHandlers[this._KEYDOWN_HANDLER_KEY] = keyDownHandler;
this._eventHandlers[this._INPUT_HANDLER_KEY] = inputHandler;
this._eventHandlers[this._DROP_HANDLER_KEY] = dropHandler;
}
else
{
//meaning either it is readOnly or is disabled, remove the handlers if they were attached previously
if(this._eventHandlers)
{
var eventEntries = [this._BLUR_HANDLER_KEY, this._KEYDOWN_HANDLER_KEY,
this._INPUT_HANDLER_KEY, this._DROP_HANDLER_KEY];
for(var i=0, j=eventEntries.length; i < j; i++)
{
if(this._eventHandlers[eventEntries[i]])
{
this.element.off(eventEntries[i], this._eventHandlers[eventEntries[i]]);
delete this._eventHandlers[eventEntries[i]];
}
}
}
}
},
/**
* when below listed options are passed to the component, corresponding CSS will be toggled
* @private
* @const
* @type {Object}
*/
_OPTION_TO_CSS_MAPPING: {
"readOnly": "oj-read-only"
},
/**
* Performs the attribute check/set by using _ATTR_CHECK variable [i.e. ojInputText must have type be set to "text"].
*
* @private
*/
_processAttrCheck : function __processAttrCheck()
{
var attrCheck = this._ATTR_CHECK;
for(var i=0, j=attrCheck.length; i < j; i++)
{
var attr = attrCheck[i]["attr"],
setMandatoryExists = "setMandatory" in attrCheck[i];
//if it doesn't exist just have to check whether one should set it to a mandatory value
if(setMandatoryExists)
{
this.element.attr(attr, attrCheck[i]["setMandatory"]);
}
}
},
/**
* Invoked when blur is triggered of the this.element
*
* @ignore
* @protected
* @param {Event} event
*/
_onBlurHandler : function __onBlurHandler(event)
{
this._SetValue(this._GetDisplayValue(), event);
},
/**
* Invoked when keydown is triggered of the this.element
*
* When of keyCode is of Enter, invoke _SetValue on it
*
* @ignore
* @protected
* @param {Event} event
*/
_onKeyDownHandler : function __onKeyDownHandler(event)
{
if(event.keyCode === $.ui.keyCode.ENTER)
{
this._SetValue(this._GetDisplayValue(), event);
}
},
/**
* Invoked when the input event happens
*
* @ignore
* @protected
* @param {Event} event
*/
_onInputHandler : function __onInputHandler(event)
{
this._SetRawValue(event, this._GetContentElement().val());
},
/**
* Whether the this.element should be wrapped in a root dom element.
* Method so that additional conditions can be placed
*
* @ignore
* @protected
* @return {boolean}
*/
_DoWrapElement : function ()
{
return this._WIDGET_CLASS_NAMES;
},
/**
* Whether the this.element and triggers should be wrapped.
* Method so that additional conditions can be placed
*
* @ignore
* @protected
* @return {boolean}
*/
_DoWrapElementAndTriggers : function ()
{
return this._ELEMENT_TRIGGER_WRAPPER_CLASS_NAMES;
},
/**
* Wraps the this.element and adds _WIDGET_CLASS_NAMES classes to the wrapped element
*
* @private
*/
_wrapElementInRootDomElement : function _wrapElementInRootDomElement()
{
//@HTMLUpdateOK
$(this.element).wrap( $("<div>").addClass(this._WIDGET_CLASS_NAMES) );
this._wrapper = this.element.parent();
},
/**
* Wraps the this.element and adds _ELEMENT_TRIGGER_WRAPPER_CLASS_NAMES classes to the wrapped element.
* We might need this extra wrapper if the component has input+triggers (like inputDate).
*
* @private
*/
_wrapElement : function _wrapElement()
{
//@HTMLUpdateOK
$(this.element).wrap( $("<div>").addClass(this._ELEMENT_TRIGGER_WRAPPER_CLASS_NAMES) );
},
/**
* In some complex components [i.e. datepicker], when the input element receives focus we wish to have Jaws read
* out some content.
*
* For those case does this method exist.
*
* @protected
* @instance
* @memberOf !oj.inputBase
*/
_AppendInputHelper : function __AppendInputHelper()
{
if(this._INPUT_HELPER_KEY && this._DoWrapElement())
{
var describedBy = this.element.attr("aria-describedby") || "",
helperDescribedById = this._GetSubId(this._INPUT_HELPER_KEY);
describedBy += " " + helperDescribedById;
this.element.attr("aria-describedby", describedBy);
this._inputHelper = $("<div class='oj-helper-hidden-accessible' id='" + helperDescribedById + "'>" + this._EscapeXSS(this.getTranslatedString(this._INPUT_HELPER_KEY)) + "</div>");
//@HTMLUpdateOK
this._AppendInputHelperParent().append(this._inputHelper);
}
},
/**
* Helper function to escape Cross site script text
*
* @param {string} escapeMe
* @return {jQuery|string}
* @ignore
*/
_EscapeXSS : function __EscapeXSS(escapeMe)
{
return $("<span>" + escapeMe + "</span>").text();
},
/**
* Which parent node the inputHelper should be appended to. Usually do not need to override.
*
* @protected
* @instance
* @memberOf !oj.inputBase
*/
_AppendInputHelperParent : function __AppendInputHelperParent()
{
return this.widget();
},
/**
* Sets up the default regExp validator.
*
* @ignore
* @protected
* @override
* @instance
* @memberof! oj.inputBase
*/
_GetImplicitValidators : function ()
{
var ret = this._superApply(arguments);
// register a default RegExp validator if we have a valid pattern
if (this.options['pattern'])
{
// add validator to the special internalValidators list. These are validators created by
// the framework. We don't want these cleared using the option - 'validators'
this._defaultRegExpValidator[oj.ValidatorFactory.VALIDATOR_TYPE_REGEXP] = this._getImplicitRegExpValidator();
}
return $.extend(this._defaultRegExpValidator, ret);
},
/**
* Toggles css selector on the widget. E.g., when readOnly option changes,
* the oj-read-only selector needs to be toggled.
* @param {string} option
* @param {Object|string} value
* @private
*/
_refreshStateTheming : function (option, value)
{
if (Object.keys(this._OPTION_TO_CSS_MAPPING).indexOf(option) != -1)
{
// value is a boolean
this.widget().toggleClass(this._OPTION_TO_CSS_MAPPING[option], !!value);
}
},
/**
* Returns the regexp validator instance or creates it if needed and caches it.
* @private
*/
_getImplicitRegExpValidator : function ()
{
if (!this.options['pattern'])
{
return null;
}
var regexpOptions = {'pattern': this.options['pattern'],
'label': this._getLabelText()};
$.extend(regexpOptions, this.options['translations']['regexp'] || {});
return oj.Validation.validatorFactory(oj.ValidatorFactory.VALIDATOR_TYPE_REGEXP).createValidator(regexpOptions);
},
/**
* This helper function will generate ids using widget's uuid as unique identifier for
* wai-aria and other necessary ids
*
* @ignore
* @protected
* @param {string} sub
* @return {string}
*/
_GetSubId : function __getSubId(sub)
{
return this["uuid"] + "_" + sub;
},
/**
* @ignore
* @protected
* @return {boolean}
*/
_IsRTL : function ()
{
return this._rtl;
},
/**
* <p>Refreshes the component.
*
* @expose
* @memberof oj.baseComponent
* @instance
*/
refresh: function()
{
var retVal = this._superApply(arguments);
this._rtl = this._GetReadingDirection() === "rtl";
return retVal;
},
/**
* Return the subcomponent node represented by the documented locator attribute values.
* @expose
* @override
* @instance
* @memberof oj.inputBase
* @param {Object} locator An Object containing at minimum a subId property whose value is a string, documented by the component, that allows the component to
* look up the subcomponent associated with that string. It contains:<p>
* component: optional - in the future there may be more than one component contained within a page element<p>
* subId: the string, documented by the component, that the component expects in getNodeBySubId to locate a particular subcomponent
* @returns {Object|null} the subcomponent located by the subId string passed in locator, if found.<p>
*/
getNodeBySubId: function(locator)
{
return this._super(locator);
},
/**
* Returns a <code class="prettyprint">jQuery</code> object containing the element visually
* representing the component, excluding the label associated with the it.
*
* <p>This method does not accept any arguments.</p>
*
* @expose
* @memberof! oj.inputBase
* @instance
* @return {jQuery} the root of the component
*
* @example <caption>Invoke the <code class="prettyprint">widget</code> method:</caption>
* var widget = $( ".selector" ).ojFoo( "widget" ); // Foo is InputText, InputPassword, TextArea
*/
widget : function _widget()
{
return this._DoWrapElement() ? this._wrapper : this.element;
}
}, true);
Source: src/main/javascript/oracle/oj/ojinputtext/InputBase.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01