/**
* Copyright (c) 2014, Oracle and/or its affiliates.
* All rights reserved.
*/
/*jslint browser: true, devel: true*/
// private to prevent creating a JSDoc page for this class. The only thing we wish
// to JSDoc is the invalidComponentTracker, which we're putting in EditableValue's output.
/**
* An extension to oj.ComponentBinding, properties exposed on this binding are available
* to jet components that extend from oj.editableValue.
*
* @private
* @constructor oj.ValueBinding
* @see oj.ComponentBinding
* @see oj.editableValue
* @since 0.6
*/
oj.ValueBinding = function(){};
/**
* <p>When this attribute is bound to an observable, the framework pushes an object of type {@link oj.InvalidComponentTracker}
* onto the observable. The object itself tracks the validity of a group of editable components.
*
* <p>When this attribute is present, the binding registers a listener for the <a href="#optionChange">optionChange</a>
* event. This event is fired by JET editable components whenever its validity changes (i.e. when
* <a href="#messagesShown">messagesShown</a> or <a href="#messagesHidden">messagesHidden</a>
* options change). When the event is fired, the listener determines the current validity of the
* component and updates the tracker.
*
* <p>
* The observable bound to this attribute is often used with multiple component binding declarations
* as shown in the example below.
* </p>
*
* <p>
* This attribute is only exposed via the <code class="prettyprint">ojComponent</code> binding, and
* is not a component option.
* </p>
*
* @example <caption>Track validity of multiple components using a single observable bound to the <code class="prettyprint">invalidComponentTracker</code> attribute:</caption>
* <input id="username" type="text" name="username" required
* data-bind="ojComponent: {component: 'ojInputText', value: userName,
* invalidComponentTracker: tracker}">
*
* <input id="password" type="password" name="password" required
* data-bind="ojComponent: {component: 'ojInputPassword', value: password,
* invalidComponentTracker: tracker}"/>
*
* // ViewModel that defines the tracker observable
* <script>
* function MemberViewModel()
* {
* var self = this;
*
* self.tracker = ko.observable();
*
* self.userName = ko.observable();
* self.password = ko.observable();
* }
* </script>
*
* @example <caption>Use tracker property <code class="prettyprint">invalid</code> to disable button:</caption>
* // button is disabled if there are components currently showing errors
* <button type="button" data-bind="ojComponent: {component: 'ojButton', label: 'Submit',
* disabled: tracker()['invalidShown']}"></button>
*
* @ojbindingonly
* @member
* @name invalidComponentTracker
* @memberof oj.editableValue
* @instance
* @type {oj.InvalidComponentTracker}
* @default <code class="prettyprint">null</code>
* @since 0.7
*/
/** prevent preceding jsdoc from applying to following line of code */
oj.ValueBinding._ATTRIBUTE_INVALID_COMPONENT_TRACKER = 'invalidComponentTracker';
// An listener is added for this event to listen to changes to the 'messagesHidden' or
// 'messagesShown' options. The listener updates the InvalidComponentTracker.
oj.ValueBinding._EVENT_OPTIONCHANGE = "ojoptionchange";
// Options we are interested in listening to changes for.
oj.ValueBinding._OPTION_MESSAGES_SHOWN = 'messagesShown';
oj.ValueBinding._OPTION_MESSAGES_HIDDEN = 'messagesHidden';
// options that are managed primarily to detect changes for tracker to be notified.
oj.ValueBinding._OPTION_DISABLED = 'disabled';
oj.ValueBinding._OPTION_READONLY = 'readOnly';
// callback called when managed attribute is being updated
oj.ValueBinding._update = function(name, value, element, component, valueAccessor)
{
var options = valueAccessor.call(), updateProps = {},
ictObs = options[oj.ValueBinding._ATTRIBUTE_INVALID_COMPONENT_TRACKER],
icTracker;
if (name === oj.ValueBinding._OPTION_DISABLED || name === oj.ValueBinding._OPTION_READONLY)
{
icTracker = ictObs && ictObs.peek() || null; // don't add extra subscriptions
// when either of these options are updated
if (icTracker !== null && ko.isWriteableObservable(ictObs))
{
if (icTracker._update.call(icTracker, component, name, value))
{
// if _update mutates state
ictObs.valueHasMutated();
}
}
updateProps[name] = value;
return updateProps;
}
};
// init callback for managed attributes. When managing options like disabled, readOnly
// this method is required to return values.
oj.ValueBinding._init = function(name, value)
{
var initProps = {};
initProps[name] = value;
return initProps;
};
/**
* Called after component binding creates the component.
* @param {string} property
* @param {Element} element the element to which binding applied the componnet
* @param {Function=} component the widget bridge
* @param {Object=} valueAccessor
* @private
*/
oj.ValueBinding._afterCreate = function(property, element, component, valueAccessor)
{
var initProps = {}, optionsSet = valueAccessor.call(), isICTOptionSet;
if (property === oj.ValueBinding._ATTRIBUTE_INVALID_COMPONENT_TRACKER)
{
isICTOptionSet = optionsSet[property] ? true : false;
if (isICTOptionSet)
{
// register a writeback for invalidComponentTracker property by registering an event listener
// for the optionChange event.
oj.ValueBinding._registerInvalidComponentTrackerWriteback(property, optionsSet, element, component);
}
}
return initProps;
};
/**
* Called right before component is destroyed.
*
* @param {Element} element
* @private
*/
oj.ValueBinding._beforeDestroy = function(property, element, component, valueAccessor)
{
var jelem = $(element), options = valueAccessor.call(), icTracker,
ictObs = options[property];
if (property === oj.ValueBinding._ATTRIBUTE_INVALID_COMPONENT_TRACKER)
{
if (jelem)
{
jelem.off(oj.ValueBinding._EVENT_OPTIONCHANGE, oj.ValueBinding._updateInvalidComponentTracker);
if (ictObs && ko.isWriteableObservable(ictObs))
{
icTracker = ictObs.peek();
// remove component from tracker
if (icTracker._remove.call(icTracker, component))
{
// if _remove mutates state, then components need to react to it.
// example a button that binds to properties on invalidComponentTracker.
ictObs.valueHasMutated();
}
}
}
}
};
/**
* Listener for the optionChange event, it updates the invalidComponentTracker associated to the
* component that triggered the event.
*
* @param {jQuery.event=} event
* @private
*/
oj.ValueBinding._updateInvalidComponentTracker = function(event)
{
var ictObs = event.data.tracker, icTracker,
component = event.data.component, payload = arguments[1], option = payload['option'],
msgs = payload['value'];
if (option === oj.ValueBinding._OPTION_MESSAGES_SHOWN ||
option === oj.ValueBinding._OPTION_MESSAGES_HIDDEN)
{
if (ictObs && ko.isWriteableObservable(ictObs))
{
icTracker = ictObs.peek();
if (icTracker && icTracker._update.call(icTracker, component, option, msgs))
{
// if _update mutates state
ictObs.valueHasMutated();
}
}
}
};
/**
* Register a default callback for the 'optionChange' event. The callback writes the component and
* its validity to the invalidComponentTracker observable.
* @param {string} property
* @param {Object} options original options set on element
* @param {Element} element
* @param {Function=} component
* @private
*/
oj.ValueBinding._registerInvalidComponentTrackerWriteback = function(property, options, element, component)
{
var ictObs = options[property], messagesShown, messagesHidden, eventData,
icTracker, jElem = $(element);
// Create new intsance of InvalidComponentTracker if the observable is not set.
if (ko.isObservable(ictObs))
{
icTracker = ictObs.peek();
// push new instance of oj.InvalidComponentTracker onto observable if none present.
if (icTracker == null) // null or undefined
{
icTracker = new oj.InvalidComponentTracker();
ictObs(icTracker);
}
}
else
{
// tracker object is not an observable.
throw new Error('Binding attribute ' + oj.ValueBinding._ATTRIBUTE_INVALID_COMPONENT_TRACKER +
' should be bound to a ko observable.');
}
if (icTracker !== null)
{
// update icTracker inital state using component's latest option values
if (ko.isWriteableObservable(ictObs))
{
messagesShown = component.call(component, "option", oj.ValueBinding._OPTION_MESSAGES_SHOWN);
messagesHidden = component.call(component, "option", oj.ValueBinding._OPTION_MESSAGES_HIDDEN);
icTracker._update.call(icTracker, component,
oj.ValueBinding._OPTION_MESSAGES_SHOWN, messagesShown);
icTracker._update.call(icTracker, component,
oj.ValueBinding._OPTION_MESSAGES_HIDDEN, messagesHidden);
ictObs.valueHasMutated();
}
// register listener for optionChange event for future changes to messages* options
eventData = {tracker: ictObs, component: component};
jElem.on(oj.ValueBinding._EVENT_OPTIONCHANGE, eventData,
oj.ValueBinding._updateInvalidComponentTracker);
}
};
/**
* editableValue Behavior Definition and Injection
*/
oj.ComponentBinding.getDefaultInstance().setupManagedAttributes(
{
'for': 'editableValue',
'attributes': [oj.ValueBinding._ATTRIBUTE_INVALID_COMPONENT_TRACKER,
oj.ValueBinding._OPTION_DISABLED,
oj.ValueBinding._OPTION_READONLY],
'init': oj.ValueBinding._init,
'update': oj.ValueBinding._update,
'afterCreate': oj.ValueBinding._afterCreate,
'beforeDestroy': oj.ValueBinding._beforeDestroy
});
Source: src/main/javascript/oracle/oj/ojknockout-validation/ValueBinding.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01