/**
* Copyright (c) 2014, Oracle and/or its affiliates.
* All rights reserved.
*/
/**
* @class JET Editable Component Utils
* @export
* @since 0.6
*
*/
oj.EditableValueUtils = {};
/**
* This method is called during _InitOptions() to initialize a component option value from DOM. This
* uusally is the case when the option value is undefined,
* i.e., this.options.optionName === undefined.
* <br/>
* Returns the attribute value for the given attribute on the element appropriately converted, or
* the default, if the attribute isn't set on the element.<br/>
*
* @param {Object} element the element the component is initialized with.
* @param {string} attribute the name of the element's attribtue. Example, value, disabled etc.
*
* @returns {Object} a JSON object containing the following properties - <br/>
* <ul>
* <li><b>fromDom</b> - whether the option value was initialized from DOM. When true the option's
* value is written back (to observable).</li>
* <li><b>value</b> - the option value. the attribute value or the default if the attribute isn't
* set on the element.</li>
* </ul>
*
* @private
*/
oj.EditableValueUtils.getAttributeValue = function (element, attribute)
{
var result, attrVal, propVal, returnVal = {};
if (element && attribute)
{
switch (attribute)
{
case "disabled" :
result = element.attr("disabled") !== undefined ?
!!element.prop("disabled") : undefined;
break;
case "pattern":
result = element.prop("pattern") || undefined;
break;
case "placeholder":
result = element.prop("placeholder") || undefined;
break;
case "readonly":
result = element.attr("readonly") !== undefined ?
!!element.prop("readonly") : undefined;
break;
case "required":
attrVal = element.attr("required");
propVal = element.prop("required");
// If attribute is present and not undefined
// - In IE9, required attribute is not supported at all, so attr() is defined, prop() is
// undefined. In such cases set default to !!attrVal
// - Otherwise set to !!propVal
// If neither attr is undefined then use defaultValue
// TODO: review needed
result = (attrVal !== undefined) ?
((propVal !== undefined) ? !!propVal : !!attrVal) : undefined;
break;
case "title":
result = element.attr("title") !== undefined ?
element.prop("title") : undefined;
break;
case "value":
// element attribute may not be set, in which case default to null
result = element.val() || undefined;
break;
case "min":
//same logic for min + max as in default
case "max":
default:
result = element.attr(attribute) || undefined;
break;
}
}
if (result !== undefined)
{
returnVal.fromDom = true;
returnVal.value = result;
}
else
{
returnVal.fromDom = false;
// returnVal.value = defaultValue;
}
return returnVal;
};
/**
* Called from component._InitOptions(), when a component might need to initialize its options from
* DOM. This usually is done when the constructor option is undefined, even in the case where
* this.options.option has a value that comes from app defaults or component (original) default.
* See below for details.
* <p>
* IMPORTANT: Do not call this method after component has been created, since option values are
* mutated directly after that point.</p>
*
* The 'final' value an option uses/initializes from, can come from these places (in order of least
* to most likely) - <br/>
* <ol>
* <li>component default - this is the widget default </li><br/>
* <li>app default - this is what a page author defines for the value in the page/app</li> <br/>
* <li>dom value - if your option also has a dom attribute, this is the value set on element for
* component. </li> <br/>
* <li>constructor value - this is the value page author sets on the component binding </li><br/>
* </ol>
*
* At the time _InitOptions is called, (1), (2) and (4) are merged, but this may not be the value a
* component wants for the option, especially when (4) is undefined. For example, if these values
* were set for a component - <br/>
* (1) - 'foo'<br/>
* (2) - 'bar'<br/>
* (3) - 'lucy'<br/>
* (4) - undefined<br/>
* <p>
* at the time _InitOptions is called, this.options.option is set to 'bar', but because DOM value
* wins over app default or component default, component needs to check if the constructor value was
* undefined and if so, set option to 'lucy'. <br/>
* If the dom value is not set, then the component provided default value - defaultOptionValue gets
* used. This method always defaults the value to be - this.options.option || defaultOptionValue -
* because we think if neither (3) nor (4) is set, then the value from (2) should win over the
* defaultOptionValue. <br/>
* </p>
*
* @param {Object} props an Object literal that a component provides with the following properties
* that helps determine the final value for one or more options.
*
* @property {string} props.attribute - name of DOM attribute
* @property {string|undefined} props.option - name of the option if different from attribute name.
* @property {Object|null|string|number|boolean} props.defaultOptionValue - the default value to use
* for the option when the DOM value is not set. For example, editable components bound to inputs,
* would pass false as the default for disabled option, while components like the *set components
* would pass null, because they support a tri-state.
*
* @property {Function|boolean|undefined} props.coerceDomValue - if the DOM value is set and
* coercing the dom value is needed, then either set to boolean true, which uses the default
* coercion rules for common attributes (a), or provide a custom callback (b). <p>
* E.g., 'value' option for input number, input date etc. have special rules for coercing the value,
* so thse provide a custom callback. For common attributes like required and disabled, set the
* value to true so the default oj.EditableValueUtils#coerceDomValueForOption method gets used.
*
* @property {boolean|undefined} props.validateOption - if set to true, then it calls
* oj.EditableValueUtils.validateValueForOption method to validate the option.
*
* @param {Object} constructorOptions the options set on the component instance, often using
* component binding.
* @param {Object} comp component instance.
* @param {Function=} postprocessCallback - optional callback that will receive a map of initialized
* options for post-processing
*
* @public
*/
oj.EditableValueUtils.initializeOptionsFromDom = function (props, constructorOptions, comp, postprocessCallback)
{
var initializedOptions = {};
// Loop through props to initialize option
for (var i = 0; i < props.length; i++)
{
var finalValue, result, prop = props[i],
attribute = prop.attribute,
option = prop.option || attribute,
coerceDomValue = prop.coerceDomValue,
validateOption = prop.validateOption,
element = comp.element,
previousValue = comp.options[option];
/* The precedence for the value that an option uses is as follows from lowest to highest -
*
* (1) component default - this is the widget default
* (2) app default - this is what a page author defines for the value in the page / app
* (3) dom value - if your option also has a dom attribute, this is the value set on element.
* (4) constructor value - this is the value page author sets on the component binding
*
* When (4) is undefined then attempt to default from (3). But if (3) is undefined use a
* defaultValue that component determines. If constructor option was not set then default
* value is the merged option value (i.e., this.options) || a hard-coded default determined
* by component.
*/
// Step 1: use DOM value
if (constructorOptions[option] === undefined)
{
previousValue = comp.options[option];
result = oj.EditableValueUtils.getAttributeValue(element, attribute);
// if we are using domValue then coerce the dom value before writing to options and trigegr
// option change so the value is written back (to ko)
if (result.fromDom)
{
finalValue = result.value;
// only required needs coercing so not bad
if (coerceDomValue)
{
if (typeof coerceDomValue === "boolean")
{
finalValue = oj.EditableValueUtils.coerceDomValueForOption(option, finalValue);
}
else if (typeof coerceDomValue === "function")
{
finalValue = coerceDomValue.call(comp, finalValue);
}
}
initializedOptions[option] = finalValue;
}
else
{
// hmm, no dom value found; if comp.options also happens to be undefined then simply use
// the default option value.
if (previousValue === undefined)
{
initializedOptions[option] = prop.defaultOptionValue;
}
}
}
var valueToValidate = (option in initializedOptions) ? initializedOptions[option] : previousValue;
// Step 2: validate the option value if needed
if (validateOption)
{
if (typeof validateOption === "boolean")
{
oj.EditableValueUtils.validateValueForOption(option, valueToValidate);
}
}
}
if (postprocessCallback != null)
{
postprocessCallback(initializedOptions);
}
comp.option(initializedOptions, {'_context': {writeback: true, internalSet: true}});
};
/**
* Validates value set for the option and throws error if invalid.
*
* @param {string} option name of the option. Validates options common to all edtiableValue
* holders.
* @param {string|Object|boolean|number|undefined} value of the option that is validated
*
* @throws {Error} if option value is invalid
* @public
*/
oj.EditableValueUtils.validateValueForOption = function (option, value)
{
var error = false;
switch (option)
{
case 'required' :
if (value !== null && typeof value !== "boolean")
{
error = true;
}
break;
case "readOnly":
case "disabled" :
if (value !== null && typeof value !== "boolean")
{
error = true;
}
break;
}
if (error)
{
throw "Option '" + option + "' has invalid value set: " + value;
}
};
/**
* Coerces the dom value being used for the option, and throws error if invalid.
*
* @param {string} option name of the option.
* @param {string|Object|boolean|number|null} domValue dom value that is being coerced to the
* option value
* @throws {Error} if domValue cannot be coerced appropriately
* @public
*/
oj.EditableValueUtils.coerceDomValueForOption = function(option, domValue)
{
var coerced = domValue;
switch (option)
{
case 'required' :
coerced = domValue ? true : false;
break;
}
return coerced;
};
Source: src/main/javascript/oracle/oj/ojeditablevalue/EditableValueUtils.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01