/**
* Copyright (c) 2014, Oracle and/or its affiliates.
* All rights reserved.
*/
// S T A T I C V A R S
/**
* The various contexts under which validation can be run by component.
* @ignore
*/
var _sValidationContext = {
COMPONENT_CREATE : 1,
CONVERTER_OPTION_CHANGE : 2,
DISABLED_OPTION_CHANGE : 3,
READONLY_OPTION_CHANGE: 4,
REFRESH_METHOD: 5,
REQUIRED_OPTION_CHANGE : 6,
RESET_METHOD : 7,
USER_ACTION: 8,
VALIDATE_METHOD: 9,
VALIDATORS_OPTION_CHANGE : 10,
VALUE_OPTION_CHANGE: 11
};
/**
* Default validation options used by validate method.
* @ignore
*/
var _sValidateMethodOptions = {doValueChangeCheck: false,
validationContext: _sValidationContext.VALIDATE_METHOD};
/**
* Default validation options used when converter option changes
* @ignore
*/
var _sConverterOptionOptions = {doNotClearMessages: true,
validationContext: _sValidationContext.CONVERTER_OPTION_CHANGE};
/**
* Default validation options used when disabled option changes
* @ignore
*/
var _sDisabledOptionOptions = {doNotClearMessages: true,
validationContext: _sValidationContext.DISABLED_OPTION_CHANGE};
/**
* Default validation options used when required option changes
* @ignore
*/
var _sRequiredOptionOptions = {doNotClearMessages: true,
validationContext: _sValidationContext.REQUIRED_OPTION_CHANGE};
/**
* Default validation options used when readOnly option changes
* @ignore
*/
var _sReadOnlyOptionOptions = {doNotClearMessages: true,
validationContext: _sValidationContext.READONLY_OPTION_CHANGE};
/**
* Default validation options used when refresh method is called.
* @ignore
*/
var _sRefreshMethodOptions = {doNotClearMessages: true,
validationContext: _sValidationContext.REFRESH_METHOD};
/**
* Default validation options used when validators option changes
* @ignore
*/
var _sValidatorsOptionOptions = {doNotClearMessages: true,
validationContext: _sValidationContext.VALIDATORS_OPTION_CHANGE};
/**
* The various validation modes
* @ignore
*/
var _sValidationMode = {
FULL : 1,
VALIDATORS_ONLY : 2,
REQUIRED_VALIDATOR_ONLY : 3
};
// E D I T A B L E V A L U E A B S T R A C T W I D G E T
/**
* @ojcomponent oj.editableValue
* @augments oj.baseComponent
* @abstract
* @since 0.6
*
* @classdesc
* Abstract base class for all editable components that are value holders and that require
* validation and messaging capabilities. <br/>
*
* <p>
* <h3 id="validation-section">
* Validation and Messaging
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#validation-section"></a>
* </h3>
* An editable component runs validation (normal or deferred) based on the action performed on it
* (either by end-user or page author), and the state it was in when the action occurred. Examples
* of actions are - creating a component, user changing the value of the component by interacting
* with it, the app setting a value programmatically, the app calling the validate() method etc. At
* the time the action occurs, the component could already be showing errors, or can have a deferred
* error or have no errors.
* <p>
* These factors also determine whether validation errors/messages get shown to the user immediately
* or get deferred. The following sections highlight the kinds of validation that are run and how
* messages get handled.
* </p>
*
* <h4 id="normal-validation-section">Normal Validation
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#normal-validation-section"></a></h4>
* Normal validation is run in the following cases on the display value, using the converter and
* validators set on the component, and validation errors are reported to user immediately.
* <ul>
* <li>When value changes as a result of user interaction all messages are cleared, including custom
* messages added by the app, and full validation is run on the UI value. The steps performed are
* outlined below.
* <ol>
* <li>All messages options are cleared -
* <code class="prettyprint">messagesShown</code>, <code class="prettyprint">messagesHidden</code>
* and <code class="prettyprint">messagesCustom</code> options. </li>
* <li>If no converter is present then processing continues to next step. If a converter is
* present, the UI value is first converted (i.e., parsed). If there is a parse error then
* the <code class="prettyprint">messagesShown</code> option is updated and processing returns.</li>
* <li>If there are no validators setup for the component then the value is set on the component.
* Otherwise all validators are run in sequence using the parsed value from the previous step. The
* implicit required is run first if the component is marked required. When a validation error is
* encountered it is remembered and the next validator in the sequence is run.
* <ul><li>NOTE: The value is trimmed before required validation is run</li></ul>
* </li>
* <li>At the end of the validation run if there are errors, the <code class="prettyprint">messagesShown</code>
* option is updated and processing returns. If there are no errors, then the
* <code class="prettyprint">value</code> option is updated and the formatted value displayed on the
* UI.</li>
* </ol>
* </li>
* <li>When the <code class="prettyprint">validate</code> method is called by app, all messages are
* cleared and full validation run using the display value. See <a href="#validate">validate</a>
* method for details.</li>
* <li>When certain options change through programmatic intervention by app, the component
* determines whether it needs to run normal validation based on the state the component is in.
* Refer to the <a href="#mixed-validation-section">Mixed Validation</a> section below for details. </li>
* </ul>
*
* <h4 id="deferred-validation-section">Deferred Validation
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#deferred-validation-section"></a>
* </h4>
* Deferred validation is run in the following cases on the component value using the implicit
* required validator, and validation errors are deferred, i.e., not shown to user immediately.
* Refer to the <a href="#deferred-messages-section">Showing Deferred Messages</a> section to
* understand how deferred messages can be shown.
* <ul>
* <li>When a component is created deferred validation is run and no messages options are cleared
* prior to running validation. Refer to the <a href="#deferred-validators-section">Validators
* Participating in Deferred Validation</a> section for details.</li>
* <li>When the <code class="prettyprint">value</code> option changes due to programmatic
* intervention deferred validation is run, after all messages options -
* <code class="prettyprint">messagesShown</code>, <code class="prettyprint">messagesHidden</code>
* and <code class="prettyprint">messagesCustom</code> - are cleared.</li>
* <li>When the <code class="prettyprint">reset</code> method is called, deferred validation is run
* after all messages options - <code class="prettyprint">messagesShown</code>,
* <code class="prettyprint">messagesHidden</code> and <code class="prettyprint">messagesCustom</code>
* - are cleared.</li>
* <li>When certain options change through programmatic intervention by app, the component
* determines whether it needs to run deferred validation based on the state the component is in.
* Refer to the <a href="#mixed-validation-section">Mixed Validation</a> section below for details.</li>
* </ul>
*
* <h4 id="mixed-validation-section">Mixed Validation
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#mixed-validation-section"></a>
* </h4>
* Either deferred or normal validation is run in the following cases based on the state the
* component is in and any validation errors encountered are either hidden or shown to user.
* <ul>
* <li>when converter option changes. See <a href="#converter">converter</a> option for details.</li>
* <li>when disabled option changes. See <a href="#disabled">disabled</a> option for details.</li>
* <li>when required option changes. See <a href="#required">required</a> option for details.</li>
* <li>when validators option changes. See <a href="#validators">validators</a> option for details.</li>
* <li>when refresh method is called. See <a href="#refresh">refresh</a> method for details.</li>
* </ul>
* </p>
*
* <p>
* <h3 id="deferred-messages-section">
* Showing Deferred Messages
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#deferred-messages-section"></a>
* </h3>
* Deferred validation messages are displayed only when page author requests for it explicitly in
* one of the following ways:
* <ul>
* <li>calls the <a href="#showMessages"><code class="prettyprint">showMessages</code></a> method on the component or</li>
* <li>calls the helper methods <code class="prettyprint">showMessages</code> using the
* {@link oj.InvalidComponentTracker}</li>
* </ul>
* </p>
*
* <p>
* <h3 id="deferred-validators-section">
* Validators Participating in Deferred Validation
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#deferred-validators-section"></a>
* </h3>
* The required validator is the only validator type that participates in deferred validation.
* </p>
*
* <p>
* <h3 id="declarative-binding-section">
* Declarative Binding
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#declarative-binding-section"></a>
* </h3>
* When the component's <code class="prettyprint">value</code> option is bound to a Knockout
* observable and when the value changes, whether the observable is updated or not, iow whether a
* 'writeback' to the observable happens or not, depends on the action that caused the value to
* change.
* <ul>
* <li>when the value changes as a result of user interaction </li>
* <li>when the value changes because normal validation was run as a result of these options
* being changed by the app - <code class="prettyprint">converter</code>, <code class="prettyprint">disabled</code>,
* <code class="prettyprint">required</code>, <code class="prettyprint">validators</code>, then the
* value is written to the observable. See the specific option docs for details.</li>
* <li>when the value changes because normal validation was run as a result of these methods being
* called by the app -
* <code class="prettyprint">refresh</code>, <code class="prettyprint">validate</code>,
* then the value is written to the observable. See the specific method docs for details.</li>
* <li>when the value changes due to programmatic intervention by app then the value is not written
* back to observable. This is based on the assumption that the app has mutated the observable
* already. In this case updating the component's <code class="prettyprint">value</code> option
* alone will not propagate the change automatically to the observable. Updating the observable is
* recommended as this will propagate the change automatically to the component.
* </li>
* </ul>
* </p>
*
* @example <caption>Initialize component</caption>
* <input id="foo" type="text"/>
* <script>
* $('#foo").ojInputText({'value': 'abc'});
* </script>
* // using knockout ojComponent binding
* <input id="foo" data-bind="ojComponent: {component: 'ojInputText', value: 'abc'}"/>
* @example <caption>Initialize component value using ko observable</caption>
* <input id="foo" data-bind="ojComponent: {component: 'ojInputText', value: salary}"/>
* <script>
* var salary = ko.observable('abc');
* </script>
* @example <caption>Initialize component value using element value</caption>
* <input id="foo" data-bind="ojComponent: {component: 'ojInputText'}" value='abc'/>
*/
oj.__registerWidget('oj.editableValue', $['oj']['baseComponent'],
{
widgetEventPrefix: "oj",
options:
{
/**
* Whether the component is disabled. The element's disabled property is used as
* its initial value if it exists, when the option is not explicitly set. When neither is set,
* disabled defaults to false.
*
* <p>The 2-way <code class="prettyprint">disabled</code> binding offered by
* the <code class="prettyprint">ojComponent</code> binding
* should be used instead of Knockout's built-in <code class="prettyprint">disable</code>
* and <code class="prettyprint">enable</code> bindings,
* as the former sets the API, while the latter sets the underlying DOM attribute.
* </p>
*
* <p>
* When the <code class="prettyprint">disabled</code> option changes due to programmatic
* intervention, the component may clear messages and run validation in some cases. </br>
* <ul>
* <li>when a required component is initialized as disabled
* <code class="prettyprint">{value: null, required:true, disabled: true}</code>,
* deferred validation is skipped.</li>
* <li>when a disabled component is enabled,
* <ul>
* <li>if component is invalid and showing messages then all component messages are cleared,
* and full validation run using the display value.
* <ul>
* <li>if there are validation errors, they are pushed to <code class="prettyprint">messagesShown</code>
* option. </li>
* <li>if no errors result from the validation, the <code class="prettyprint">value</code>
* option is updated. Page authors can listen to the <code class="prettyprint">optionChange</code>
* event on the <code class="prettyprint">value</code> option to clear custom errors.</li>
* </ul>
* </li>
*
* <li>if component is valid and has no errors then deferred validation is run.
* <ul>
* <li>if there is a deferred validation error, then
* <code class="prettyprint">messagesHidden</code> option is updated. </li>
* </ul>
* </li>
* <li>if component is invalid and deferred errors then component messages are cleared and
* deferred validation re-run.
* <ul>
* <li>if there is a deferred validation error, then
* <code class="prettyprint">messagesHidden</code> option is updated. </li>
* </ul>
* </li>
* </ul>
* </li>
* <li>when enabled component is disabled then no validation is run and the component appears
* disabled.</li>
* </ul>
* </p>
*
* @example <caption>Initialize component with <code class="prettyprint">disabled</code> option:</caption>
* $(".selector").ojFoo({"disabled": true}); // Foo is InputText, InputNumber, Select, etc.
*
* @expose
* @type {boolean}
* @default <code class="prettyprint">false</code>
* @public
* @instance
* @memberof oj.editableValue
*/
disabled : false,
/**
* Display options for auxilliary content that determines where it should be displayed
* in relation to the component.
*
* <p>
* The types of messaging content for which display options can be configured include
* <code class="prettyprint">messages</code>, <code class="prettyprint">converterHint</code>,
* <code class="prettyprint">validatorHint</code> and <code class="prettyprint">title</code>.<br/>
* The display options for each type is specified either as an array of strings or a string. When
* an array is specified the first display option takes precedence over the second and so on.
* </p>
* <p>
* JET editable components set defaults that apply to the entire app/page.
* It is possible to override the defaults on
* a per instance basis as explained in the examples below or change defaults for the entire
* application using
* <a href="oj.Components.html#setDefaultOptions"><code class="prettyprint">oj.Components#setDefaultOptions</code></a> method.
* It is much easier to change the defaults using setDefaultOptions once rather than putting
* the displayOptions option on every component instance.<br/>
* </p>
* <p>
* When displayOptions changes due to programmatic intervention, the component updates its
* display to reflect the updated choices. For example, if 'title' property goes from
* 'notewindow' to 'none' then it no longer shows in the notewindow.
* </p>
*
* @property {Array|string=} converterHint - supported values are <code class="prettyprint">'placeholder'</code>,
* <code class="prettyprint">'notewindow'</code>, <code class="prettyprint">'none'</code>. The
* default value is <code class="prettyprint">['placeholder', 'notewindow']</code>. When there
* is already a placeholder set on the component, the converter hint falls back to display
* type of 'notewindow'. To change the default value you can do this - <br/>
* E.g. <code class="prettyprint">{'displayOptions: {'converterHint': ['none']}}</code>
* @property {Array|string=} validatorHint - supported values are <code class="prettyprint">'notewindow'</code>,
* <code class="prettyprint">'none'</code>. To change the default value you can do this - <br/>
* <code class="prettyprint">{'displayOptions: {'validatorHint': ['none']}}</code>
* @property {Array|string=} messages - supported values are <code class="prettyprint">'notewindow'</code>,
* <code class="prettyprint">'inline'</code>,
* <code class="prettyprint">'none'</code>. The default is 'inline'.
* To change the default value you can do this - <br/>
* E.g. <code class="prettyprint">{'displayOptions: {'messages': 'none'}}</code>
* @property {Array|string=} title - supported values are <code class="prettyprint">'notewindow'</code>,
* <code class="prettyprint">'none'</code>. To change the default value you can do this - <br/>
* E.g. <code class="prettyprint">{'displayOptions: {'title': 'none'}}</code>
*
* @example <caption>Override default values for <code class="prettyprint">displayOptions</code>
* for messages for the entire application:</caption>
* // messages will be shown in the notewindow for the application.
* oj.Components.setDefaultOptions({
* 'editableValue':
* {
* 'displayOptions':
* {
* 'messages': ['notewindow']
* }
* }
* });
*
* @example <caption>Override default values for <code class="prettyprint">displayOptions</code>
* for one component instance:</caption>
* // In this example, the instance of ojFoo changes its displayOptions from the defaults.
* // The 'converterHint' is none, the 'validatorHint' is none and the 'title' is none,
* // so only the 'messages' will display in its default state.
* // For most apps, you will want to change the displayOptions app-wide
* // for all EditableValue components, so you should use the
* // oj.Components#setDefaultOptions function instead (see previous example).
* //
* // Foo is InputText, InputNumber, Select, etc.
* $(".selector").ojFoo("option", "displayOptions", {
* 'converterHint': 'none',
* 'validatorHint': 'none',
* 'title' : 'none'
* });
*
* @expose
* @access public
* @instance
* @default <code class="prettyprint">
* {<br/>
* 'messages': ['inline'], <br/>
* 'converterHint': ['placeholder', 'notewindow'], <br/>
* 'validatorHint': ['notewindow'], <br/>
* 'title': ['notewindow']<br/>
* }</code>
* @memberof oj.editableValue
* @type {Object|undefined}
* @since 0.7
*/
displayOptions : undefined,
/**
* Help information that goes on the label. When help is set on the input component, then
* help information is added to the input's label.
* <p>
* The properties supported on the <code class="prettyprint">help</code> option are:
*
* @property {string=} definition this is the help definition text. It is what shows up
* when the user hovers over the label or the help icon, or tabs into the help icon, or press
* and holds the help icon on a mobile device. No formatted text is available for
* help definition because the help definition renders on <code class="prettyprint">title</code>
* attribute.
* The default value is <code class="prettyprint">null</code>.
* @property {string=} source this is the help source url. If present, the help icon's
* anchor's target is this source. For security reasons we only support
* urls with protocol http: or https:.
* If the url doesn't comply we ignore it and throw an error. The default value is null.
*
* @expose
* @memberof oj.editableValue
* @instance
* @type {Object.<string, string>}
* @default <code class="prettyprint">{help : {definition :null, source: null}}</code>
*
* @example <caption>Initialize the input with the help definition and external url information:</caption>
* // Foo is InputText, InputNumber, Select, etc.
* $( ".selector" ).ojFoo({ "help": {"definition":"some help definition, "source":"some external url" } });
*
*
* @example <caption>Set the <code class="prettyprint">help</code> option, after initialization:</caption>
*
* // setter
* // Foo is InputText, InputNumber, Select, etc.
* $( ".selector" ).ojFoo( "option", "help", {"definition":"fill out the name", "source":"http:\\www.oracle.com" } );
*
*/
help:
{
/**
* <p>help definition text. See the top-level <code class="prettyprint">help</code> option for details.
*
* @expose
* @alias help.definition
* @memberof! oj.editableValue
* @instance
* @type {?string}
* @default <code class="prettyprint">null</code>
*
* @example <caption>Get or set the <code class="prettyprint">help.definition</code> sub-option, after initialization:</caption>
* // getter
* var definitionText = $( ".selector" ).ojFoo( "option", "help.definition" );
*
* // setter:
* $( ".selector" ).ojFoo( "option", "help.definition", "Enter your name" );
*/
definition: null,
/**
* <p>help source url. See the top-level <code class="prettyprint">help</code> option for details.
*
* @expose
* @alias help.source
* @memberof! oj.editableValue
* @instance
* @type {?string}
* @default <code class="prettyprint">null</code>
*
* @example <caption>Get or set the <code class="prettyprint">help.source</code> sub-option, after initialization:</caption>
* // getter
* var helpSource = $( ".selector" ).ojFoo( "option", "help.source" );
*
* // setter:
* $( ".selector" ).ojFoo( "option", "help.source", "www.abc.com" );
*/
source: null
},
/**
* List of messages an app would add to the component when it has business/custom validation
* errors that it wants the component to show. When this option is set the
* <code class="prettyprint">messagesShown</code> option is also updated.<br/>
* Each message in the array is either an instance of oj.Message or an object that duck types it.
* See {@link oj.Message} for details.
*
* <p>
* An optionChange event is triggered every time this option value changes.
* </p>
*
* @example <caption>Get the current list of app messages using <code class="prettyprint">messagesCustom</code> option:</caption>
* // Foo is InputText, InputNumber, Select, etc.
* var customMsgs = $(".selector").ojFoo("option", "messagesCustom");
*
* @example <caption>Clear all app messages set on the component:</caption>
* // Foo is InputText, InputNumber, Select, etc.
* $(".selector").ojFoo("option", "messagesCustom", []);
*
* @example <caption>Set app messages using the <code class="prettyprint">messagesCustom</code> option:</caption>
* var msgs = [];
* msgs.push({'summary': 'Error Summary', 'detail': 'Error Detail'});
* // Foo is InputText, InputNumber, Select, etc.
* $(".selector").ojFoo("option", "messagesCustom", msgs);
*
* @expose
* @access public
* @instance
* @memberof oj.editableValue
* @default empty array when no option is set.
* @type {Array|undefined}
* @since 0.7
* @see #messagesShown
*/
messagesCustom : undefined,
/**
* List of messages currently hidden on component, these are added by component when it runs
* deferred validation. Each message in the array is
* either an instance of oj.Message or an object that duck types it. See {@link oj.Message} for
* details. <br/>
*
* <p>
* This is a read-only option so page authors cannot set or change it directly.
* </p>
*
* <p>
* An optionChange event is triggered every time this option value changes.
* </p>
*
* <p>
* These messages are not shown to the end-user by default, but page author
* can show hidden messages using the {@link showMessages} method.
* </p>
*
* @example <caption>Get <code class="prettyprint">messagesShown</code> for the component:</caption>
* // Foo is InputText, InputNumber, Select, etc.
* var messages = $(".selector").ojFoo("option", "messagesShown");
*
* @expose
* @access public
* @instance
* @memberof oj.editableValue
* @default empty array when no option is set.
* @type {Array|undefined}
* @since 0.7
* @see #showMessages
* @readonly
*/
messagesHidden : undefined,
/**
* List of messages currently shown on component, these include messages generated both by the
* component and ones provided by app using <code class="prettyprint">messagesCustom</code>.
* Each message in the array is either an instance of oj.Message or an object that duck types
* it. See {@link oj.Message} for details. <br/>
*
* <p>
* This is a read-only option so page authors cannot set or change it directly.
* </p>
*
* <p>
* An optionChange event is triggered every time its value changes.
* </p>
*
* <p>
* Messages retrieved using the <code class="prettyprint">messagesShown</code> option are by
* default shown in the notewindow, but this can be controlled using the 'messages' property of
* the <code class="prettyprint">displayOptions</code> option.
* </p>
*
* @example <caption>Get <code class="prettyprint">messagesShown</code> for the component:</caption>
* // Foo is InputText, InputNumber, Select, etc.
* var messages = $(".selector").ojFoo("option", "messagesShown");
*
* @expose
* @access public
* @instance
* @memberof oj.editableValue
* @default empty array when no option is set.
* @type {Array|undefined}
* @since 0.7
* @readonly
*/
messagesShown : undefined,
/**
* Whether the component is required or optional. When required is set to true, an implicit
* required validator is created using the validator factory -
* <code class="prettyprint">oj.Validation.validatorFactory(oj.ValidatorFactory.VALIDATOR_TYPE_REQUIRED).createValidator()</code>.
*
* Translations specified using the <code class="prettyprint">translations.required</code> option
* and the label associated with the component, are passed through to the options parameter of the
* createValidator method.
*
* <p>
* When <code class="prettyprint">required</code> option changes due to programmatic intervention,
* the component may clears messages and run validation, based on the current state it's in. </br>
*
* <h4>Running Validation</h4>
* <ul>
* <li>if component is valid when required is set to true, then it runs deferred validation on
* the option value. This is to ensure errors are not flagged unnecessarily.
* <ul>
* <li>if there is a deferred validation error, then
* <code class="prettyprint">messagesHidden</code> option is updated. </li>
* </ul>
* </li>
* <li>if component is invalid and has deferred messages when required is set to false, then
* component messages are cleared but no deferred validation is run.
* </li>
* <li>if component is invalid and currently showing invalid messages when required is set, then
* component messages are cleared and normal validation is run using the current display value.
* <ul>
* <li>if there are validation errors, then <code class="prettyprint">value</code>
* option is not updated and the error pushed to <code class="prettyprint">messagesShown</code>
* option.
* </li>
* <li>if no errors result from the validation, the <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.</li>
* </ul>
* </li>
* </ul>
*
* <h4>Clearing Messages</h4>
* <ul>
* <li>Only messages created by the component are cleared. These include ones in
* <code class="prettyprint">messagesHidden</code> and <code class="prettyprint">messagesShown</code>
* options.</li>
* <li><code class="prettyprint">messagesCustom</code> option is not cleared.</li>
* </ul>
*
* </p>
*
* @ojvalue {boolean} false - implies a value is not required to be provided by the user.
* This is the default.
* @ojvalue {boolean} true - implies a value is required to be provided by user and the
* input's label will render a required icon. Additionally a required validator -
* {@link oj.RequiredValidator} - is implicitly used if no explicit required validator is set.
* An explicit required validator can be set by page authors using the validators option.
*
* @example <caption>Initialize the component with the <code class="prettyprint">required</code> option:</caption>
* $(".selector").ojFoo({required: true}); // Foo is InputText, InputNumber, Select, etc.<br/>
* @example <caption>Initialize <code class="prettyprint">required</code> option from html attribute 'required':</caption>
* <input type="text" value= "foobar" required/><br/>
* // retreiving the required option returns true
* $(".selector").ojFoo("option", "required"); // Foo is InputText, InputNumber, Select, etc.<br/>
*
* @example <caption>Customize messages and hints used by implicit required validator when
* <code class="prettyprint">required</code> option is set:</caption>
* <!-- Foo is InputText, InputNumber, Select, etc. -->
* <input type="text" value="foobar" required data-bind="ojComponent: {
* component: 'ojFoo',
* value: password,
* translations: {'required': {
* hint: 'custom: enter at least 3 alphabets',
* messageSummary: 'custom: \'{label}\' is Required',
* messageDetail: 'custom: please enter a valid value for \'{label}\''}}}"/>
* @expose
* @access public
* @instance
* @default when the option is not set, the element's required property is used as its initial
* value if it exists.
* @memberof oj.editableValue
* @type {boolean|undefined}
* @since 0.7
* @see #translations
*/
required: undefined,
/**
* Represents advisory information for the component, such as would be appropriate for a tooltip.
*
* <p>
* When a title is present it is by default displayed in the notewindow, or as determined by the
* 'title' property set on the <code class="prettyprint">displayOptions</code> option.
* When the <code class="prettyprint">title</code> option changes the component refreshes to
* display the new title.
* </p>
*
* <p>
* To include formatted text in the title, format the string using html tags. For example the
* title might look like:
* <pre class="prettyprint"><code><html>Enter <b>at least</b> 6 characters</html></code></pre>
*
* @example <caption>Initialize the component with the <code class="prettyprint">title</code> option:</caption>
* <!-- Foo is InputText, InputNumber, Select, etc. -->
* <input id="username" type="text" data-bind="
* ojComponent: {component: 'ojFoo', title : 'enter at least 3 alphanumeric characters',
* pattern: '[a-zA-Z0-9]{3,}', value: ''}"/><br/>
*
* @example <caption>Initialize <code class="prettyprint">title</code> option from html attribute 'title':</caption>
* <!-- Foo is InputText, InputNumber, Select, etc. -->
* <input id="username" type="text" value= "foobar" title="enter at least 3 alphanumeric characters"
* pattern="[a-zA-Z0-9]{3,}"/><br/>
* $("#username").ojFoo({}); // Foo is InputText, InputNumber, Select, etc.
*
* // reading the title option will return "enter at least 3 alphanumeric characters"
* $("#username").ojFoo("option", "title"); // Foo is InputText, InputNumber, Select, etc. <br/>
*
* @expose
* @access public
* @instance
* @default when the option is not set, the element's title attribute is used as its initial
* value if it exists.
* @memberof oj.editableValue
* @type {string|undefined}
*/
title: undefined,
/**
* List of validators used by component when performing validation. Each item is either an
* instance that duck types {@link oj.Validator}, or is an Object literal containing the
* properties listed below. Implicit validators created by a component when certain options
* are present (e.g. <code class="prettyprint">required</code> option), are separate from
* validators specified through this option. At runtime when the component runs validation, it
* combines the implicit validators with the list specified through this option.
* <p>
* Hints exposed by validators are shown in the notewindow by default, or as determined by the
* 'validatorHint' property set on the <code class="prettyprint">displayOptions</code>
* option.
* </p>
*
* <p>
* When <code class="prettyprint">validators</code> option changes due to programmatic
* intervention, the component may decide to clear messages and run validation, based on the
* current state it is in. </br>
*
* <h4>Steps Performed Always</h4>
* <ul>
* <li>The cached list of validator instances are cleared and new validator hints 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 validators changes, component does nothing other than the
* steps it always performs.</li>
* <li>if component is invalid and is showing messages -
* <code class="prettyprint">messagesShown</code> option is non-empty, when
* <code class="prettyprint">validators</code> changes then all component messages are cleared
* and full validation run using the display value on the component.
* <ul>
* <li>if there are validation errors, then <code class="prettyprint">value</code>
* option is not updated and the error pushed to <code class="prettyprint">messagesShown</code>
* option.
* </li>
* <li>if no errors result from the validation, the <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.</li>
* </ul>
* </li>
* <li>if component is invalid and has deferred messages when validators changes, it does
* nothing other than the steps it performs always.</li>
* </ul>
* </p>
*
* <h4>Clearing Messages</h4>
* <ul>
* <li>Only messages created by the component are cleared. These include ones in
* <code class="prettyprint">messagesHidden</code> and <code class="prettyprint">messagesShown</code>
* options.</li>
* <li><code class="prettyprint">messagesCustom</code> option is not cleared.</li>
* </ul>
* </p>
*
* @property {string} type - the validator type that has a {@link oj.ValidatorFactory} that can
* be retrieved using the {@link oj.Validation} module. For a list of supported validators refer
* to {@link oj.ValidatorFactory}. <br/>
* E.g., <code class="prettyprint">{validators: [{type: 'regExp'}]}</code>
* @property {Object=} options - optional Object literal of options that the validator expects.
* <br/>
* E.g., <code class="prettyprint">{validators: [{type: 'regExp', options: {pattern: '[a-zA-Z0-9]{3,}'}}]}</code>
*
* @example <caption>Initialize the component with validator object literal:</caption>
* // Foo is InputText, InputNumber, Select, etc.
* $(".selector").ojFoo({
* validators: [{
* type: 'numberRange',
* options : {
* hint: {min: 'Enter a value greater than {min}'},
* min: 100
* }
* }],
* });
*
* NOTE: oj.Validation.validatorFactory('numberRange') returns the validator factory that is used
* to instantiate a range validator for numbers.
*
* @example <caption>Initialize the component with multiple validator instances:</caption>
* var validator1 = new MyCustomValidator({'foo': 'A'});
* var validator2 = new MyCustomValidator({'foo': 'B'});
* // Foo is InputText, InputNumber, Select, etc.
* $(".selector").ojFoo({
* value: 10,
* validators: [validator1, validator2]
* });
*
* @expose
* @access public
* @instance
* @memberof oj.editableValue
* @type {Array|undefined}
*/
validators: undefined,
/**
* The value of the component.
*
* <p>
* When <code class="prettyprint">value</code> option changes due to programmatic
* intervention, the component always clears all messages -
* <code class="prettyprint">messagesHidden</code>, <code class="prettyprint">messagesShown</code>
* and <code class="prettyprint">messagesCustom</code>, runs deferred validation, and
* always refreshes UI display value.</br>
*
* <h4>Running Validation</h4>
* <ul>
* <li>component always runs deferred validation; if there is a validation error the
* <code class="prettyprint">messagesHidden</code> option is updated.</li>
* </ul>
* </p>
*
* @example <caption>Initialize the component with the <code class="prettyprint">value</code> option specified:</caption>
* $(".selector").ojFoo({'value': '10'}); // Foo is InputText, InputNumber, Select, etc.<br/>
* @example <caption>Get or set <code class="prettyprint">value</code> option, after initialization:</caption>
* // Getter: returns '10'
* $(".selector").ojFoo("option", "value");// Foo is InputText, InputNumber, Select, etc.
* // Setter: sets '20'
* $(".selector").ojFoo("option", "value", '20'); // Foo is InputText, InputNumber, Select, etc.
*
* @expose
* @access public
* @instance
* @default When the option is not set, the element's dom value is used as its initial value
* if it exists. The type of value is as defined by the component that extends this class. Refer
* to specific components for defaults.
* @memberof oj.editableValue
* @type {Object|undefined}
*/
value: undefined
},
// P U B L I C M E T H O D S
/**
* Return the subcomponent node represented by the documented locator attribute values. <br/>
* If the locator is null or no subId string is provided then this method returns the element that
* this component was initalized with. <br/>
* If a subId was provided but a subcomponent node cannot be located this method returns null.
*
* <p>If the <code class="prettyprint">locator</code> or its <code class="prettyprint">subId</code> is
* <code class="prettyprint">null</code>, then this method returns the element on which this component was initalized.
*
* <p>If a <code class="prettyprint">subId</code> was provided but no corresponding node
* can be located, then this method returns <code class="prettyprint">null</code>.
*
* @expose
* @override
* @memberof oj.editableValue
* @instance
*
* @param {Object} locator An Object containing, at minimum, a <code class="prettyprint">subId</code>
* property. See the table for details on its fields.
*
* @property {string=} locator.subId - A string that identifies a particular DOM node in this component.
*
* <p>The supported sub-ID's are documented in the <a href="#subids-section">Sub-ID's</a> section of this document.
*
* @property {number=} locator.index - A zero-based index, used to locate a message content node
* or a hint node within the popup.
* @returns {Element|null} The DOM node located by the <code class="prettyprint">subId</code> string passed in
* <code class="prettyprint">locator</code>, or <code class="prettyprint">null</code> if none is found.
*
* @example <caption>Get the node for a certain subId:</caption>
* // Foo is ojInputNumber, ojInputDate, etc.
* var node = $( ".selector" ).ojFoo( "getNodeBySubId", {'subId': 'oj-some-sub-id'} );
*/
getNodeBySubId: function(locator)
{
return this._super(locator);
},
/**
* whether the component is currently valid.
* @example <caption>Check whether the component is valid:</caption>
* var value = $(".selector").ojInputText("isValid");
* @returns {boolean}
* @access public
* @instance
* @expose
* @memberof oj.editableValue
*/
isValid : function ()
{
if (this._valid === undefined)
{
this._valid = this._getValid();
}
return this._valid;
},
/**
* Called when the DOM underneath the component chages requiring a re-render of the component. An
* example is when the label for the input changes. <br/>
* <p>
* Another time when refresh might be called is when the locale for the page changes. When it
* changes, options used by its converter and validator that are locale specific, its hints,
* messages and translations will be updated.
* </p>
*
* <p>
* When <code class="prettyprint">refresh</code> method is called, the component may take various
* steps such as clearing messages, running validation etc., based on the state it is in. </br>
*
* <h4>Steps Performed Always</h4>
* <ul>
* <li>The converter and validators used by the component are reset, and new converter and
* validator hints 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 refresh() is called, the display value is refreshed if component
* has a converter set.</li>
* <li>if component is invalid and is showing messages -
* <code class="prettyprint">messagesShown</code> option is non-empty, when
* <code class="prettyprint">refresh()</code> is called, then all component messages are cleared
* and full validation run using the display value on the component.
* <ul>
* <li>if there are validation errors, then <code class="prettyprint">value</code>
* option is not updated and the error pushed to <code class="prettyprint">messagesShown</code>
* option.
* </li>
* <li>if no errors result from the validation, the <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.</li>
* </ul>
* </li>
* <li>if component is invalid and has deferred messages when <code class="prettyprint">refresh()</code>
* is called, then all component messages are cleared and deferred validation is run.</li>
* </ul>
* </p>
*
* <h4>Clearing Messages</h4>
* <ul>
* <li>If clearing messages only those created by the component are cleared. These include ones in
* <code class="prettyprint">messagesHidden</code> and <code class="prettyprint">messagesShown</code>
* options.</li>
* <li><code class="prettyprint">messagesCustom</code> option is not cleared.</li>
* </ul>
* </p>
*
* @example <caption>Refresh component after changing the label DOM.</caption>
* // Foo is ojInputNumber, ojInputText, etc.
* $(selector).ojFoo("refresh");<br/>
*
* @access public
* @instance
* @expose
* @memberof oj.editableValue
* @since 0.7
*/
refresh : function ()
{
this._super();
this._doRefresh(true);
},
/**
* Resets the component by clearing all messages options - <code class="prettyprint">messagesCustom</code>,
* <code class="prettyprint">messagesHidden</code> and <code class="prettyprint">messagesShown</code>,
* and updates the component's display value using the option value. User entered values will be
* erased when this method is called.
*
* @example <caption>Reset component</caption>
* $(selector).ojInputText("reset"); <br/>
*
* @access public
* @instance
* @expose
* @memberof oj.editableValue
* @since 0.7
*/
reset : function ()
{
this._clearAllMessages();
// since we are pushing component value to UI, only deferred validation need to be run; this is
// same as setting value option.
this._runDeferredValidation(this._VALIDATION_CONTEXT.RESET_METHOD);
this._refreshComponentDisplayValue(this.options['value'], true);
},
/**
* Takes all hidden messages that are in the <code class="prettyprint">messagesHidden</code>
* option and moves them to <code class="prettyprint">messagesShown</code> option. If there were
* no messages in <code class="prettyprint">messagesHidden</code> then this method simply returns.
*
* <p>
* To view messages user has to set focus on the component.
* </p>
*
* <p>
* An <code class="prettyprint">optionChange</code> event is triggered on both
* <code class="prettyprint">messagesHidden</code> and <code class="prettyprint">messagesShown</code>
* options.
* </p>
*
* @example <caption>Display all messages including deferred ones.</caption>
* $(selector).ojInputText("showMessages");
* @access public
* @instance
* @expose
* @memberof oj.editableValue
* @since 0.7
*/
showMessages : function ()
{
var msgs = this.options['messagesHidden'], msg, mutated = false, i, clonedMsgs = [];
for (i = 0; i < msgs.length; i++)
{
mutated = true;
msg = msgs[i];
if (msg instanceof oj.ComponentMessage)
{
msg._forceDisplayToShown();
}
clonedMsgs.push(msg.clone()); // TODO: revisit clone msg??
}
if (mutated)
{
// clear hidden messages. push cloned hidden messages into messagesShown option
this._clearMessages('messagesHidden');
this._updateMessagesOption('messagesShown', clonedMsgs);
}
},
/**
* Validates the component's display value using the converter and all validators registered on
* the component and updates the <code class="prettyprint">value</code> option by performing the
* following steps.
*
* <p>
* <ol>
* <li>All messages are cleared, including custom messages added by the app. </li>
* <li>If no converter is present then processing continues to next step. If a converter is
* present, the UI value is first converted (i.e., parsed). If there is a parse error then
* the <code class="prettyprint">messagesShown</code> option is updated and method returns false.</li>
* <li>If there are no validators setup for the component the <code class="prettyprint">value</code>
* option is updated using the display value and the method returns true. Otherwise all
* validators are run in sequence using the parsed value from the previous step. The implicit
* required validator is run first if the component is marked required. When a validation error is
* encountered it is remembered and the next validator in the sequence is run. </li>
* <li>At the end of validation if there are errors, the <code class="prettyprint">messagesShown</code>
* option is updated and method returns false. If there were no errors, then the
* <code class="prettyprint">value</code> option is updated and method returns true.</li>
* </ol>
*
* @returns {boolean} true if component passed validation, false if there were validation errors.
*
* @example <caption>Validate component using its current value.</caption>
* // validate display value.
* $(.selector).ojInputText('validate');
*
* @deprecated param 'requiredOnly' has been deprecated since version 0.7.
*
* @access public
* @expose
* @instance
* @memberof oj.editableValue
* @since 0.7
*/
validate : function ()
{
// clear all messages; run full validation on display value
return this._SetValue(this._GetDisplayValue(), null, this._VALIDATE_METHOD_OPTIONS);
},
// P R O T E C T E D C O N S T A N T S A N D M E T H O D S
// *********** START WIDGET FACTORY METHODS (they retain _camelcase naming convention) **********
/**
* Validation mode specifying the kind of validation that gets run.
* <ul>
* <li>FULL - the default and runs both the converter and all validators. </li>
* <li>VALIDATORS_ONLY - runs all validators including the required validator is run.</li>
* <li>REQUIRED_VALIDATOR_ONLY - runs just the required validator. </li>
* </ul>
* @protected
* @const
* @type {Object}
* @memberof oj.editableValue
*/
_VALIDATION_MODE : _sValidationMode,
/**
* The context the component can be in when validation is run.
* <ul>
* <li>COMPONENT_CREATE - when component is created and we run validators. usually messages are
* not displayed right away, i.e, are 'deferred'. </li>
* <li>VALUE_OPTION_CHANGE - when component's value is updated programmatically. messages are
* deferred.</li>
* <li>REQUIRED_OPTION_CHANGE - when component's required option is updated programmatically. messages are
* deferred.</li>
* <li>USER_ACTION - when component runs validation as a result of user interating with component.
* messages are displayed immediately.</li>
* <li>VALIDATE_METHOD - when component's validate() method is called explicitly. messages are
* displayed immediately.</li>
* </ul>
*
* @protected
* @const
* @type {Object}
* @memberof oj.editableValue
*
*/
_VALIDATION_CONTEXT : _sValidationContext,
/**
* Default options used by validate method.
*
* @protected
* @const
* @type {Object}
* @memberof oj.editableValue
* @see #validate
*/
_VALIDATE_METHOD_OPTIONS : _sValidateMethodOptions,
/**
* Called at component create time primarily to initialize options, often using DOM values. This
* method is called before _ComponentCreate is called, so components that override this method
* should be aware that the component has not been rendered yet. The element DOM is available and
* can be relied on to retrieve any default values. <p>
*
* This method sets defaults for its options that have a DOM namesake. E.g., value, required,
* disabled etc. Subclasses can override this method to set their own defaults for these options.
* Example, the value option is often not set on this.element for components like radioset, which
* walk the sub-tree to determine the value.
*
* @param {!Object} originalDefaults - original default options defined on the widget and its ancestors
* @param {?Object} constructorOptions - options passed into the wiget constructor
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_InitOptions : function(originalDefaults, constructorOptions)
{
this._super(originalDefaults, constructorOptions);
},
/**
* Initializes options defined by this base class.
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_ComponentCreate : function ()
{
var node = this.element,
attrsToRemove = ["required", "title"], //remove attributes that trigger html5 validation +
// inline bubble
savedAttributes = this._GetSavedAttributes(node);
this._super();
this.options['messagesCustom'] = this.options['messagesCustom'] || [];
this.options['messagesHidden'] = [];
this.options['messagesShown'] = this.options['messagesCustom'].length > 0 ?
this._cloneMessagesBeforeSet(this.options['messagesCustom']) : [];
// update element DOM for disabled. TODO: say why
this._SetDisabledDom(node);
// we do this here instead of in _InitOptions because here we have the final value.
//Bug 18926010 - an empty placeholder shows up if data changed after first binding
if (this._HasPlaceholderSet())
{
// update element placeholder
this._SetPlaceholder(this.options['placeholder']);
this._customPlaceholderSet = true;
}
// remove html5 validation attributes; it's safe to remove these here because components should
// have already initialized options based on DOM in _InitOptions().
$.each(attrsToRemove, function (index, value)
{
if (value in savedAttributes)
{
node.removeAttr(value);
}
});
},
/**
* The value option alone is initialized here since it requires the component to be fully
* created. Calling this.options.value before this method does not guarantee the correct
* value to be returned.
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_AfterCreate : function ()
{
this._super();
// decorate the label
this._createOjLabel();
// refresh value, theming and aria attributes
this._doRefresh(false);
// initialize component messaging
this._initComponentMessaging();
// run deferred validation
this._runDeferredValidation(this._VALIDATION_CONTEXT.COMPONENT_CREATE);
// trigger optionChange for messagesShown if it's non-empty. messagesShown would have been
// updated in _ComponentCreate if messagesCustom was non-empty
if (this.options['messagesShown'].length > 0)
{
this._setMessagesOption('messagesShown', this.options['messagesShown'], null, true);
}
this.widget().addClass("oj-form-control");
},
/**
* <p>Saves all the element's attributes. In _destroy all attributes will be restored.
* </p>
*
* @param {Object} element - jQuery selection to save attributes for
* @protected
* @memberof oj.editableValue
* @instance
* @override
*/
_SaveAttributes : function (element)
{
this._SaveAllAttributes(element);
},
/**
* @protected
* @memberof oj.editableValue
* @instance
* @override
*/
_RestoreAttributes : function (element)
{
this._RestoreAllAttributes(element);
},
/**
* Performs post processing after _SetOption() is called. Different options when changed perform
* different tasks.
*
* @param {string} option
* @param {Object=} flags
* @protected
* @memberof oj.editableValue
* @instance
*/
_AfterSetOption : function (option, flags)
{
switch (option)
{
case "disabled":
this._AfterSetOptionDisabledReadOnly(option, _sDisabledOptionOptions);
break;
case "converter":
this._AfterSetOptionConverter(option);
break;
case "displayOptions" :
// clear the cached merged options; the getter setup for this.options['displayOptions']
// will merge the new value with the defaults
this._initComponentMessaging();
break;
case "help":
this._Refresh(option, this.options[option]);
break;
case "messagesCustom":
this._messagesCustomOptionChanged(flags);
break;
case "placeholder":
this._placeholderOptionChanged(flags);
break;
case "readOnly":
this._AfterSetOptionDisabledReadOnly(option, _sReadOnlyOptionOptions);
break;
case "required":
this._AfterSetOptionRequired(option);
break;
case "title":
// no reason to refresh component when title changes.
this._titleOptionChanged();
break;
case "translations":
this.refresh();
break;
case 'value':
this._AfterSetOptionValue(option, flags);
break;
case "validators":
this._AfterSetOptionValidators(option);
break;
default:
break;
}
},
/**
* Performs post processing after converter option changes by taking the following steps.
*
* - always push new converter hint to messaging <br/>
* - if component has no errors -> refresh UI value<br/>
* - if component is invalid has messagesShown -> clear all component errors and run full
* validation using display value. <br/>
* - if there are validation errors, value is not pushed to model; messagesShown is
* updated.<br/>
* - if no errors result from the validation, push value to model; author needs to
* listen to optionChange(value) to clear custom errors.<br/>
* - if component is invalid has messagesHidden -> refresh UI value. no need to run deferred
* validations. <br/>
* - messagesCustom is never cleared<br/>
*
* @param {String} option
*
* @protected
* @memberof oj.editableValue
* @instance
*/
// called when 'converter' option changed, usually from option/setOption calls
_AfterSetOptionConverter : function (option)
{
var runFullValidation = false;
// clear the cached converter instance and push new hint to messaging
this._ResetConverter();
if (this._hasInvalidMessagesShowing())
{
runFullValidation = true;
}
if (runFullValidation)
{
this._clearComponentMessages();
this._updateValue(_sConverterOptionOptions);
}
else
{
// refresh UI display value when there are no errors or where there are only deferred errors
this._Refresh(option, this.options[option], true);
}
},
/**
* Performs post processing after disabled or readOnly option changes by taking the following
* steps. (Steps are same for readOnly option).
* <p>
* if disabled component is enabled then, <br/>
* - if there are no errors, run deferred validation. component could have been initialized with
* empty value and disabled.<br/>
* - if component is invalid and showing messages clear component error, grab UI value and run
* full validation.<br/>
* - if component is invalid and has hidden messages; do nothing. <br/>
* </p>
* <p>
* if enabled component is disabled no validation is run.<br/>
* </p>
*
* @param {String} option
* @param {Object} validationOptions
*
* @protected
* @memberof oj.editableValue
* @instance
*/
_AfterSetOptionDisabledReadOnly : function (option, validationOptions)
{
var isEnabled = !(this.options[option] || false);
// always refresh
this._Refresh(option, this.options[option]);
if (isEnabled)
{
this._runMixedValidationAfterSetOption(validationOptions);
}
},
/**
* Performs post processing after required option is set by taking the following steps.
*
* - if component is invalid and has messgesShown -> required: false/true -> clear component errors;
* run full validation with UI value (we don't know if the UI error is from a required validator
* or something else);<br/>
* - if there are validation errors, then value not pushed to model; messagesShown is
* updated<br/>
* - if no errors result from the validation, push value to model; author needs to
* listen to optionChange(value) to clear custom errors.<br/>
*
* - if component is invalid and has messagesHidden -> required: false -> clear component
* errors; no deferred validation is run.<br/>
* - if component has no error -> required: true -> run deferred validation (we don't want to flag
* errors unnecessarily)<br/>
* - messagesCustom is never cleared<br/>
*
* @param {string} option
*
* @protected
* @memberof oj.editableValue
* @instance
*/
_AfterSetOptionRequired : function (option)
{
// refresh hints, theming and aria to reflect new state
this._Refresh(option, this.options[option]);
this._runMixedValidationAfterSetOption(_sRequiredOptionOptions);
},
/**
* Performs post processing after value option changes by taking the following steps.
*
* - triggers an optionChange and does writeback if required.<br/>
* - if setOption was from programmatic intervention, <br/>
* - clear custom messages and component messages; <br/>
* - run deferred validation. if there is an error, updates messagesHidden. <br/>
* - always refreshes UI display <br/>
*
* @param {string} option
* @param {Object=} flags
*
* @protected
* @memberof oj.editableValue
* @instance
*
*/
_AfterSetOptionValue : function(option, flags)
{
var isUIValueChange = false, doNotClearMessages, context = flags ? flags['_context'] : null;
if (context)
{
isUIValueChange = context.originalEvent ? true : false;
doNotClearMessages = context.doNotClearMessages || false;
}
if (!isUIValueChange)
{
// value option can be updated directly (i.e., programmatically or through user interaction)
// or updated indirectly as a result of some other option changing - e.g., converter,
// validators, required etc. See _updateValue() method for details.
// When value changes directly due to programatic intervention (usually page author does this)
// then clear all messages and run deferred validation.
// If value changes indirectly do not clear custom messages (component messages are already
// cleared) and run deferred validation.
if (!doNotClearMessages)
{
this._clearAllMessages(null);
}
this._runDeferredValidation(this._VALIDATION_CONTEXT.VALUE_OPTION_CHANGE);
}
// refresh UI display value
this._Refresh(option, this.options[option], true);
},
/**
* When validators option changes, take the following steps.
*
* - Clear the cached normalized list of all validator instances. push new hints to messaging.<br/>
* - if component is valid -> validators changes -> no change<br/>
* - if component is invalid has messagesShown -> validators changes -> clear all component
* messages and re-run full validation on displayValue. if there are no errors push value to
* model;<br/>
* - if component is invalid has messagesHidden -> validators changes -> do nothing; doesn't change
* the required-ness of component <br/>
* - messagesCustom is not cleared.<br/>
*
* NOTE: The behavior applies to any option that creates implicit validators - min, max, pattern,
* etc. Components can call this method when these options change.
*
* @returns {undefined}
* @protected
* @memberof oj.editableValue
* @instance
*/
_AfterSetOptionValidators : function ()
{
var runFullValidation = false;
// resets all validators and pushes new hints to messaging
this._ResetAllValidators();
if (this._hasInvalidMessagesShowing())
{
runFullValidation = true;
}
if (runFullValidation)
{
this._clearComponentMessages();
this._updateValue(_sValidatorsOptionOptions);
}
},
/**
* Whether the a value can be set on the component. If the component is
* disabled or readOnly then setting value on component is a no-op.
*
* @see #_SetValue
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_CanSetValue: function ()
{
var disabled = this.options['disabled'] || false, readOnly = this.options['readOnly'] || false;
return (disabled || readOnly) ? false : true;
},
/**
* Detaches the widget from the element and restores element exactly like it was before the widget
* was attached.
* @protected
* @expose
* @memberof oj.editableValue
* @instance
*
*/
_destroy : function ()
{
var widget = this.widget();
var ret = this._super();
this._clearAllMessages(null, true);
this._getComponentMessaging().deactivate();
// make sure the label is still "alive". Otherwise we could get error when we try to
// destroy it if the dom was removed or something.
if (this.$label && oj.Components.getWidgetConstructor(this.$label[0]) != null)
{
this.$label._ojLabel( "destroy" );
}
return ret;
},
/**
* Sets focus on the element that naturally gets focus. For example, this would be the input
* element for input type components. <br/>
*
* @returns {*} a truthy value if focus was set to the intended element, a falsey value
* otherwise.
* @expose
* @memberof oj.editableValue
* @instance
* @protected
* @since 0.7
*/
Focus : function ()
{
this._GetContentElement().focus();
return true;
},
/**
* Called (by the widget factory) when the option changes, this method responds to the change
* by refreshing the component if needed. This method is not called for the options passed in
* during the creation of the widget.
*
* @param {string} name of the option
* @param {Object|string} value
* @param {Object?} flags - optional flags. The following flags are currently supported:
* <ul>
* <li>changed - true if the caller wants to indicate the value has changed, so no comparison is necessary</li>
* </ul>
*
* @expose
* @memberof oj.editableValue
* @instance
* @protected
*/
_setOption : function (name, value, flags)
{
var retVal, previous, skipSetOption = false;
// Step 1: Remember previous values
if (typeof name === "string" && value !== undefined)
{
switch (name)
{
case "messagesHidden":
// this option can never be set programmatically by page author
skipSetOption = true;
break;
case "messagesShown":
// this option can never be set programmatically by page author
skipSetOption = true;
break;
case "rawValue":
// rawValue is readOnly, so throw an error here.
skipSetOption = true;
break;
}
}
if (skipSetOption)
{
oj.Logger.error(name + " option cannot be set");
return this;
}
// Step 2: Update option value
retVal = this._superApply(arguments);
// Step 3: Do post processing like triggering events, refreshing component DOM etc.
this._AfterSetOption(name, flags);
return retVal;
},
// *********** END WIDGET FACTORY METHODS **********
/**
* Returns a jquery object of the element representing the content node. This could be a jQuery
* object of the element the widget was invoked on - typically this is an input or select or
* textarea element for which a value can be set.
*
* @memberof oj.editableValue
* @instance
* @protected
* @return {Object} the jquery element that represents the editable content. E.g., an input
*/
_GetContentElement : function ()
{
return this.element;
},
/**
* Returns a jquery object of the element representing the primary label node for the input
* component.
* First we look for the aria-labelledby attribute on the input.
* If that's not found, we look for the label with 'for' attribute
* pointing to input.
* If that's not found, we walk up the dom looking for aria-labelledby.
*
* @memberof oj.editableValue
* @instance
* @protected
* @return {Object} the jquery element that represents the input component's label.
* return null if it can't find anything.
*/
_GetLabelElement : function ()
{
// If input has aria-labelledby set, then look for label it is referring to.
var queryResult = this._getAriaLabelledByElement(this.element);
if (queryResult !== null && queryResult.length !== 0)
{
return queryResult;
}
// if no aria-labelledby is on the input, then look for a label with 'for'
// set.
var id = this.element.prop("id");
if (id !== undefined)
{
var labelQuery = "label[for='" + id + "']";
queryResult = $(labelQuery);
if (queryResult.length !== 0)
{
return queryResult;
}
}
// if no aria-labelledby on input and no label with 'for' pointing to input,
// then as a final step we walk up the dom to see if aria-labelledby is set.
// If so, then we find the label it is referring to.
// This would be the case when you have multiple inputs grouped in a div
// <label id="grouplabel">Address</label>
// <div aria-labelledby="grouplabel"><input/><input/><input/></div>
var ariaElement = this.element.closest("[aria-labelledby]");
if (ariaElement.length !== 0)
{
// Element has aria-labelledby set, so look for label it is referring to.
queryResult = this._getAriaLabelledByElement(ariaElement);
if (queryResult !== null && queryResult.length !== 0)
{
return queryResult;
}
}
return null;
},
/**
* Returns the element's value. Normally, this is a call to this.element.val(), but for some
* components, it could be something else. E.g., for ojRadioset the element's value is really the
* value of the selected radio in the set.
*
* @override
* @memberof oj.editableValue
* @instance
* @protected
*/
_GetElementValue : function ()
{
return this.element.val();
},
/**
* Returns a jquery object of the element that triggers messaging behavior. The trigger element
* is usually an input or select or textarea element for which a value can be set/retrieved and
* validated.
*
* @return {Object} jquery object
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_GetMessagingLauncherElement : function ()
{
return this._GetContentElement();
},
/**
* Returns the normalized converter instance.
*
* @return {Object} a converter instance or null
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_GetConverter : function ()
{
// this._converter holds the instance
if (!this._converter)
{
var converterOption = this.options['converter'];
this._converter = oj.IntlConverterUtils.getConverterInstance(converterOption);
}
return this._converter || null;
},
/**
* Returns an array of implicit validators setup by component. This list contains validators for
* the internal use of the component and are not a part of this.options.validators. <br/>
* E.g., if the pattern attribute or option is set, a RegExpValidator instance is automatically
* created and added to this list. <br/>
* RequiredValidator is tracked separately from the default validators.
*
* @return {Object} a map of string name to the validator instance.
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_GetImplicitValidators : function ()
{
if (!this._implicitValidators)
{
this._implicitValidators = {};
}
return this._implicitValidators;
},
/**
* Returns the display value that is ready to be passed to the converter.
*
* @param {Object} value the stored value if available that needs to be formatted for display
* @return {string} usually a string display value
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_GetDisplayValue : function (value)
{
return this._GetContentElement().val();
},
/**
* Returns an array of all validators built by merging the validators option set on the component
* and the implicit validators setup by the component. <br/>
* This does not include the implicit required validator. Components can override to add to this
* array of validators.
*
* @return {Array} of validators
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_GetAllValidators : function ()
{
if (!this._allValidators)
{
var allValidators = [], validatorsOption = this.options['validators'], validator,
isValidatorInstance = true, implicitValidatorMap = this._GetImplicitValidators(),
implicitValidators = [], vType, vOptions, vTypeStr, normalizedValidators, i;
// combine public and implicit validators to get the combined list
var keys = Object.keys(implicitValidatorMap), valType;
var len = keys.length;
if (len > 0)
{
for (var idx = 0; idx < len; idx++)
{
valType = keys[idx];
implicitValidators.push(implicitValidatorMap[valType]);
}
allValidators = allValidators.concat(implicitValidators);
}
if (validatorsOption)
{
normalizedValidators = [];
// Normalize validators
for (i = 0; i < validatorsOption.length; i++)
{
validator = validatorsOption[i];
if (typeof validator === "object")
{
// check if we have an actual validator instance that implements the validate() method
// oj.Validation.__doImplementsCheck(validator, oj.Validator);
if (!(validator['validate'] && typeof validator['validate'] === "function"))
{
isValidatorInstance = false;
}
if (!isValidatorInstance)
{
// we maybe dealing with an object literal
// {'type': 'numberRange', 'options': { 'min': 100, 'max': 1000,
// 'hint': {'min': 'some hint about min'}}}
vTypeStr = validator['type'];
if (vTypeStr && typeof vTypeStr === "string")
{
vType = oj.Validation.validatorFactory(vTypeStr);
if (vType)
{
vOptions = oj.CollectionUtils.copyInto({}, validator['options']) || {};
// we push converter into the options if not provided explicitly. This is to allow
// validators to format values shown in the hint and messages
vOptions['converter'] = vOptions['converter'] || this._GetConverter();
vOptions['label'] = vOptions['label'] || this._getLabelText();
validator = vType.createValidator(vOptions);
}
else
{
oj.Logger.error("Unable to locate a validatorFactory for the requested type: " + vTypeStr);
}
}
}
normalizedValidators.push(validator);
}
else
{
oj.Logger.error("Unable to parse the validator provided:" + validator);
}
}
allValidators = allValidators.concat(normalizedValidators);
}
this._allValidators = allValidators;
}
return this._allValidators || [];
},
/**
* EditableValue caches the validators to be run, within this._allValidators variable.
* This is great; however when the implicit validator needs to be reset [i.e. min + max changing]
* or the validators option changes, then the cached this._allValidators needs to be cleared.
* This method also updates the messaging strategies as hints associated with validators could
* have changed.
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_ResetAllValidators : function ()
{
if (this._allValidators)
{
this._allValidators.length = 0;
}
this._allValidators = null;
// update messagingstrategy as hints associated with validators could have changed
this._getComponentMessaging().update(this._getMessagingContent(
this._MESSAGING_CONTENT_UPDATE_TYPE.VALIDATOR_HINTS));
},
/**
* Clears the cached converter stored in _converter and pushes new converter hint to messaging.
* Called when convterer option changes
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_ResetConverter : function ()
{
this._converter = null;
this._getComponentMessaging().update(
this._getMessagingContent(this._MESSAGING_CONTENT_UPDATE_TYPE.CONVERTER_HINT));
},
/**
* Whether the component is required.
*
* @return {boolean} true if required; false
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_IsRequired : function ()
{
return this.options['required'];
},
/**
* Convenience handler for the DOM 'change' event. Subclasses are expected to wire up event
* handlers for DOM events that they wish to handle.<br/>
*
* The implementation retrieves the display value for the component by calling _GetDisplayValue()
* and calls _SetValue(), with full validation.
*
* @param {Event} event DOM event
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_HandleChangeEvent : function (event)
{
var submittedValue = this._GetDisplayValue();
// run full validation
this._SetValue(submittedValue, event);
},
/**
* Convenience function to set the rawValue option. Called by subclasses
*
* @param {Event} event DOM event
* @param {String=} val value to set rawValue to
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_SetRawValue : function (event, val)
{
var flags = {};
flags['_context'] = {originalEvent: event, writeback: true, internalSet: true};
if (this.options['rawValue'] !== val)
this.option("rawValue", val, flags);
},
/**
* Called in response to a change in the options set for this component, this method refreshes the
* component display value. Subclasses can override to provide custom refresh behavior.
*
* @param {String=} name the name of the option that was changed
* @param {Object=} value the current value of the option
* @param {boolean=} forceDisplayValueRefresh
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_Refresh : function (name, value, forceDisplayValueRefresh)
{
switch (name)
{
case "converter":
value = this.options['value'];
this._refreshComponentDisplayValue(value, forceDisplayValueRefresh);
break;
case "disabled":
this._refreshTheming("disabled", this.options['disabled']);
break;
case "help":
// refresh the help - need to keep the label in sync with the input.
var helpDef = this.options.help["definition"];
var helpSource = this.options.help["source"];
var labelHelpIconWrapper = this._getAriaDescribedByIconWrapperId();
if (this.$label)
{
// Calling option this way calls _setOption in _ojLabel.
// order matters here. When _ojLabel's help is changed, it removes
// the help dom, then re-adds it. We need to make sure _ojLabel's
// describedById option is there so we can find the dom to remove
// and recreate if needed.
this.$label._ojLabel("option", "describedById", labelHelpIconWrapper);
this.$label._ojLabel("option", "help",
{"definition":helpDef,
"source":helpSource});
}
break;
case "required":
this._refreshTheming("required", this._IsRequired());
this._RefreshAriaRequired(value);
// need to keep the label in sync with the input
if (this.$label)
{
var labelHelpIconWrapper = this._getAriaDescribedByIconWrapperId();
this.$label._ojLabel("option", {"describedById": labelHelpIconWrapper,
"ariaRequiredUnsupported": this._AriaRequiredUnsupported()});
this.$label._ojLabel("option", "required", value);
}
break;
case "value":
this._refreshComponentDisplayValue(value, forceDisplayValueRefresh);
break;
}
},
/**
* Called when a aria-required attribute needs to be set or removed.
* Most inputs/selects need aria-required on the input element (aka 'content')
* But it is not legal to have aria-required on radio/checkboxes.
* Subclasses can override to put aria-required where they want.
*
* @param {Object=} value the current value of the required option
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_RefreshAriaRequired : function (value)
{
var ariaValue, contentNode = this._GetContentElement();
ariaValue = value; //(value == "required") ? true : false;
if (ariaValue && contentNode)
{
contentNode.attr("aria-required", ariaValue);
}
else
{
contentNode.removeAttr("aria-required");
}
},
/**
* Called to find out if aria-required is unsupported. This is needed for the label.
* It is not legal to have aria-required on radio/checkboxes, nor on
* radiogroup/group.
* @memberof oj.editableValue
* @instance
* @protected
*/
_AriaRequiredUnsupported : function()
{
return false;
},
/**
* Called anytime the label DOM changes requiring a reset of any dependent feature that caches the
* label, including all validators.
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_ResetComponentState : function ()
{
// the DOM for the label and its text could have changed.
if (this.$label)
{
this.$label._ojLabel("refresh");
}
// reset all validators when label changes
this._implicitReqValidator = null;
this._converter = null;
this._ResetAllValidators();
},
/**
* Called when the display value on the element needs to be updated. This method updates the
* (content) element value. Widgets can override this method to update the element appropriately.
*
* @param {String} displayValue of the new string to be displayed
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_SetDisplayValue : function (displayValue)
{
var contentElem = this._GetContentElement();
if (contentElem.val() !== displayValue)
contentElem.val(displayValue);
},
/**
* Sets the disabled option onto the dom.
* Component subclasses can override this method to not do this in cases where it is invalid,
* like on a div (e.g., radioset's root dom element is a div).
* @param {Object} node - dom node
*
* @memberof oj.editableValue
* @instance
* @protected
* @since 1.0.0
*/
_SetDisabledDom : function(node)
{
if (typeof this.options['disabled'] === "boolean")
{
node.prop("disabled", this.options['disabled']);
}
},
/**
* Sets the placeholder text on the content element by default. It sets the placeholder attribute
* on the element. Component subclasses can override this method to control where placeholder text
* gets set.
* @param {string} value
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_SetPlaceholder : function(value)
{
this._GetContentElement().attr("placeholder", value);
},
/**
* Sets the placeholder option with the value.
*
* @param {string} value
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_SetPlaceholderOption : function (value)
{
this.options['placeholder'] = value;
},
/**
* whether the placeholder option is set
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_HasPlaceholderSet : function()
{
//Bug 18926010 - an empty placeholder shows up if data changed after first binding
return this.options['placeholder'];
},
/**
* Clear the placeholder option
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_ClearPlaceholder : function()
{
//Bug 18926010 - an empty placeholder shows up if data changed after first binding
this._SetPlaceholderOption("");
this._SetPlaceholder("");
},
/**
* Runs full validation on the newValue (usually the display value) and sets the parsed value on
* the component if value passes basic checks and there are no validation errors. <br/>
* If the newValue is undefined or if it differs from the last saved displayValue this method
* skips validation and does not set value (same as ADF).<br/>
*
* @param {string|Object} newValue the ui value that needs to be parsed before it's set.
* @param {Object=} event an optional event if this was a result of ui interaction. For user
* initiated actions that trigger a DOM event, passing this event is required. E.g., if user action
* causes a 'blur' event.
* @param {Object=} options - an Object literal that callers pass in to determine how validation
* gets run.
* @param {boolean=} options.doValueChangeCheck - if set to true compare newValue with last
* displayValue before running validation; if false, always run validation. E.g., set to false
* when validate() is called.
* @param {boolean=} options.doNotClearMessages - if set method will not clear all messages. This
* is provided for callers that may want to clear only some of the messages. E.g., when required
* option changes, it clears only component messages, not custom.
* @param {number=} options.validationMode - accepted values (defined in _VALIDATION_MODE) are:
* <ul>
* <li>FULL - the default and runs both the converter and all validators. </li>
* <li>VALIDATORS_ONLY - runs all validators including the required validator is run.</li>
* <li>REQUIRED_VALIDATOR_ONLY - runs just the required validator. </li>
* </ul>
* @return {boolean} false if value was not set due to validation error.
* @example <caption>Widget subclasses can use this convention to run full validation</caption>
* this._SetValue(value, event);
*
* @memberof oj.editableValue
* @instance
* @protected
*
*/
_SetValue : function (newValue, event, options)
{
var doValueChangeCheck = (options && typeof options.doValueChangeCheck === "boolean") ?
options.doValueChangeCheck : true, parsed;
// disallow setting a value of undefined by widgets
if (newValue === undefined)
{
oj.Logger.warn("Attempt to set a value of undefined");
return false;
}
if (!doValueChangeCheck || newValue !== this._getLastDisplayValue())
{
parsed = this._Validate(newValue, event, options);
// if validation passed
if (parsed !== undefined && this.isValid())
{
// update value option
this._updateValueOption(parsed, event, options && options.validationContext);
return true;
}
}
else
{
if (oj.Logger.level > oj.Logger.LEVEL_WARN)
{
oj.Logger.info("Validation skipped and value option not updated as submitted value '" +
(newValue.toString) ? newValue.toString() : newValue + " same as previous.");
}
}
return false;
},
/**
* Runs full validation on the value. If value fails basic checks
* (see <a href="#_CanSetValue">_CanSetValue</a>, or if value failed validation, this method
* returns false. Otherwise it returns true.
*
* <p>
* Components should call this method if they know UI value has changed and want to set the
* new component value.
* </p>
*
* @param {string|Object} newValue the actual value to be set. Usually this is the string display value
* @param {Object=} event an optional event if this was a result of ui interaction. For user
* initiated actions that trigger a DOM event, passing this event is required. E.g., if user action
* causes a 'blur' event.
* @param {{doNotClearMessages:boolean,validationContext:number,validationMode:number}=} options
* an Object literal that callers pass in to determine how validation gets run.
* @param {boolean=} options.doNotClearMessages - if set method will not clear all messages. This
* is provided for callers that may want to clear only some of the messages. E.g., when required
* option changes, it clears only component messages, not custom.
* @param {number=} options.validationContext - the context this method was called. When not set it
* defaults to _VALIDATION_CONTEXT.USER_ACTION.
* @param {number=} options.validationMode - accepted values defined in _VALIDATION_MODE
*
* @return {Object|string|undefined} the parsed value or undefined if validation failed
*
* @memberof oj.editableValue
* @instance
* @protected
*/
_Validate : function (newValue, event, options)
{
var mode = options && options.validationMode ? options.validationMode :
this._VALIDATION_MODE.FULL,
context = options && options.validationContext ? options.validationContext :
this._VALIDATION_CONTEXT.USER_ACTION,
doNotClearMessages = options && options.doNotClearMessages || false, result;
// disallow setting a value of undefined by widgets
if (newValue === undefined)
{
oj.Logger.warn("Attempt to set a value of undefined");
}
else if (this._CanSetValue())
{
if (!doNotClearMessages)
{
this._clearAllMessages(event);
};
this._setLastSubmittedValue(newValue);
try
{
return this._runNormalValidation(newValue, mode, context, event);
}
catch(e)
{
// validation failed
}
}
else
{
if (oj.Logger.level > oj.Logger.LEVEL_WARN)
{
oj.Logger.info("Validation skipped and value option not set as component state does not " +
" allow setting value. For example if the component is readonly or disabled.");
}
}
return result;
},
_CompareOptionValues : function (option, value1, value2)
{
if (option === 'value')
{
return oj.Object.compareValues(value1, value2);
}
else if (option.indexOf('messages') === 0)
{
return this._messagesEquals(value1, value2);
}
return this._superApply(arguments);
},
/**
* Returns the default styleclass for the component. All input components must override.
*
* @return {string}
*
* @memberof oj.editableValue
* @instance
* @protected
* @abstract
*/
_GetDefaultStyleClass : function ()
{
oj.Assert.failedInAbstractFunction();
return "";
},
// I N T E R N A L P R I V A T E C O N S T A N T S A N D M E T H O D S
// Subclasses should not override or call these methods
/**
* Types of messaging content to update.
* <ul>
* <li>'ALL' - builds all messaging content</li>
* <li>'VALIDITY_STATE' - updates only validityState every time validation runs and there are
* new messages or when the messages option changes.</li>
* <li>'CONVERTER_HINT' - updates only converter hints, this is used when converter option
* changes</li>
* <li>'VALIDATOR_HINTS' - updates only validator hints, this is used when validators option
* changes</li>
* <li>'TITLE' - updates only title, when the title property changes</li>
* </ul>
* @private
*/
_MESSAGING_CONTENT_UPDATE_TYPE : {ALL : 1,
VALIDITY_STATE : 2,
CONVERTER_HINT : 3,
VALIDATOR_HINTS : 4,
TITLE : 5},
/**
* when below listed options are passed to the component, corresponding CSS will be toggled
* @private
* @const
* @type {Object}
* @memberof oj.editableValue
*/
_OPTION_TO_CSS_MAPPING: {
"disabled": "oj-disabled",
"required": "oj-required"
},
/**
* Clears all messages for this component. Today this only happens when <br/>
* - the component's value changes as a result of user interaction, <br/>
* - the reset method is called, <br/>
* - component is destroyed.<br/>
*
* @param {Event=} event
* @param {boolean=} doNotSetOption default value is false; a true value clears the option
* directly without using the public option method, causing no events to be fired.
* @private
* @memberof oj.editableValue
* @instance
*/
_clearAllMessages : function(event, doNotSetOption)
{
doNotSetOption = doNotSetOption ? true : false;
if (!doNotSetOption)
{
this._clearMessages('messagesHidden', event);
this._clearMessages('messagesShown', event);
this._clearMessages('messagesCustom', event);
}
else
{
this.options['messagesHidden'] = [];
this.options['messagesShown'] = [];
this.options['messagesCustom'] = [];
}
},
/**
* Clears all messages that were added by component. These includes all messages in messagesHidden
* option and all messages except custom in messagesShown.
*
* Called when these options change - validators, disabled, readonly, required, and methods -
* refresh are called.
*
* @private
* @memberof oj.editableValue
* @instance
*/
_clearComponentMessages : function ()
{
var shownMsgs = this.options['messagesShown'], beforeLen = shownMsgs.length, msg;
this._clearMessages('messagesHidden');
// remove component messages in messagesShown. Custom messges are kept intact.
for (var i = beforeLen -1; i >= 0; i--)
{
msg = shownMsgs[i];
if (msg instanceof oj.ComponentMessage)
{
shownMsgs.splice(i, 1);
}
}
if (shownMsgs.length !== beforeLen)
{
this._setMessagesOption('messagesShown', shownMsgs, null, true);
}
},
/**
* Sets the messages option with the new value. Updates the new 'valid' state and notifies
* messaging.
*
* This method updates the option directly without invoking setOption() method. This is done by
* setting the following property in flags parameter of the option() method -
* <code class="prettyprint">{'_context': {internalSet: true}}</code>
*
* @param {String} key
* @param {Array} value
* @param {Event=} event
* @param {Boolean=} changed
*
* @private
*/
_setMessagesOption : function (key, value, event, changed)
{
var flags = {};
// Optimize for the common 'clear' operation
var bothEmpty = value.length === 0 && this.options[key].length === 0;
if (changed || !bothEmpty)
{
flags['_context'] = {originalEvent: event, writeback: true, internalSet: true};
flags['changed'] = changed || !bothEmpty;
this._resetValid();
this.option(key, value, flags);
this._updateMessagingContent();
}
},
/**
* Clears the messages options - <code class="prettyprint">messagesHidden</code>,
* <code class="prettyprint">messagesShown</code>, <code class="prettyprint">messagesCustom</code>.
*
* @param {String} option messages option that is being cleared.
* @param {Event=} event
*
* @private
* @memberof oj.editableValue
* @instance
*/
_clearMessages : function (option, event)
{
this._setMessagesOption(option, [], event);
},
/**
* Clones messages before it's set. <br/>
*
* @param {Array=} value
* @returns {Array} of cloned messages
*
* @private
* @memberof oj.editableValue
* @instance
*/
_cloneMessagesBeforeSet : function (value)
{
var val, msgsClone = [], i, msg;
// we want all messages to be an instance of oj.Message. So clone array
if (value && value.length > 0)
{
for (i = 0; i < value.length; i++)
{
val = value[i];
if (val instanceof oj.Message)
{
msgsClone.push(val.clone());
}
else
{
msg = new oj.Message(val['summary'], val['detail'], val['severity']);
msg = Object.freeze ? Object.freeze(msg) : msg;
msgsClone.push(msg);
}
}
}
return msgsClone;
},
/**
* Create the _ojLabel component
* @private
* @memberof oj.editableValue
* @instance
*
*/
_createOjLabel : function ()
{
this.$label = this._GetLabelElement();
if (this.$label)
{
var helpDef = this.options['help']['definition'];
var helpSource = this.options['help']['source'];
var required = this.options['required'];
var ariaRequiredUnsupported = this._AriaRequiredUnsupported();
var labelHelpIconWrapper =
this._addAriaDescribedByAndReturnId(helpSource, helpDef, required, ariaRequiredUnsupported);
// create the ojLabel component
this.$label._ojLabel(
// {inputLabelStyleClass: this._GetDefaultStyleClass()+"-label",
{rootAttributes:{'class': this._GetDefaultStyleClass()+"-label"},
describedById: labelHelpIconWrapper,
required:this.options['required'],
ariaRequiredUnsupported: ariaRequiredUnsupported,
help:{'definition': helpDef, 'source': helpSource}});
}
},
/**
* Refreshes the component to respond to DOM changes, in which case fullRefresh=true.
* @param {boolean} fullRefresh true if a full refresh of the component is desired.
* @private
* @memberof oj.editableValue
* @instance
*/
_doRefresh : function (fullRefresh)
{
var runFullValidation = false;
fullRefresh = fullRefresh || false;
if (fullRefresh)
{
// reset state and re-initialize component messaging, since refresh() can be called when
// locale changes, requiring component to show messaging artifacts for current locale.
this._ResetComponentState();
this._initComponentMessaging();
if (this._hasInvalidMessagesShowing())
{
runFullValidation = true;
}
// clear component messages always
this._clearComponentMessages();
if (runFullValidation)
{
// run full validation using the current display value;
// show messages immediately if there are errors otherwise set value to option
this._updateValue(_sRefreshMethodOptions);
}
else
{
// run deferred validation if comp is either showing a deferred error or has no errors.
// But only when required is true.
if (this._IsRequired())
{
this._runDeferredValidation(_sRefreshMethodOptions.validationContext);
}
// refresh UI display value when there are no errors or where there are only deferred errors
this._Refresh("value", this.options['value'], true);
}
}
else
{
this._Refresh("value", this.options['value']);
this._Refresh("required", this.options['required']);
//this._RefreshAriaRequired(this.options.required);
//this._refreshTheming("required", this.options.required);
}
// always refresh these
this._Refresh("disabled", this.options['disabled']);
},
/**
* Gets the last stored model value
*
* @private
* @memberof oj.editableValue
* @instance
*/
_getLastModelValue : function ()
{
return this._oj_lastModelValue;
},
_getLastDisplayValue : function ()
{
return this._oj_lastElementValue;
},
/**
* Get the element whose id matches the elem's aria-labelledby value, if any.
* @param {Object} elem the dom element from which you want to get the
* aria-labelledby property value
* @return {Object} if element does not have aria-labelledby defined, then
* returns null. If it does, then return a new jQuery object with the
* label with an id equal to the aria-labelledby value. If no match, then
* the jQuery object will be empty.
*
* @private
* @memberof oj.editableValue
* @instance
*/
_getAriaLabelledByElement: function (elem)
{
// look for a label with an id equal to the value of aria-labelledby.
// .prop does not work for aria-labelledby. Need to use .attr to find
// aria-labelledby.
var ariaId = elem.attr("aria-labelledby");
if (ariaId !== undefined )
{
var labelQuery = "label[id='" + ariaId + "']";
return $(labelQuery);
}
else
return null;
},
/**
* Cconstruct an id from the element's id, render aria-describedby with this id,
* and then return the id to be used by the ojLabel.
*
* @return {string} an id we constructed
* @private
* @memberof oj.editableValue
* @instance
*
*/
_getAriaDescribedByIconWrapperId : function ()
{
// generate a unique id if one does not exist, then get the id and use it to
// create an id that will be set on the label help icon wrapper
this.element.uniqueId();
var id = this.element.prop("id");
var labelHelpIconWrapperId = id + "Icons";
return labelHelpIconWrapperId;
},
/**
* If there is help source (an external URL), then construct an
* id from the element's id, render aria-describedby with this id, and then
* return the id to be used by the ojLabel.
*
* For accessibility, we need to wrap help icons with a div.
* Then, on the input component we use aria-describedby to point to this div.
* <div id="fooIcons">
* <span class="oj-label-help-icon oj-component-icon oj-clickable-icon">
* /<span/>
* /<div/>
* <input aria-describedby="fooIcons" id="foo" type="text"/>
*
* This function constructs an id to put on the help icon div and returns it.
* It also adds the aria-describedby on the input.
*
* We render the help icon only if there is help source.
*
* @return {string} an id we constructed
* @param {string} helpSource helpSource or helpDef must be present
* for the aria-describedby to be rendered.
* @param {string} helpDef or helpSource must be present
* for the aria-describedby to be rendered.
* @param {boolean} required
* @param {boolean} ariaRequiredUnsupported
* @private
* @memberof oj.editableValue
* @instance
*
*/
_addAriaDescribedByAndReturnId : function (helpSource, helpDef, required, ariaRequiredUnsupported)
{
// generate a unique id if one does not exist, then get the id and use it to
// create an id that will be set on the label help icon wrapper
var labelHelpIconWrapperId = this._getAriaDescribedByIconWrapperId();
if (helpSource || helpDef || (required && ariaRequiredUnsupported))
{
this._addAriaDescribedBy(labelHelpIconWrapperId);
}
return labelHelpIconWrapperId;
},
/**
* Add the aria-describedby on the input.
*
* @param {string} ariaDescribedById the id for aria-describedby
* @private
* @memberof oj.editableValue
* @instance
*
*/
_addAriaDescribedBy : function (ariaDescribedById)
{
var contentElements = this._GetContentElement();
// aria-describedby can be a list of ids, so make sure to append
//contentElements.attr("aria-describedby", labelHelpIconWrapperId);
contentElements.each(function() {
var ariaDescBy = $(this).attr("aria-describedby");
if (ariaDescBy)
$(this).attr("aria-describedby", ariaDescBy + ' ' + ariaDescribedById);
else
$(this).attr("aria-describedby", ariaDescribedById);
});
},
/**
* Returns a concat of messagesShown and messagesHidden.
*
* @returns {Array}
* @private
* @memberof oj.editableValue
* @instance
*/
_getMessages : function()
{
return this.options['messagesShown'].concat(this.options['messagesHidden']); // todo: revisit
},
// helper method to retrieve the label text.
_getLabelText : function ()
{
if (this.$label)
{
return this.$label.text();
}
},
_getValidityState : function ()
{
if (this._validityState)
{
return this._validityState;
}
else
{
this._validityState = new oj.ComponentValidity(this.isValid(), this._getMessages());
}
return this._validityState;
},
/**
* Whether component has invalid messages.
*
* @return {boolean} true if invalid; false otherwise
* @private
* @memberof oj.editableValue
* @instance
*/
_hasInvalidMessages : function ()
{
return !oj.Message.isValid(this._getMessages());
},
/**
* Whether there are invalid messages, that are currently showing.
*
* @return {boolean}
* @private
* @memberof oj.editableValue
* @instance
*/
_hasInvalidMessagesShowing : function ()
{
return (!this.isValid() && this.options['messagesShown'].length > 0);
},
/**
* Whether component has invalid messages added by component, that are currently showing.
*
* @return {boolean}
* @private
* @memberof oj.editableValue
* @instance
*/
_hasInvalidComponentMessagesShowing : function ()
{
var shown = this.options['messagesShown'], msg, compMsgs;
for (var i = 0; i < shown.length; i++)
{
msg = shown[i];
if (msg instanceof oj.ComponentMessage && msg._isMessageAddedByComponent())
{
compMsgs = compMsgs || [];
compMsgs.push(msg);
}
}
return (compMsgs === undefined) ? false : !oj.Message.isValid(compMsgs);
},
/**
* Initializes component messaging both when component is initialized or when displayOptions is
* set/changed.
*
* @private
* @memberof oj.editableValue
* @instance
*/
_initComponentMessaging : function()
{
var compMessaging = this._getComponentMessaging(),
messagingLauncher = this._GetMessagingLauncherElement(),
messagingContent = this._getMessagingContent(this._MESSAGING_CONTENT_UPDATE_TYPE.ALL);
// if default placeholder is currently set then it needs to be cleared here. This is needed for
// the following reasons
// i. a component is reinitialized when the locale changed, requiring the converter hint for
// new locale to be set as placeholder.
// ii. or a component's placeholder option or displayOptions option, could have changed
// requiring the placeholder to be reset if it's currently set to the default.
//
if (!this._customPlaceholderSet)
{
this._ClearPlaceholder();
}
compMessaging.activate(messagingLauncher, messagingContent);
},
/**
* Called after messagesCustom option changed. This method pushes custom messages to the
* messagesShown option.
*
* @param {Object} flags
* @returns {undefined}
* @private
*/
_messagesCustomOptionChanged : function (flags)
{
var customMsgs = this.options['messagesCustom'], shownMsgs = [], msg, i,
previousShown = this.options['messagesShown'], context = flags ? flags['_context'] : null;
// remove old custom messages from messagesShown array
for (i = 0; i < previousShown.length; i++)
{
msg = previousShown[i];
if (msg instanceof oj.ComponentMessage && msg._isMessageAddedByComponent())
{
shownMsgs.push(msg);
}
}
// add new customMsgs to messagesShown
for (i = 0; i < customMsgs.length; i++)
{
shownMsgs.push(customMsgs[i]);
}
// set 'messagesShown' option as an internal set
this._setMessagesOption('messagesShown', shownMsgs,
context ? context.originalEvent : null,
flags && flags['changed']);
},
_placeholderOptionChanged : function (flags)
{
var context = flags && flags['_context'] || {},
refreshMessagingOptions =
// internalMessagingSet indicates whether the current change is from the messaging module.
// see ComponentMessaging for details
context.internalMessagingSet ? false : true,
value = this.options['placeholder'];
this._SetPlaceholder(value);
if (refreshMessagingOptions)
{
// if placeholder was set and it's not from messaging code, then the messaging display options
// may need to re-evaluated. E.g., the default display for
// converterHint: ['placeholder', 'notewindow'] is 'placeholder', but if user were to set a
// custom placeholder, this changes the default display for convererHint from 'placeholder'
// to 'notewindow'.
this._customPlaceholderSet = true;
if (this._GetConverter())
{
this._initComponentMessaging();
}
}
else
{
this._customPlaceholderSet = false;
}
},
_setLastModelValue : function (value)
{
this._oj_lastModelValue = value;
},
_setLastSubmittedValue : function (value)
{
this._oj_lastElementValue = value;
},
_titleOptionChanged : function ()
{
// when title changes push new title to messaging
this._getComponentMessaging().update(
this._getMessagingContent(this._MESSAGING_CONTENT_UPDATE_TYPE.TITLE));
},
/**
* Adds messages to the option specified - <code class="prettyprint">messagesShown</code> or
* <code class="prettyprint">messagesHidden</code>.
*
* @param {String} option name of the option
* @param {Object|Array} newMsgs an Array of one or more oj.Message object.
* @param {Event=} event
* @private
* @memberof oj.editableValue
* @instance
*/
_updateMessagesOption : function(option, newMsgs, event)
{
var len, i, msgs;
if (typeof newMsgs === "object" && Array.isArray(newMsgs))
{
msgs = this.options[option];
len = newMsgs.length;
for (i = 0; i < len; i++)
{
msgs.push(newMsgs[i]);
}
}
this._setMessagesOption(option, msgs, event, true);
},
/**
* Called after the messages option has changed to update internal valid property and messaging
* display.
*
* @private
*/
_updateMessagingContent : function()
{
// update component messaging
this._getComponentMessaging().update(this._getMessagingContent());
},
/**
* Writes the value into the option by calling the option method.
*
* @param {Object|string} newValue the new value to be written to option
* @param {Object=} event the original event that triggered this
* @param {number=} validationContext the context in which validation was run that resulted in
* value being updated.
*
* @private
* @memberof oj.editableValue
* @instance
* @see #_setOption
* @see #_AfterSetOptionValue
*/
_updateValueOption : function (newValue, event, validationContext)
{
var context = {};
// set dom event
if (event)
{
context.originalEvent = event;
}
// set writeback flag that determines whether value is written back.
switch (validationContext)
{
// value is written back outside of normal UI interaction in the following cases.
case this._VALIDATION_CONTEXT.CONVERTER_OPTION_CHANGE:
case this._VALIDATION_CONTEXT.DISABLED_OPTION_CHANGE:
case this._VALIDATION_CONTEXT.READONLY_OPTION_CHANGE:
case this._VALIDATION_CONTEXT.REFRESH_METHOD:
case this._VALIDATION_CONTEXT.REQUIRED_OPTION_CHANGE:
case this._VALIDATION_CONTEXT.VALIDATE_METHOD:
case this._VALIDATION_CONTEXT.VALIDATORS_OPTION_CHANGE:
context.writeback = true;
// when the above options change or methods are called, and full validation is run the
// current display value is parsed, and set on the value option if all validations pass.
// Typically when the value option changes - either programmatically or user changes it, we
// end up clearing all 3 messages options.
// But when value changes indirectly as a result of the above cases, then we do not clear
// custom messages. So a special flag is set so _AfterSetOptionValue can do the right
// thing. Component messages are already cleared when this method is called.
context.doNotClearMessages = true;
break;
}
this.option({'value': newValue}, {'_context': context});
},
/**
* Resets the internal property so that the next call to this.isValid() re-evaluates the correct
* value.
*
* @private
* @memberof oj.editableValue
* @instance
*/
_resetValid : function ()
{
this._valid = undefined;
},
/**
* Determines the validity of component based on current value of the messages* options.
*
* @return {boolean} true if valid, false otherwise.
*
* @private
* @memberof oj.editableValue
* @instance
*/
_getValid : function ()
{
var msgs = this._getMessages(), hasMessages, valid = true;
// When new messages are written update valid
hasMessages = msgs && msgs.length !== 0;
if (hasMessages)
{
valid = !this._hasInvalidMessages();
}
return valid;
},
/**
* Formats the value for display, based on the converter options. If no converter is set then
* returns the value as is.
*
* @param {string} value value to be formatted
*
* @return {string} formatted value
* @throws {Error} when an error occurs during formatting
* @protected
* @memberof oj.editableValue
* @instance
*/
_formatValue : function (value)
{
var formattedValue = value, converter = this._GetConverter();
// don't clear messages here because we clear messages only when direct user interaction with
// component changes value. All other usecases we expect page authors to clear messages.
if (converter)
{
// TODO: We should support chaining converters but for now we use just the first converter
// to parse.
// Check if we have a converter instance
if (typeof converter === "object")
{
if (converter['format'] && typeof converter['format'] === "function")
{
formattedValue = converter['format'](value);
}
else
{
if (oj.Logger.level > oj.Logger.LEVEL_WARN)
{
oj.Logger.info("converter does not support the format method.");
}
}
}
}
return formattedValue;
},
/**
* @private
* @memberof oj.editableValue
* @instance
*/
_getComponentMessaging : function ()
{
if (!this._componentMessaging)
{
this._componentMessaging = new oj.ComponentMessaging(this);
}
return this._componentMessaging;
},
/**
* Returns an array of validator hints.
* @param {Array} allValidators
* @private
* @memberof oj.editableValue
* @instance
*/
_getHintsForAllValidators : function(allValidators)
{
var validator, validatorHints = [], vHint = "", i;
if (this._IsRequired())
{
// get the hint for the default required validator and push into array if it's not already
// present in the validators array
validator = this._getImplicitRequiredValidator();
if (validator['getHint'] && typeof validator['getHint'] === "function")
{
vHint = validator['getHint']();
if (vHint)
{
validatorHints.push(vHint);
}
}
}
// loop through all remaining validators to gather hints
for (i = 0; i < allValidators.length; i++)
{
validator = allValidators[i], vHint = "";
if (typeof validator === "object")
{
if (validator['getHint'] && typeof validator['getHint'] === "function")
{
vHint = validator['getHint']();
if (vHint)
{
validatorHints.push(vHint);
}
}
}
}
return validatorHints;
},
/**
* Returns the required validator instance or creates it if needed and caches it.
*
* @private
* @memberof oj.editableValue
* @instance
*/
_getImplicitRequiredValidator : function ()
{
var vf, reqTrans = {}, reqValOptions;
if (this._implicitReqValidator == null) // falsey check
{
reqTrans =
this.options['translations'] ? this.options['translations']['required'] || {} : {} ;
// TODO: cache required validator; purged when its options change, i.e., translations or label
// DOM changes
reqValOptions = {
'hint': reqTrans['hint'] || null,
'label': this._getLabelText(),
'messageSummary': reqTrans['messageSummary'] || null,
'messageDetail': reqTrans['messageDetail'] || null
};
vf = oj.Validation.validatorFactory(oj.ValidatorFactory.VALIDATOR_TYPE_REQUIRED);
this._implicitReqValidator = vf ? vf.createValidator(reqValOptions) : null;
}
return this._implicitReqValidator;
},
/**
* Returns content that will be used by messaging strategies.
*
* @param {number} updateType of messaging content to update. Accepted values are defined by
* this._MESSAGING_CONTENT_UPDATE_TYPE.
*
* @private
* @memberof oj.editableValue
* @instance
*/
_getMessagingContent : function (updateType)
{
var messagingContent = {}, converter = this._GetConverter(), converterHint = "", allValidators,
validatorHints = [], messages;
updateType = updateType || this._MESSAGING_CONTENT_UPDATE_TYPE.VALIDITY_STATE;
// Add validityState which includes messages, valid and severity
if (updateType === this._MESSAGING_CONTENT_UPDATE_TYPE.ALL ||
updateType === this._MESSAGING_CONTENT_UPDATE_TYPE.VALIDITY_STATE)
{
messages = this._getMessages();
// update validityState before packaging it
this._getValidityState().update(this.isValid(), messages);
messagingContent.validityState = this._getValidityState();
}
if (updateType === this._MESSAGING_CONTENT_UPDATE_TYPE.ALL ||
updateType === this._MESSAGING_CONTENT_UPDATE_TYPE.CONVERTER_HINT)
{
if (converter)
{
if (typeof converter === "object")
{
if (converter['getHint'] && typeof converter['getHint'] === "function")
{
converterHint = converter['getHint']() || "";
}
}
}
messagingContent.converterHint = converterHint;
}
if (updateType === this._MESSAGING_CONTENT_UPDATE_TYPE.ALL ||
updateType === this._MESSAGING_CONTENT_UPDATE_TYPE.VALIDATOR_HINTS)
{
allValidators = this._GetAllValidators();
validatorHints = this._getHintsForAllValidators(allValidators) || [];
messagingContent.validatorHint = validatorHints;
}
if (updateType === this._MESSAGING_CONTENT_UPDATE_TYPE.ALL ||
updateType === this._MESSAGING_CONTENT_UPDATE_TYPE.TITLE)
{
messagingContent.title = this.options['title'] || "";
}
return messagingContent;
},
/**
* Compares the messages arrays for equality.
*
* @param {Array} pm previous messages
* @param {Array} m new messages
* @returns {boolean} true if equal false otherwise
* @private
* @memberof oj.editableValue
* @instance
*
*/
_messagesEquals : function (pm, m)
{
var match = -1, pmo, passed = true, previousMsgs = $.extend([], pm), msgs = $.extend([], m);
if (previousMsgs.length !== msgs.length)
{
return false;
}
$.each(previousMsgs, function (i, pMsg)
{
if (!(pMsg instanceof oj.Message))
{
// freeze message instance once its created
pmo = new oj.Message(pMsg['summary'], pMsg['detail'], pMsg['severity']);
pmo = Object.freeze ? Object.freeze(pmo) : pmo;
}
else
{
pmo = pMsg;
}
match = -1;
$.each(msgs, function(j, msg) {
{
if (pmo.equals(msg))
{
match = j;
return; // found a match, so break out of loop
}
}
});
// remove entry at 'match' index from msgs
if (match > -1)
{
msgs.splice(match, 1);
}
else
{
// we found no match so no need to loop
passed = false;
return;
}
});
return passed;
},
/**
* Parses the value using the converter set and returns the parsed value. If parsing fails the
* error is written into the element
*
* @param {string=} submittedValue to parse
* @return {Object} parsed value
* @throws {Error} an Object with message
* @protected
* @memberof oj.editableValue
* @instance
*/
_parseValue: function(submittedValue)
{
var converter = this._GetConverter(), parsedValue = submittedValue;
if (converter)
{
// TODO: We should support chaining converters but for now we use just the first converter
// to parse.
if (typeof converter === "object")
{
if (converter['parse'] && typeof converter['parse'] === "function")
{
// we are dealing with a converter instance
parsedValue = converter['parse'](submittedValue);
}
else
{
if (oj.Logger.level > oj.Logger.LEVEL_WARN)
{
oj.Logger.info("converter does not support the parse method.");
}
}
}
}
return parsedValue;
},
_addValidationError : function(e, msgs)
{
var ojmessage, summary, detail, severity;
if (e instanceof oj.ConverterError || e instanceof oj.ValidatorError)
{
ojmessage = e.getMessage();
oj.Assert.assertPrototype(ojmessage, oj.Message);
severity = ojmessage['severity'];
summary = ojmessage['summary'];
detail = ojmessage['detail'];
}
else
{
// TODO: is this error message generic enough to use for both converter and validator errors?
severity = oj.Message.SEVERITY_LEVEL['ERROR'];
summary = oj.Translations.getTranslatedString("oj-message.error");
detail = e['message'] || oj.Translations.getTranslatedString("oj-converter.detail");
}
msgs.push({summary: summary, detail: detail, severity: severity});
},
/**
* Processes the error information for one or more errors and returns an Array of
* oj.ComponentMessage instances.
*
* @param {Error} e instance of Error
* @param {number=} context the context in which the validation error was thrown
* @param {String=} display whether message is shown or hidden
* @private
* @memberof oj.editableValue
* @instance
*/
_processValidationErrors : function (e, context, display)
{
var msg, options = {}, componentMsgs = [], msgs = e._messages || [];
options['context'] = context || 0;
options['display'] = display || oj.ComponentMessage.DISPLAY.SHOWN;
if (msgs.length === 0)
{
this._addValidationError(e, msgs);
}
for (var i = 0; i < msgs.length; i++)
{
msg = msgs[i];
componentMsgs.push(this._createComponentMessage(msg.summary, msg.detail, msg.severity, options));
}
return componentMsgs || null;
},
_createComponentMessage : function (summary, detail, severity, options)
{
var cMsg;
// new properties can't be added but existing properties can be changed
cMsg = new oj.ComponentMessage(summary, detail, severity, options);
cMsg = Object.seal ? Object.seal(cMsg) : cMsg;
return cMsg;
},
/**
* Refreshes the component display value, only when the current value is different from the last
* saved value, unless asked to always refresh the display value.
*
* @param {Object|undefined} value the changed value that needs to be updated on UI
* @param {boolean=} fullRefresh false is the default; true means always refresh component
* display value using the current option value. This overwrites any UI value, the user may have
* entered.
*
* @private
* @memberof oj.editableValue
* @instance
*/
_refreshComponentDisplayValue : function (value, fullRefresh)
{
var modelValue = value || this.options['value'], lastModelValue, shouldUpdateElementValue;
lastModelValue = this._getLastModelValue();
shouldUpdateElementValue = fullRefresh || (modelValue !== lastModelValue) || false;
if (shouldUpdateElementValue)
{
this._updateElementDisplayValue(modelValue);
}
},
/**
* Toggles css selector on the widget. E.g., when required option changes, the oj-required
* selector needs to be toggled.
* @param {string} option
* @param {Object|string} value
* @private
* @memberof oj.editableValue
* @instance
*/
_refreshTheming : function (option, value)
{
if (Object.keys(this._OPTION_TO_CSS_MAPPING).indexOf(option) !== -1)
{
this.widget().toggleClass(this._OPTION_TO_CSS_MAPPING[option], !!value);
}
},
/**
* Runs validators in deferred mode using the option value. Any validation error thrown is
* deferred, or hidden by component, until explicitly asked to show them (see showMessages()).
* Deferred error is pushed to <code class="prettyprint">messagesHidden</code> option.
*
* @param {number} context in which validation was run.
*
* @see #showMessages
* @private
* @memberof oj.editableValue
* @instance
*/
_runDeferredValidation : function (context)
{
var newValue, newMsgs;
if (this._CanSetValue())
{
try
{
// TODO: Today required is the only validator that runs deferred. We need a generic way to
// retrieve deferred validators.
newValue = this._validateValue(this.options['value'],
this._VALIDATION_MODE.REQUIRED_VALIDATOR_ONLY);
}
catch (ve)
{
newMsgs = this._processValidationErrors(ve, context, oj.ComponentMessage.DISPLAY.HIDDEN);
if (newMsgs)
{
this._updateMessagesOption('messagesHidden', newMsgs);
}
}
}
else
{
if (oj.Logger.level > oj.Logger.LEVEL_WARN)
{
oj.Logger.info("Deferred validation skipped as component is readonly or disabled.");
}
}
},
/**
* Runs validation based on the mode settings.
*
* @param {Object} value to parse and/or validate
* @param {number=} mode determines how validation is run. see _VALIDATION_MODE
* @param {number=} context determines when validation is being run.
* @param {Event=} event the original event or undefined
*
* @return {Object|string} parsed value
* @throws {Error} when validation fails.
*
* @private
* @memberof oj.editableValue
* @instance
*/
_runNormalValidation : function (value, mode, context, event)
{
var newValue = value, newMsgs;
// callers of this function should clear messages
try
{
// Step 1: only when "full" validation is requested converters get run
if (mode === this._VALIDATION_MODE.FULL)
{
// Step1: Parse value using converter
newValue = this._parseValue(value);
}
// Step 2: Run validators
this._validateValue(newValue, mode === this._VALIDATION_MODE.REQUIRED_VALIDATOR_ONLY);
}
catch (ve)
{
newMsgs = this._processValidationErrors(ve, context);
this._updateMessagesOption ('messagesShown', newMsgs, event);
throw ve;
}
return newValue;
},
/**
* Runs either deferred or normal validation based on the state component is in. This method is
* called when certain options change - required, disabled etc.
*
* <p>
* - if component is invalid and has messgesShown -> required: false/true -> clear component errors;
* run full validation with UI value (we don't know if the UI error is from a required validator
* or something else);<br/>
* - if there are validation errors, then value not pushed to model; messagesShown is
* updated<br/>
* - if no errors result from the validation, push value to model; author needs to
* listen to optionChange(value) to clear custom errors.<br/>
*
* - if component is invalid and has messagesHidden -> required: false -> clear component
* errors; no deferred validation is run.<br/>
* - if component has no error -> required: true -> run deferred validation (we don't want to flag
* errors unnecessarily)<br/>
* - messagesCustom is never cleared<br/>
* </p>
*
* @param {Object} validationOptions
*
* @private
* @memberof oj.editableValue
* @instance
*/
_runMixedValidationAfterSetOption : function (validationOptions)
{
var runDeferredValidation = true;
if (this._hasInvalidMessagesShowing())
{
runDeferredValidation = false;
}
// clear component messages always
this._clearComponentMessages();
if (!runDeferredValidation)
{
// run full validation using the current display value;
// show messages immediately if there are errors otherwise set value to option
this._updateValue(validationOptions);
}
else
{
// run deferred validation if comp is either showing a deferred error or has no errors.
// But only when required is true.
if (this._IsRequired())
{
this._runDeferredValidation(validationOptions.validationContext);
}
}
},
_updateElementDisplayValue : function (modelValue, event)
{
var displayValue, newMsgs, actualDisplayValue;
// cache the new model value on the element
this._setLastModelValue(modelValue);
// Update element with the displayValue
displayValue = modelValue;
try
{
displayValue = this._formatValue(modelValue);
}
catch (e)
{
newMsgs = this._processValidationErrors(e);
this._updateMessagesOption ('messagesShown', newMsgs, event);
}
this._SetDisplayValue(displayValue);
// getting the display value right after we set it is probably not necessary, but just in
// case a subclass did something to it, we do.
actualDisplayValue = this._GetDisplayValue();
this._setLastSubmittedValue(actualDisplayValue);
// update rawValue option to keep it in sync with the display value
this._SetRawValue(null, actualDisplayValue);
},
/**
* Runs normal validation when certain options change - converter, required, validators etc. - and
* updates (re-sets) the value if no new errors are found. This is different from _SetValue()
* which is called when end-user changes the value as a result of interacting with the component.
* Additionally _SetValue() clears all messages always, whereas callers of this method determine
* which messages they want cleared.
*
* @param {Object} options - name value properties used when validation is run.
* @private
* @memberof oj.editableValue
* @instance
*/
_updateValue : function (options)
{
var newValue;
// run full validation using the current display value;
// show messages immediately if there are errors otherwise set value to option
newValue = this._Validate(this._GetDisplayValue(), null, options);
// if validation was a success and component is valid, or it's invalid with no new component
// messages then update value option.
// NOTE: often custom msgs are not cleared when this method is called.
if (newValue !== undefined &&
(this.isValid() || !this._hasInvalidComponentMessagesShowing()))
{
this._updateValueOption(newValue, null, options.validationContext);
}
},
/**
* Validates the value by running through the list of regsitered validators. The algorithm is as
* follows -
* 1. check to see if we are currently valid, if not return
* 2. run required check.
* 3. if value is not empty get all the validators and validate in sequence.
* 4. if all validators pass return.
*
* Callers can rely on the 'valid' options property to determine the validity state of the
* component after calling this method
*
* @param {Object|string} value to be validated
* @param {boolean} requiredOnly true only runs required validation
* @throws {Error} when validation fails.
* @private
* @memberof oj.editableValue
* @instance
*/
_validateValue : function (value, requiredOnly)
{
var allValidators = this._GetAllValidators(), validator, i, valMsgs = [];
// run required validation first;
if (this._IsRequired())
{
validator = this._getImplicitRequiredValidator();
try
{
// check if trimmed value is empty. See AdfUIEditableValue.prototype.ValidateValue
validator.validate(oj.StringUtils.trim(value));
}
catch (e)
{
// save all validation errors
this._addValidationError(e, valMsgs);
}
}
// run other validators when requested.
// latest review - we want to do exactly as ADF does and it validates empty field values by
// default. The condition always resolves to true in JET!
if (!requiredOnly) // && (!isEmptyValue || this._shouldValidateEmptyFields()))
{
for (i = 0; i < allValidators.length; i++)
{
validator = allValidators[i];
if (typeof validator === "object")
{
if (validator['validate'] && typeof validator['validate'] === "function")
{
try
{
validator['validate'](value);
}
catch (e)
{
// save all validation errors
this._addValidationError(e, valMsgs);
}
}
else
{
if (oj.Logger.level > oj.Logger.LEVEL_WARN)
{
oj.Logger.info("validator does not support the validate method.");
}
}
}
}
}
// throw error if there were validation failures
if (valMsgs.length > 0)
{
var ve = new Error();
ve._messages = valMsgs;
throw ve;
}
}
// Fragments:
/**
* <tr>
* <td rowspan="2">Label's help icon</td>
* <td><kbd>Tap</kbd></td>
* <td>Go the Help Source URL.</td>
* </tr>
* <tr>
* <td><kbd>Press & Hold</kbd></td>
* <td>Show help definition text.</td>
* </tr>
* @ojfragment labelTouchDoc - Used in touch gesture section of classdesc, and standalone gesture doc
* @memberof oj.editableValue
*/
/**
* <tr>
* <td rowspan="2">Label's help icon</td>
* <td><kbd>Enter</kbd></td>
* <td>Go the Help Source URL.</td>
* </tr>
* <tr>
* <td><kbd>Tab In</kbd></td>
* <td>Show help definition text.</td>
* </tr>
* @ojfragment labelKeyboardDoc - Used in touch gesture section of classdesc, and standalone gesture doc
* @memberof oj.editableValue
*/
}, true);
oj.Components.setDefaultOptions(
{
// We used to use oj.Components.createDynamicPropertyGetter, but we don't need the 'context' yet,
// so we switched it to this simplest code so that overriding displayOptions
// for defaultOptions is easier. We only need to define what we want to override, not
// re-define all the sub-options of displayOptions.
// See Bug 20441549 - allow multiple setdefaultoptions calls on properties with dynamic getter.
'editableValue': // properties for all editableValue components
{
'displayOptions': {
'messages': ['inline'],
'converterHint': ['placeholder', 'notewindow'],
'validatorHint': ['notewindow'],
'title': ['notewindow'] }
}
}
);
Source: src/main/javascript/oracle/oj/ojeditablevalue/EditableValue.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01