/**
* Copyright (c) 2014, Oracle and/or its affiliates.
* All rights reserved.
*/
/*jslint browser: true*/
/**
* @private
*/
var _BASE_COMPONENT = 'baseComponent';
/**
* @ojcomponent oj.baseComponent
* @abstract
* @since 0.6
*/
$.widget('oj.' + _BASE_COMPONENT,
{
options:
{
/**
* <p>JQ selector identifying the JET Menu that the component should launch as a context menu on right-click, <kbd>Shift-F10</kbd>, <kbd>Press & Hold</kbd>,
* or component-specific gesture. If specified, the browser's native context menu will be replaced by the specified JET Menu.
*
* <p>To specify a JET context menu on a DOM element that is not a JET component, see the <code class="prettyprint">ojContextMenu</code> binding.
*
* <p>To make the page semantically accurate from the outset, applications are encouraged to specify the context menu via the standard
* HTML5 syntax shown in the below example. When the component is initialized, the context menu thus specified will be set on the component.
*
* <p>After create time, the <code class="prettyprint">contextMenu</code> option should be set via this API, not by setting the DOM attribute.
*
* @expose
* @memberof oj.baseComponent
* @instance
* @type {Object}
* @default <code class="prettyprint">null</code>
*
* @example <caption>Initialize a JET component with a context menu:</caption>
* // via recommended HTML5 syntax:
* <div id="myComponent" contextmenu="myMenu" data-bind="ojComponent: { ... }>
*
* // via JET initializer (less preferred) :
* // Foo is the component, e.g., InputText, InputNumber, Select, etc.
* $( ".selector" ).ojFoo({ "contextMenu": "#myMenu" });
*
* @example <caption>Get or set the <code class="prettyprint">contextMenu</code> option, after initialization:</caption>
* // getter
* // Foo is the component, e.g., Menu, Button, InputText, InputNumber, Select, etc.
* var menu = $( ".selector" ).ojFoo( "option", "contextMenu" );
*
* // setter
* // Foo is the component, e.g., InputText, InputNumber, Select, etc.
* $( ".selector" ).ojFoo( "option", "contextMenu", ".my-marker-class" );
*
* @example <caption>Set a JET context menu on an ordinary HTML element:</caption>
* <a href="#" id="myAnchor" contextmenu="myMenu" data-bind="ojContextMenu: {}">Some text</a>
*/
contextMenu: null,
/**
* <p>Attributes specified here will be set on the component's root DOM element at creation time.
* This is particularly useful for components like Dialog that wrap themselves in a new root element
* at creation time.
*
* <p>The supported attributes are <code class="prettyprint">id</code>, which overwrites any existing value,
* and <code class="prettyprint">class</code> and <code class="prettyprint">style</code>, which are appended
* to the current class and style, if any.
*
* <p>Setting this option after component creation has no effect. At that time, the root element already
* exists, and can be accessed directly via the <code class="prettyprint">widget</code> method, per the second example below.
*
* @example <caption>Initialize a JET component, specifying a set of attributes to be set
* on the component's root DOM element:</caption>
* // Foo is the component, e.g., Menu, Button, InputText, InputNumber, Select, etc.
* $( ".selector" ).ojFoo({ "rootAttributes": {
* "id": "myId",
* "style": "max-width:100%; color:blue;",
* "class": "my-class"
* }});
*
* @example <caption>After initialization, <code class="prettyprint">rootAttributes</code> should not be used. It is
* not needed at that time, as attributes of the root DOM element can simply be set directly, using
* <code class="prettyprint">widget</code>:</caption>
* // Foo is the component, e.g., Menu, Button, InputText, InputNumber, Select, etc.
* $( ".selector" ).ojFoo( "widget" ).css( "height", "100px" );
* $( ".selector" ).ojFoo( "widget" ).addClass( "my-class" );
*
* @expose
* @memberof oj.baseComponent
* @instance
* @type {?Object}
* @default <code class="prettyprint">null</code>
*/
rootAttributes: null,
/**
* <p>A collection of translated resources from the translation bundle, or <code class="prettyprint">null</code> if this
* component has no resources. Resources may be accessed and overridden individually or collectively, as seen in the examples.
*
* <p>If this component has (or inherits) translations, their documentation immediately follows this doc entry.
*
* @member
* @name translations
* @memberof oj.baseComponent
* @instance
* @type {Object}
* @default an object containing all resources relevant to the component and all its superclasses, or <code class="prettyprint">null</code> if none
*
* @example <caption>Initialize the component, overriding some translated resources. This syntax leaves the other translations intact at create
* time, but not if called after create time:</caption>
* // Foo is InputDate, InputNumber, etc.
* $( ".selector" ).ojFoo({ "translations": { someKey: "someValue",
* someOtherKey: "someOtherValue" } });
*
* @example <caption>Get or set the <code class="prettyprint">translations</code> option, after initialization:</caption>
* // Get one. (Foo is InputDate, InputNumber, etc.)
* var value = $( ".selector" ).ojFoo( "option", "translations.someResourceKey" );
*
* // Get all. (Foo is InputDate, InputNumber, etc.)
* var values = $( ".selector" ).ojFoo( "option", "translations" );
*
* // Set one, leaving the others intact. (Foo is InputDate, InputNumber, etc.)
* $( ".selector" ).ojFoo( "option", "translations.someResourceKey", "someValue" );
*
* // Set many. Any existing resource keys not listed are lost. (Foo is InputDate, InputNumber, etc.)
* $( ".selector" ).ojFoo( "option", "translations", { someKey: "someValue",
* someOtherKey: "someOtherValue" } );
*
*/
// translations option is initialized programmatically, so this top-level API doc lives in this virtual comment.
// Translations for all components are listed and JSDoc'ed in rt\src\main\resources\nls\root\ojtranslations.js.
// That JSDoc appears in the same generated doc page as this top-level doc.
// Events
/**
* <p>Triggered when any option changes. The event payload has the following properties:
*
* @property {Event} event <code class="prettyprint">jQuery</code> event object
* @property {Object} data event payload
* @property {string} data.option the name of the option that changed.
* @property {Object} data.previousValue - an Object holding the previous value of the option.
* When previousValue is not a primitive type, i.e., is an Object, it may hold the same value as
* the value property.
* @property {Object} data.value - an Object holding the current value of the option.
* @property {Object} data.optionMetadata information about the option that changed
* @property {string} data.optionMetadata.writeback <code class="prettyprint">"shouldWrite"</code> or
* <code class="prettyprint">"shouldNotWrite"</code>. For use by the JET writeback mechanism;
* 'shouldWrite' indicates that the value should be written to the observable.
*
* @example <caption>Initialize component with the <code class="prettyprint">optionChange</code> callback</caption>
* // Foo is Button, InputText, etc.
* $(".selector").ojFoo({
* 'optionChange': function (event, data) {}
* });
* @example <caption>Bind an event listener to the ojoptionchange event</caption>
* $(".selector").on({
* 'ojoptionchange': function (event, data) {
* window.console.log("option that changed is: " + data['option']);
* };
* });
*
* @memberof oj.baseComponent
* @expose
* @event
* @instance
*/
optionChange: undefined,
/**
* <p>Triggered before the component is destroyed. This event cannot be canceled; the
* component will always be destroyed regardless.
*
* @example <caption>Initialize component with the <code class="prettyprint">destroy</code> callback</caption>
* // Foo is Button, InputText, etc.
* $(".selector").ojFoo({
* 'destroy': function (event, data) {}
* });
* @example <caption>Bind an event listener to the destroy event</caption>
* $(".selector").on({
* 'ojdestroy': function (event, data) {
* window.console.log("The DOM node id for the destroyed component is : %s", event.target.id);
* };
* });
*
* @memberof oj.baseComponent
* @expose
* @event
* @instance
*/
destroy: undefined
},
// TODO: flesh out JSDoc verbiage, re: call after dom changes underneath component...
/**
* <p>Refreshes the component.
*
* @expose
* @memberof oj.baseComponent
* @instance
*/
refresh: function()
{
this._propertyContext = null;
},
/**
* <p>Overridden to save off component's default options and the options passed into the constructor (to be passed into
* the _InitOptions() call).
*
* <p>This method is final. Components should instead override one or more of the overridable create-time methods
* listed in <a href="#_ComponentCreate">_ComponentCreate</a>.
*
* @memberof oj.baseComponent
* @instance
* @private
* @final
*/
_createWidget: function(options, element)
{
// There is no need to clone these objects since they are not modified by the _createWidget() in the base class
this._originalDefaults = this.options || {};
this._constructorOptions = options || {};
this._super(options, element);
this._AfterCreateEvent();
},
/**
* <p>Reads the <code class="prettyprint">rootAttributes</code> option, and sets the root attributes on the
* component's root DOM element. See <a href="#rootAttributes">rootAttributes</a> for the set of supported
* attributes and how they are handled.
*
* @memberof oj.baseComponent
* @instance
* @protected
* @throws if unsupported attributes are supplied.
*/
_SetRootAttributes : function ()
{
var value = this.options.rootAttributes;
if (value)
{
var widget = this.widget();
if (widget == null)
return;
var classValue = value["class"];
if (classValue)
{
widget.addClass(classValue);
}
var styleValue = value["style"];
if (styleValue)
{
var currStyle = widget.attr('style');
if (currStyle)
{
widget.attr('style', currStyle + ';' + styleValue);
}
else
{
widget.attr('style', styleValue);
}
}
// make shallow copy, remove class and style from the copy, and set all
// remaining attrs on the element. Currently id is the only remaining attr
// that we support.
value = $.extend({}, value);
delete value['class'];
delete value['style'];
widget.attr(value);
delete value['id']; // remove the remaining supported value
var unsupportedAttrs = Object.keys(value);
if (unsupportedAttrs.length)
throw new Error("Unsupported values passed to rootAttributes option: " + unsupportedAttrs.toString());
}
},
/**
* <p>This method is final in JET. Components should instead override one or more of the overridable create-time methods
* listed in <a href="#_ComponentCreate">_ComponentCreate</a>.
*
* @memberof oj.baseComponent
* @instance
* @protected
* @final
*/
_create : function()
{
this._SaveAttributes(this.element);
this._InitOptions(this._originalDefaults, this._constructorOptions);
delete this._originalDefaults;
delete this._constructorOptions;
this._ComponentCreate();
this._AfterCreate();
// Marker class for all JET components on the init node (as opposed to the outer node)
// This marker class is used to:
// 1) find all JET components within a subtree
// 2) to prevent FOUC: init nodes NOT yet having this class are hidden.
this.element.addClass(_OJ_COMPONENT_NODE_CLASS);
},
/**
* <p>This method is not used in JET. Components should instead override <a href="#_InitOptions">_InitOptions</a>.
*
* @method
* @name oj.baseComponent#_getCreateOptions
* @memberof oj.baseComponent
* @instance
* @protected
* @final
*/
/**
* <p>This method is called before <a href="#_ComponentCreate">_ComponentCreate</a>, at which point
* the component has not yet been rendered. Component options should be initialized in this method,
* so that their final values are in place when <a href="#_ComponentCreate">_ComponentCreate</a> is called.
*
* <p>This includes getting option values from the DOM, where applicable, and coercing option
* values (however derived) to their appropriate data type if needed.
*
* <p>No work other than setting options should be done in this method. In particular, nothing should be
* set on the DOM until <a href="#_ComponentCreate">_ComponentCreate</a>, e.g. setting the <code class="prettyprint">disabled</code>
* DOM attribute from the <code class="prettyprint">disabled</code> option.
*
* <p>A given option (like <code class="prettyprint">disabled</code>) appears in the <code class="prettyprint">constructorOptions</code>
* param iff the app set it in the constructor:
*
* <ul>
* <li>If it appears in <code class="prettyprint">constructorOptions</code>, it should win over what's in the DOM
* (e.g. <code class="prettyprint">disabled</code> DOM attribute). If for some reason you need to tweak the value
* that the app set, then enable writeback when doing so:
* <code class="prettyprint">this.option('foo', bar, {'_context': {writeback: true, internalSet: true}})</code>.</li>
* <li>If it doesn't appear in <code class="prettyprint">constructorOptions</code>, then that option definitely is not bound,
* so writeback is not needed. So if you need to set the option (e.g. from a DOM attribute), use
* <code class="prettyprint">this.option('foo', bar, {'_context': {internalSet: true}})</code>.</li>
* </ul>
*
* <p>Overrides of this method should call <code class="prettyprint">this._super</code> first.
*
* @param {!Object} originalDefaults - original default options defined on the component and its ancestors
* @param {?Object} constructorOptions - options passed into the widget constructor
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_InitOptions : function (originalDefaults, constructorOptions)
{
this._setupDefaultOptions(originalDefaults, constructorOptions);
this._initContextMenuOption(constructorOptions);
},
/**
* <p>All component create-time initialization lives in this method, except the logic that specifically
* needs to live in <a href="#_InitOptions">_InitOptions</a>, <a href="#_AfterCreate">_AfterCreate</a>,
* or <a href="#_AfterCreateEvent">_AfterCreateEvent</a>,
* per the documentation for those methods. All DOM creation must happen here, since the intent of
* <a href="#_AfterCreate">_AfterCreate</a>, which is called next, is to contain superclass logic that must
* run after that DOM is created.
*
* <p>Overrides of this method should call <code class="prettyprint">this._super</code> first.
*
* <p>Summary of create-time methods that components can override, in the order that they are called:
*
* <ol>
* <li><a href="#_InitOptions">_InitOptions</a></li>
* <li><a href="#_ComponentCreate">_ComponentCreate</a> (this method)</li>
* <li><a href="#_AfterCreate">_AfterCreate</a></li>
* <li>(The <code class="prettyprint">create</code> event is fired here.)</li>
* <li><a href="#_AfterCreateEvent">_AfterCreateEvent</a></li>
* </ol>
*
* <p>For all of these methods, the contract is that overrides must call <code class="prettyprint">this._super</code> <i>first</i>, so e.g., the
* <code class="prettyprint">_ComponentCreate</code> entry means <code class="prettyprint">baseComponent._ComponentCreate</code>,
* then <code class="prettyprint">_ComponentCreate</code> in any intermediate subclasses, then
* <code class="prettyprint">_ComponentCreate</code> in the leaf subclass.
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_ComponentCreate : function ()
{
this['activeable'] = $();
// Store widget name, so that oj.Components.getWidgetConstructor() can get widget from the element
_storeWidgetName(this.element, this.widgetName);
},
/**
* <p>This method is called after <a href="#_ComponentCreate">_ComponentCreate</a>, but before the
* <code class="prettyprint">create</code> event is fired. The JET base component does
* tasks here that must happen after the component (subclass) has created itself in its override of
* <a href="#_ComponentCreate">_ComponentCreate</a>. Notably, the base component handles the
* <a href="#rootAttributes">rootAttributes</a> and <a href="#contextMenu">contextMenu</a> options here,
* since those options operate on the component root node, which for some components is created in their override
* of <a href="#_ComponentCreate">_ComponentCreate</a>.
*
* <p>Subclasses should override this method only if they have tasks that must happen after a superclass's
* implementation of this method, e.g. tasks that must happen after the context menu is set on the component.
*
* <p>Overrides of this method should call <code class="prettyprint">this._super</code> first.
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_AfterCreate : function ()
{
this._SetRootAttributes(); // do first, since has no dependencies, but other stuff might care about these attrs
// namespace facilitates removing contextMenu handlers separately, if app clears the "contextMenu" option
this.contextMenuEventNamespace = this.eventNamespace + "contextMenu";
this._setupContextMenu(true);
},
/**
* <p>This method is called after the <code class="prettyprint">create</code> event is fired.
* Components usually should not override this method, as it is rarely correct to wait until after the
* <code class="prettyprint">create</code> event to perform a create-time task.
*
* <p>An example of a correct usage of this method is [Dialog's auto-open behavior]{@link oj.ojDialog#initialVisibility},
* which needs to happen after the <code class="prettyprint">create</code> event.
*
* <p>Only <i>behaviors</i> (like Dialog auto-open behavior) should occur in this method. Component <i>initialization</i>
* must occur earlier, before the <code class="prettyprint">create</code> event is fired, so that
* <code class="prettyprint">create</code> listeners see a fully inited component.
*
* <p>Overrides of this method should call <code class="prettyprint">this._super</code> first.
*
* <p>Do not confuse this method with the <a href="#_AfterCreate">_AfterCreate</a> method, which is more commonly used.
*
* @method
* @memberof oj.baseComponent
* @instance
* @protected
*/
_AfterCreateEvent : $.noop,
/**
* <p>JET components should almost never implement this JQUI method. Please consult an architect if you believe you have an exception. Reasons:
* <ul>
* <li>This method is called at create time, after the <code class="prettyprint">create</code> event is fired. It is rare
* for that to be the appropriate time to perform a create-time task. For those rare cases, we have the
* <a href="#_AfterCreateEvent">_AfterCreateEvent</a> method, which is preferred over this method since it is called only
* at that time, not also at re-init time (see next).</li>
* <li>This method is also called at "re-init" time, i.e. when the initializer is called after the component has already been created.
* JET has not yet identified any desired semantics for re-initing a component.</li>
* </ul>
*
* @method
* @name oj.baseComponent#_init
* @memberof oj.baseComponent
* @instance
* @protected
*/
/**
* <p>Saves the element's attributes. This is called during _create.
* <a href="#_RestoreAttributes">_RestoreAttributes</a> will restore all these attributes
* and is called during _destroy.
* </p>
* <p> This base class default implementation does nothing.
* </p>
* <p>We also have <a href="#_SaveAllAttributes">_SaveAllAttributes</a> and
* <a href="#_RestoreAllAttributes">_RestoreAllAttributes</a> methods
* that save and restore <i>all</i> the attributes on an element.
* Component subclasses can opt into these _SaveAllAttributes/_RestoreAllAttributes
* implementations by overriding _SaveAttributes and _RestoreAttributes to call
* _SaveAllAttributes/_RestoreAllAttributes. If the subclass wants a different implementation
* (like save only the 'class' attribute), it can provide the implementation itself in
* _SaveAttributes/_RestoreAttributes.
*
*
* @param {Object} element - jQuery selection to save attributes for
* @memberof oj.baseComponent
* @instance
* @protected
*/
_SaveAttributes : function (element)
{
// default implementation does nothing.
},
/**
* <p>Saves all the element's attributes within an internal variable.
* <a href="#_RestoreAllAttributes">_RestoreAllAttributes</a> will restore the attributes
* from this internal variable.</p>
* <p>
* This method is final in JET.
* Subclasses can override _RestoreAttributes and call _RestoreAllAttributes.
* </p>
*
* <p>The JSON variable will be held as:
*
* <pre class="prettyprint">
* <code>[
* {
* "element" : element[i],
* "attributes" :
* {
* attributes[m]["name"] : {"attr": attributes[m]["value"], "prop": $(element[i]).prop(attributes[m]["name"])
* }
* }
* ]
* </code></pre>
*
* @param {Object} element - jQuery selection to save attributes for
* @memberof oj.baseComponent
* @instance
* @protected
* @final
*/
_SaveAllAttributes : function (element)
{
var self = this;
this._savedAttributes = [];
$.each(element, function (index, ele)
{
//need to be able to save for multiple elements
var saveAttributes = {},
save = { "element" : ele, "attributes" : saveAttributes },
attributes = ele.attributes;
self._savedAttributes.push(save);
$.each(attributes, function (index, attr)
{
// for proper access certain so called attributes should be accessed as properties
// [i.e. required, disabled] so fetch them initially
var attrName = attr["name"];
saveAttributes[attrName] = { "attr" : attr["value"], "prop" : $(ele).prop(attrName) };
});
});
},
/**
* <p>Gets the saved attributes for the provided element.
*
* <p>If you don't override <a href="#_SaveAttributes">_SaveAttributes</a> and
* <a href="#_RestoreAttributes">_RestoreAttributes</a>, then this will return null.
* <p>If you override _SaveAttributes to call <a href="#_SaveAllAttributes">_SaveAllAttributes</a>,
* then this will return all the attributes.
* If you override _SaveAttributes/_RestoreAttributes to do your own thing, then you may also have
* to override _GetSavedAttributes to return whatever you saved if you need access to the saved
* attributes.
*
* @param {Object} element - jQuery selection, should be a single entry
* @return {Object|null} savedAttributes - attributes that were saved for this element
* in <a href="#_SaveAttributes">_SaveAttributes</a>, or null if none were saved.
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_GetSavedAttributes : function (element)
{
var savedAttributes = this._savedAttributes;
// The component may not have saved any attributes. If so, return.
if (savedAttributes === undefined)
return null;
element = element[0];
for (var i = 0, j = savedAttributes.length;i < j;i++)
{
var curr = savedAttributes[i];
if (curr["element"] === element)
{
return curr["attributes"];
}
}
return {};
},
/**
* <p>Restore the attributes saved in <a href="#_SaveAttributes">_SaveAttributes</a>.</p>
* <p>
* _SaveAttributes is called during _create. And _RestoreAttributes is called during _destroy.
* </p>
* <p> This base class default implementation does nothing.
* </p>
* <p>We also have <a href="#_SaveAllAttributes">_SaveAllAttributes</a> and
* <a href="#_RestoreAllAttributes">_RestoreAllAttributes</a> methods
* that save and restore <i>all</i> the attributes on an element.
* Component subclasses can opt into these _SaveAllAttributes/_RestoreAllAttributes
* implementations by overriding _SaveAttributes and _RestoreAttributes to call
* _SaveAllAttributes/_RestoreAllAttributes. If the subclass wants a different implementation
* (like save only the 'class' attribute), it can provide the implementation itself in
* _SaveAttributes/_GetSavedAttributes/_RestoreAttributes.
* @memberof oj.baseComponent
* @instance
* @protected
*/
_RestoreAttributes : function ()
{
// default implementation does nothing.
},
/**
* <p>Restores <i>all</i> the element's attributes which were saved in
* <a href="#_SaveAllAttributes">_SaveAllAttributes</a>.
* This method is final in JET.</p>
* <p>
* If a subclass wants to save/restore all attributes on create/destroy, then the
* subclass can override <a href="#_SaveAttributes">_SaveAttributes</a>
* and call <a href="#_SaveAllAttributes">_SaveAllAttributes</a> and also
* override <a href="#_RestoreAttributes">_RestoreAttributes</a>
* and call <a href="#_RestoreAllAttributes">_RestoreAllAttributes</a>.
*
*
* @memberof oj.baseComponent
* @instance
* @protected
* @final
*/
_RestoreAllAttributes : function ()
{
$.each(this._savedAttributes, function (index, savedAttr)
{
var element = $(savedAttr["element"]),
attributes = savedAttr["attributes"];
//sanity check
if (element.length === 1)
{
var currAttr = savedAttr["element"].attributes,
removeAttr = [];
//request is to remove any attributes that didn't exist previously
//need to store the attributes in an array and remove them afterwards as otherwise there are side affects
for(var i=0, j=currAttr.length; i < j; i++)
{
if(!(currAttr[i]["name"] in attributes))
{
removeAttr.push(currAttr[i]["name"]);
}
}
for(var i=0, j=removeAttr.length; i < j; i++)
{
element.removeAttr(removeAttr[i]);
}
for (var attribute in attributes)
{
element.attr(attribute, attributes[attribute]["attr"]);
}
}
});
},
/**
* <p>Determines the name of the translation bundle section for this component.
*
* @return {string} the name of this component's translations section
* @memberof oj.baseComponent
* @protected
*/
_GetTranslationsSectionName: function()
{
return this.widgetFullName;
},
/**
* <p>Compares 2 option values for equality and returns true if they are equal; false otherwise.
*
* @param {String} option - the name of the option
* @param {Object} value1 first value
* @param {Object} value2 another value
* @return {boolean}
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_CompareOptionValues : function (option, value1, value2)
{
return value1 == value2;
},
/**
* <p>Retrieves a translated string after inserting optional parameters.
*
* @param {string} key the translations resource key
* The key is used to retrieve a format pattern from the component options, or if none
* is found - from the translated resource bundle.
* Tokens like {0}, {1}, {name} within the pattern will be used to define placement
* for the optional parameters. Token strings should not contain comma (,)
* or space characters, since they are reserved for future format type enhancements.
* The reserved characters within a pattern are:
* $ { } [ ]
* These characters will not appear in the formatted output unless they are escaped
* with a dollar character ('$').
*
* @param {...string|Object|Array} var_args - optional parameters to be inserted into the
* translated pattern.
*
* If more than one var_args arguments are passed, they will be treated as an array
* for replacing positional tokens like {0}, {1}, etc.
* If a single argument is passed, it will be treated as a Javascript Object whose
* keys will be matched to tokens within the pattern. Note that an Array is just
* a special kind of such an Object.
*
* For backward compatibility, a var_args argument whose type is neither
* Object or Array will be used to replace {0} in the pattern.
*
* @return formatted translated string or the key argument if the resource for the
* key was not found
*
* @memberof oj.baseComponent
* @instance
* @private
*/
// TODO: non-public methods need to start with "_". Pinged architect, who thinks this
// method should become protected post-V1, which would imply a capital _GetTranslatedString
getTranslatedString : function (key, var_args)
{
var params = {}, pattern;
if (arguments.length > 2)
{
params = Array.prototype.slice.call(arguments, 1);
}
else if (arguments.length == 2)
{
params = arguments[1];
if (typeof params !== 'object' && !(params instanceof Array))
{
params = [params];
}
}
pattern = this.option(_OJ_TRANSLATIONS_PREFIX + key);
// pattern could be undefined
return (pattern == null) ? key : oj.Translations.applyParameters(pattern.toString(), params);
},
/**
* <p>Returns the component DOM node indicated by the <code class="prettyprint">locator</code> parameter.
*
* <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 initialized.
*
* <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>.
*
* For more details on subIds, see the <a href="#subids-section">subIds section</a>.
*
* @expose
* @memberof oj.baseComponent
* @instance
*
* @param {Object} locator An Object containing, at minimum, a <code class="prettyprint">subId</code>
* property, whose value is a string that identifies a particular DOM node in this component.
*
* <p>If this component has (or inherits) any subIds, then they are documented in the
* "Sub-ID's" section of this document.
*
* <p>Subclasses of this component may support additional fields of the
* <code class="prettyprint">locator</code> Object, to further specify the desired node.
*
* @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)
{
if (locator == null || locator['subId'] == null)
{
return this.element ? this.element[0] : null;
}
// Non-null locators have to be handled by the component subclasses
return null;
},
/**
* <p>Returns the subId string for the given child DOM node. For more details, see
* <a href="#getNodeBySubId">getNodeBySubId</a>.
*
* For more details on subIds, see the <a href="#subids-section">subIds section</a>.
* @expose
* @memberof oj.baseComponent
* @instance
*
* @param {!Element} node - child DOM node
* @return {Object|null} The locator for the DOM node, or <code class="prettyprint">null</code> when none is found.
*
* @example <caption>Get the subId for a certain DOM node:</caption>
* // Foo is ojInputNumber, ojInputDate, etc.
* var locator = $( ".selector" ).ojFoo( "getSubIdByNode", nodeInsideComponent );
*
* @private
*/
getSubIdByNode: function(node)
{
return null;
},
// Overridden to set oj-hover and oj-focus classes
// TODO: Move JSDoc from subclasses to here. Don't include above internal comment. Make at-final.
destroy: function()
{
// Fire 'destroy' event
this._trigger('destroy');
this._super();
this._removeContextMenuBehavior();
// clean up states
this.element.removeClass(_OJ_COMPONENT_NODE_CLASS);
this.widget().removeClass( "oj-disabled" );
this['hoverable'].removeClass( "oj-hover" );
this['focusable'].removeClass( "oj-focus" );
this['activeable'].removeClass( "oj-active" );
_removeWidgetName(this.element, this.widgetName);
this._RestoreAttributes();
// TODO: move this to _RestoreAttributes?
if (this._initialCmDomAttr)
this.element.attr("contextmenu", this._initialCmDomAttr);
else
this.element.removeAttr("contextmenu");
},
/*
* Internal notes:
* Overridden to pass extra flags to _setOption
* param {...Object} var_args - key (or map), value, flags
*/
/**
* <p>This method has several overloads, which get and set component options and their fields. The functionality is unchanged from
* that provided by JQUI. See the examples for details on each overload.
*
* @expose
* @memberof oj.baseComponent
* @instance
* @final
*
* @param {string|Object=} optionName the option name (string, first two overloads), or the map (Object, last overload).
* Omitted in the third overload.
* @param {Object=} value a value to set for the option. Second overload only.
* @return {Object|undefined} The getter overloads return the retrieved value(s). When called via the public jQuery syntax, the setter overloads
* return the object on which they were called, to facilitate method chaining.
*
* @example <caption>First overload: get one option:
* <p>This overload accepts a (possibly dot-separated) <code class="prettyprint">optionName</code> param as a string, and returns
* the current value of that option.</caption>
* var isDisabled = $( ".selector" ).ojFoo( "option", "disabled" ); // Foo is Button, Menu, etc.
*
* // For object-valued options, dot notation can be used to get the value of a field or nested field.
* var startIcon = $( ".selector" ).ojButton( "option", "icons.start" ); // icons is object with "start" field
*
* @example <caption>Second overload: set one option:
* <p>This overload accepts two params: a (possibly dot-separated) <code class="prettyprint">optionName</code> string, and a new value to
* which that option will be set.</caption>
* $( ".selector" ).ojFoo( "option", "disabled", true ); // Foo is Button, Menu, etc.
*
* // For object-valued options, dot notation can be used to set the value
* // of a field or nested field, without altering the rest of the object.
* $( ".selector" ).ojButton( "option", "icons.start", myStartIcon ); // icons is object with "start" field
*
* @example <caption>Third overload: get all options:
* <p>This overload accepts no params, and returns a map of key/value pairs representing all the component
* options and their values.</caption>
* var options = $( ".selector" ).ojFoo( "option" ); // Foo is Button, Menu, etc.
*
* @example <caption>Fourth overload: set one or more options:
* <p>This overload accepts a single map of option-value pairs to set on the component. Unlike the first two
* overloads, dot notation cannot be used.</caption>
* $( ".selector" ).ojFoo( "option", { disabled: true, bar: 42 } ); // Foo is Button, Menu, etc.
*/
option: function(optionName, value) // actually varArgs per comment above the JSDoc, but GCC warns unless matches the @param that we wish to doc
{
if (arguments.length === 0)
{
// don't return a reference to the internal hash
return $.widget.extend({}, this.options);
}
var key = arguments[0];
var options = key;
var subkey = null;
var flags = {};
if (typeof key === "string")
{
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
options = {};
var parts = key.split(".");
key = parts.shift();
if (parts.length)
{
subkey = parts.join(".");
var curOption;
try
{
// Inform dynamic getters that the subkey is being set
if (arguments.length > 1)
{
this._settingNestedKey = subkey;
}
curOption = options[key] = $.widget.extend({}, this.options[key]);
}
finally
{
this._settingNestedKey = null;
}
for (var i = 0; i < parts.length - 1; i++)
{
curOption[parts[i]] = curOption[parts[i]] || {};
curOption = curOption[parts[i]];
}
key = parts.pop();
if (arguments.length === 1)
{
return curOption[key] === undefined ? null : curOption[key];
}
curOption[key] = value;
}
else
{
if (arguments.length === 1)
{
return this.options[key] === undefined ? null : this.options[key];
}
options[key] = value;
}
flags = arguments[2] || flags;
}
else
{
flags = arguments[1] || flags;
}
// Store subkey on the flags to let _setOption() know that dot notation was used
if (subkey != null)
{
flags = $.widget.extend({}, flags, {'subkey': subkey});
}
var context = flags ? flags['_context'] : null;
var internalSet = context ? context.internalSet : false;
// Avoid _setOption() calls for internal sets, since component's _setOption()
// and setOptions() overrides do not expect to be called in that case
if (internalSet)
{
this._internalSetOptions(options, flags);
}
else
{
this._setOptions(options, flags);
}
return this;
},
/**
* option() calls this rather than _setOption() if the caller was internal.
*
* @memberof oj.baseComponent
* @instance
* @private
*/
_internalSetOptions: function (options, flags)
{
for (var key in options)
{
var value = options[key];
var oldValue = this.options[key];
this.options[key] = value;
this._optionChanged(key, value, oldValue, flags);
}
},
/**
* <p>Overridden to pass extra flags to _setOption.
*
* @memberof oj.baseComponent
* @instance
* @protected
* @ignore
*/
_setOptions : function (options, flags)
{
for (var key in options)
{
this._setOption(key, options[key], flags);
}
return this;
},
/**
* <p>Overridden to set oj-hover and oj-focus classes.
*
* @memberof oj.baseComponent
* @instance
* @protected
* @ignore
*/
_setOption: function(key, value, flags)
{
var originalValue = this.options[key];
if (key === "disabled" )
{
// The JQUI superclass method has hard-coded style classes in the 'if key === "disabled"' block, so unfortunately
// we must copy that logic here with updated style classes, and NOT call _super() for the disabled case.
// TBD: keep this logic updated if superclass method changes.
this.options[ key ] = value;
// TBD: widget() is not always the thing that should have aria-disabled on it. E.g. for the checkbox/radio flavors of ojButton,
// widget() returns the root node, but aria-disabled belongs on the <input>. We fixed this JQUI bug in ojButton by having ojButton
// override this method to remove it from the root node and add it to the input. Would be better for each component to know which
// element to apply that to, e.g. an overridable method returning that element, or copying "hoverable" paradigm if appropriate.
// In the cases where this.element is different than widget(), this.element is more likely to be the right thing, so maybe change
// default to that.
// Update: this issue is getting even more awkward now that we have "effectively disabled". Probably need to refactor this code!
this.widget()
.toggleClass( "oj-disabled", !!value )
.attr( "aria-disabled", value );
if (value)
{
this['hoverable'].removeClass( "oj-hover" );
this['focusable'].removeClass( "oj-focus" );
// TODO: when we have worked out the 'create' super code change,
// this this check should not be necessary. Right now, this gets
// called before _create for radioset (and possibly others) when
// we create a component like ojRadioset({disabled: true});
if (!this['activeable'])
this['activeable'] = $();
this['activeable'].removeClass( "oj-active" );
}
}
else {
try
{
var subkey = (flags == null)? null :flags['subkey'];
if (subkey != null)
{
this._settingNestedKey = subkey;
}
this._super(key, value);
}
finally
{
this._settingNestedKey = null;
}
if ( key === "contextMenu" )
this._setupContextMenu(false);
}
this._optionChanged(key, value, originalValue, flags);
return this;
},
/**
* @memberof oj.baseComponent
* @instance
* @private
*/
_optionChanged: function(key, value, originalValue, flags)
{
// TODO: add option change notification
var changed = false;
var context = null;
var writeback = false;
var originalEvent = null;
var optionMetadata = null;
if (flags)
{
changed = flags['changed'];
context = flags['_context'];
if (context)
{
originalEvent = context.originalEvent;
writeback = (context.writeback === undefined) ? originalEvent != null : context.writeback;
optionMetadata = context.optionMetadata;
}
}
if (changed || !this._CompareOptionValues(key, originalValue, value))
{
optionMetadata = optionMetadata || {};
optionMetadata['writeback'] = writeback ? "shouldWrite" : "shouldNotWrite";
var optionChangeData =
{
"option" : key,
"previousValue" : originalValue,
"value" : value,
"optionMetadata" : optionMetadata
};
this._trigger("optionChange", originalEvent, optionChangeData);
}
},
/**
* <p>Overridden to change the way the component events are treating original events:
*
* 1) preventDefault(), stopPropagation() and stopImmediatePropagation() no longer invoke
* the corresponding methods on the .originalEvent
* 2) Properties of the .originalEvent are no longer copied to the new event being
* triggered
*
* @param {string} type - component event type
* @param {?Object} event - original event
* @param {Object=} data - event data
*
* @private
*/
_trigger : function (type, event, data)
{
var prop, orig, callback = this.options[type];
data = data || {};
event = $.Event(event, _OJ_COMPONENT_EVENT_OVERRIDES);
event.type = (this.widgetEventPrefix + type).toLowerCase();
// the original event may come from any element
// so we need to reset the target on the new event
event.target = this.element[0];
this.element.trigger(event, data);
return !($.isFunction(callback) && callback.apply(this.element[0], [event].concat(data)) === false || event.isDefaultPrevented());
},
/**
* <p>Sets contextMenu option from DOM if option not set.
*
* <p>Do not override. To be called only from _InitOptions().
*
* @memberof oj.baseComponent
* @instance
* @private
* @final
*/
_initContextMenuOption: function(constructorOptions)
{
var contextMenu = this.element.attr("contextmenu");
this._initialCmDomAttr = contextMenu; // TODO: remove this after the _RestoreAttributes() call in destroy() is uncommented
if (contextMenu && !('contextMenu' in constructorOptions)) // if app set DOM attr but not option, then set the option from the DOM
{
this.option("contextMenu",
"#" + contextMenu,
{'_context': {internalSet: true}}); // writeback not needed since "not in constructorOptions" means "not bound"
}
},
/**
* <p>Call this method from _AfterCreate() and _setOption("contextMenu").
*
* <p>- This method first removes contextMenu listeners/attr from the component root node.
* <p>- Then, if the component's "contextMenu" option is set, then it sets those things on the component root node.
*
* <p>We don't look for the menu until _getContextMenu() is called on the first launch,
* so that the menu needn't be inited before this component.
*
* <p>If needed, override <code class="prettyprint">_NotifyContextMenuGesture()</code>, not this private method.
*
* @memberof oj.baseComponent
* @instance
* @private
*/
_setupContextMenu: function(isCreateTime)
{
this._removeContextMenuBehavior();
if ( this.options.contextMenu )
{
var rootNode = this.widget();
var initNode = this.element;
// if the context menu was specified via DOM attr of the init node at create time, and if that node is
// different than the root node (i.e. if this component wraps itself in a new root node in
// _ComponentCreate()), then remove the attr from the init node.
if (isCreateTime && !initNode.is(rootNode))
initNode.removeAttr("contextmenu");
// if menu elem exists already and has an id, then set id on the *root* node's contextmenu DOM attr
var id = $(this.options.contextMenu).attr("id");
if (id)
rootNode.attr("contextmenu", id);
var self = this;
//Bug 18745622, on Chrome preventDefault on "keyup" will avoid triggering contextmenu event
//which will display native contextmenu.This also need to be added on document as event target is not menu launcher.
this._preventKeyUpEventIfMenuOpen = function(event){
if (event.which == 121 && event.shiftKey) // Shift-F10
{
if (self._getContextMenuNode().is(":visible"))
event.preventDefault();
}
};
// Note: Whether or not we use Hammer to detect press-hold, this code would need to do the following things seen below:
//
// (1) Prevent the compatibility mousedown event from triggering Menu's clickAway logic.
// (2) Prevent press-hold from also generating a click (unless Hammer does this automatically; I'm guessing it doesn't).
// (3) Ensure we don't respond to *both* press-hold and contextmenu events on Android.
//
// So the only thing that Hammer would replace is:
//
// (4) Detecting the press-hold.
//
// Not currently using Hammer for (4), since:
//
// - This code predates Hammer, and was already stable after extensive iteration / fine-tuning.
// - We use the same listeners for parts of 1-4. If moved 4 off to Hammer (separate listener), just need to ensure that
// we don't introduce any race conditions, etc. (May be easy or hard, just need to look.)
// - Hammer only wants to have one instance per DOM node, else they fight to control some things like touch-action. So
// a prereq for having this baseComponent logic put Hammer on components is to work out a protocol for super- and sub-
// classes to share the same instance and not step on each other. Not insurmountable; just need to have the conversation.
var pressHoldThreshold = 750; // per UX spec. Point of reference: JQ Mobile uses 750ms by default.
var isPressHold = false; // to prevent pressHold from generating a click
var touchInProgress = false;
var doubleOpenTimer; // to prevent double open. see usage below.
var doubleOpenThreshold = 300; // made up this number. TBD: Tweak as needed to make all platforms happy.
var doubleOpenType = null; // "touchstart" or "contextmenu"
var launch = function(event, eventType, pressHold) {
// ensure that pressHold doesn't result in a click. Set this before the bailouts below.
isPressHold = pressHold;
var menu = self._getContextMenu();
// In Mobile Safari only, mousedown fires *after* the touchend, which causes at least 2 problems:
// 1) CM launches after 750ms (good), then disappears when lift finger (bad), because touchend -->
// mousedown, which calls Menu's "clikAway" mousedown listener, which dismisses Menu.
// 2) The isPressHold logic needs to reset the isPressHold ivar on any event that can start a click,
// including mousedown. This problem causes the mousedown listener to incorrectly clear the ivar
// after a pressHold, which broke the whole mechanism.
// SOLUTION FOR 1-2: On each launch (at 750ms), set a one-time touchend listener that will set a
// var and clear it 50ms later. While the var is set, both mousedown listeners can disregard the
// mousedown. Make the var a static var in Menu, since Menu's listener is static, and since this
// launcher component can get/set it via an (effectively static) menu method.
// NON-SOLUTIONS: Cancelling touchstart or touchend, via pD() and sP(), doesn't cancel iPad's mousedown.
// Cancelling mousedown from here doesn't work even if capture phase, since ojMenu's listener is capture phase.
// TIMING: The following block should be before the doubleOpen bailout.
if (isPressHold)
{
rootNode.one( "touchend" + self.contextMenuEventNamespace, function( event ) {
var touchendMousedownThreshold = 50; // 50ms. Make as small as possible to prevent unwanted side effects.
menu.__contextMenuPressHoldJustEnded(true);
setTimeout(function() {
menu.__contextMenuPressHoldJustEnded(false);
}, touchendMousedownThreshold);
});
}
// On platforms like Android Chrome where long presses already fire the contextmenu event, the pressHold
// logic causes the menu to open twice, once for the pressHold, once for the contextmenu. There's no
// guarantee which will happen first, but as long as they happen within doubleOpenThreshold ms
// of each other, this logic should prevent the double open.
// Note: Another option is a platform-specific solution where we only use pressHold for platforms that need
// it (that don't already fire a contextmenu event for pressHold), but architectural preference is to avoid
// platform-specific solutions if possible.
if ((doubleOpenType === "touchstart" && event.type === "contextmenu")
|| (doubleOpenType === "contextmenu" && event.type === "touchstart"))
{
doubleOpenType = null;
clearTimeout(doubleOpenTimer);
return;
}
// If a nested element or component already showed a JET context menu for this event, don't replace it with ours.
// Hack: must check defaultPrevented on the nested event too, because for touchstart events on iOS7 at least, when
// the outer component reaches this point, event is a different JQ wrapper event than the one on which the inner
// component previously called preventDefault, although they both wrap the same native originalEvent. The new wrapper
// never had its isDefaultPrevented field set to the returnTrue method, so must check the nested originalEvent.
// This never seems to happen with right-click and Shift-F10 events. Has nothing to do with the setTimeout: the events
// received by the rootNode.on("touchstart"...) code are different (firstWrapper==secondWrapper returns false).
// TODO: link to JQ bug once filed.
if (event.isDefaultPrevented() || (event.originalEvent && event.originalEvent.defaultPrevented))
return;
// For components like Button where "effectively disabled" --> "not focusable", keyboard CM launch is impossible, so
// allowing right-click access would be an a11y issue. If there's ever a need to enable this for focusable effectively
// disabled components, we can always replace the _IsEffectivelyDisabled() call with a new protected method whose
// baseComponent impl returns _IsEffectivelyDisabled().
if (self._IsEffectivelyDisabled())
return;
self._NotifyContextMenuGesture(menu, event, eventType);
// if _NotifyContextMenuGesture() (or subclass override of it) actually opened the CM, and if that launch wasn't
// cancelled by a beforeOpen listener...
if (self._getContextMenuNode().is(":visible"))
{
event.preventDefault(); // don't show native context menu
document.addEventListener("keyup", self._preventKeyUpEventIfMenuOpen);
// see double-open comments above
if (event.type === "touchstart" || event.type === "contextmenu")
{
doubleOpenType = event.type;
doubleOpenTimer = setTimeout(function(){
doubleOpenType = null;
}, doubleOpenThreshold);
}
}
};
// At least some of the time, the pressHold gesture also fires a click event same as a short tap. Prevent that here.
this._clickListener = function( event ) {
if (isPressHold) {
// For Mobile Safari capture phase at least, returning false doesn't work; must use pD() and sP() explicitly.
event.preventDefault();
event.stopPropagation();
isPressHold = false;
}
};
// Use capture phase to make sure we cancel it before any regular bubble listeners hear it.
rootNode[0].addEventListener("click", this._clickListener, true);
rootNode
.on( "touchstart" + this.contextMenuEventNamespace + " " +
"mousedown" + this.contextMenuEventNamespace + " " +
"keydown" + this.contextMenuEventNamespace + " ", function( event ) {
// for mousedown-after-touchend Mobile Safari issue explained above where __contextMenuPressHoldJustEnded is set.
if (event.type === "mousedown" && self._getContextMenu().__contextMenuPressHoldJustEnded())
return;
// reset isPressHold flag for all events that can start a click.
isPressHold = false;
// start a pressHold timer on touchstart. If not cancelled before 750ms by touchend/etc., will launch the CM.
if (event.type === "touchstart") {
touchInProgress = true;
this._contextMenuPressHoldTimer = setTimeout(launch.bind(undefined, event, "touch", true), pressHoldThreshold);
}
return true;
})
// if the touch ends before the 750ms is up, it's not a long enough tap-and-hold to show the CM
.on( "touchend" + this.contextMenuEventNamespace + " " +
"touchcancel" + this.contextMenuEventNamespace, function( event ) {
touchInProgress = false;
clearTimeout(this._contextMenuPressHoldTimer);
return true;
})
.on( "keydown" + this.contextMenuEventNamespace + " " +
"contextmenu" + this.contextMenuEventNamespace, function( event ) {
if (event.type === "contextmenu" // right-click. pressHold for Android but not iOS
|| (event.which == 121 && event.shiftKey)) // Shift-F10
{
var eventType = touchInProgress ? "touch" : event.type === "keydown" ? "keyboard" : "mouse";
launch(event, eventType, false);
}
return true;
})
// Does 2 things:
// 1) Prevents native context menu / callout from appearing in Mobile Safari. E.g. for links, native CM has "Open in New Tab".
// 2) In Mobile Safari and Android Chrome, prevents tap-hold from selecting the text and showing the selection handles and (in Safari) the Copy/Define callout.
// In UX discussion, we decided to prevent both of these things for all JET components for now. If problems, can always, say, add protected method allowing
// subclass to opt out (e.g. if they need 1 and/or 2 to work).
// Per comments in scss file, this suppression has issues in specific versions of Mobile Safari.
.addClass("oj-menu-context-menu-launcher");
}
},
/**
* <p>Sets the ivars used by _getContextMenu() and _getContextMenuNode(). These 2 _ivars should be set/cleared in lockstep.
*
* <p>This method should be called only by those 2 methods.
*
* @memberof oj.baseComponent
* @instance
* @throws if no Menu found, which is app error since Menu should be inited by the time this is called
* @private
*/
_setContextMenuIvars: function()
{
// JQ obj containing the menu element. Empty if no element found.
this._contextMenuNode = $(this.options.contextMenu).first();
// Menu component. undefined if _contextMenuNode empty, or if its one node has no JET Menu.
this._contextMenu = this._contextMenuNode.data( "oj-ojMenu" );
if (!this._contextMenu)
throw new Error('"contextMenu" option set to "' + this.options.contextMenu + '", which does not reference a valid JET Menu.');
var self = this;
this._contextMenuNode.on( "oj__dismiss" + this.contextMenuEventNamespace , function( event, ui ) {
document.removeEventListener("keyup", self._preventKeyUpEventIfMenuOpen);
});
},
/**
* <p>Lazy getter for the context menu.
*
* <p>This method should be called only by the "user is launching the context menu" listeners, which should only be registered
* if the "contextMenu" option is set. Do not call at create time.
*
* <p>We wait until menu-launch time to lazily get the menu, to avoid an init-order dependency. It should be OK to
* init the component before its context menu.
*
* @return the JET Menu component pointed to by the "contextMenu" option
* @throws if no Menu found, which is app error since Menu should be inited by the time this is called
*
* @memberof oj.baseComponent
* @instance
* @private
*/
_getContextMenu: function()
{
if (!this._contextMenu)
this._setContextMenuIvars();
return this._contextMenu;
},
/**
* <p>All _getContextMenu doc applies to this method too, except this method returns
* the menu node, not the menu itself.
*
* @return the root DOM node of the JET Menu
* @throws if no Menu found, which is app error since Menu should be inited by the time this is called
*
* @memberof oj.baseComponent
* @instance
* @private
*/
_getContextMenuNode: function()
{
if (!this._contextMenuNode)
this._setContextMenuIvars();
return this._contextMenuNode;
},
/**
* <p>This method removes contextMenu functionality from the component.
*
* @memberof oj.baseComponent
* @instance
* @private
*/
_removeContextMenuBehavior: function()
{
this.widget()
.removeAttr( "contextmenu" )
.off( this.contextMenuEventNamespace )
.removeClass("oj-menu-context-menu-launcher")
[0].removeEventListener("click", this._clickListener, true);
this._contextMenuNode && this._contextMenuNode.off( this.contextMenuEventNamespace );
// the other 2 contextMenu timeouts don't need to be cleared here
clearTimeout(this._contextMenuPressHoldTimer);
// set/unset these ivars in lockstep
this._contextMenu = undefined;
this._contextMenuNode = undefined;
},
/**
* <p>When the <a href="#contextMenu">contextMenu</a> option is set, this method is called when the user invokes the context menu via
* the default gestures: right-click, <kbd>Press & Hold</kbd>, and <kbd>Shift-F10</kbd>. Components should not call this method directly.
*
* <p>The default implementation simply calls <a href="#_OpenContextMenu">this._OpenContextMenu(event, eventType)</a>.
* Overrides of this method should call that same method, perhaps with additional params, not [menu.open()]{@link oj.ojMenu#open}.
*
* <p>This method may be overridden by components needing to do things like the following:
*
* <ul>
* <li>Customize the [launcher]{@link oj.ojMenu#openOptions.launcher} or [position]{@link oj.ojMenu#openOptions.position} passed to
* <a href="#_OpenContextMenu">_OpenContextMenu()</a>. See that method for guidance on these customizations.</li>
*
* <li>Customize the menu contents. E.g. some components need to enable/disable built-in commands like <kbd>Cut</kbd> and <kbd>Paste</kbd>,
* based on state at launch time.</li>
*
* <li>Bail out in some cases. E.g. components with UX approval to use <kbd>PressHoldRelease</kbd> rather than <kbd>Press & Hold</kbd> can override this method
* to say <code class="prettyprint">if (eventType !== "touch") this._OpenContextMenu(event, eventType);</code>. When those components
* detect the alternate context menu gesture (e.g. <kbd>PressHoldRelease</kbd>), that separate listener should call <a href="#_OpenContextMenu">this._OpenContextMenu()</a>,
* not this method (<code class="prettyprint">_NotifyContextMenuGesture()</code>), and not [menu.open()]{@link oj.ojMenu#open}. </li>
* </ul>
*
* <p>Components needing to do per-launch setup like the above tasks should do so in an override of this method, <i>not</i> in
* a [beforeOpen]{@link oj.ojMenu#event:beforeOpen} listener or an <a href="#_OpenContextMenu">_OpenContextMenu()</a> override.
* This is discussed more fully <a href="#_OpenContextMenu">here</a>.
*
* @memberof oj.baseComponent
* @instance
* @protected
*
* @param {!Object} menu The JET Menu to open as a context menu. Always non-<code class="prettyprint">null</code>.
* @param {!Event} event What triggered the menu launch. Always non-<code class="prettyprint">null</code>.
* @param {string} eventType "mouse", "touch", or "keyboard". Never <code class="prettyprint">null</code>.
*/
_NotifyContextMenuGesture: function(menu, event, eventType)
{
this._OpenContextMenu(event, eventType);
},
/**
* <p>The only correct way for a component to open its context menu is by calling this method, not by calling [Menu.open()]{@link oj.ojMenu#open} or
* <a href="#_NotifyContextMenuGesture">_NotifyContextMenuGesture()</a>. This method should be called in two cases:
*
* <ul>
* <li>This method is called by <a href="#_NotifyContextMenuGesture">_NotifyContextMenuGesture()</a> and its overrides. That method is
* called when the baseComponent detects the default context menu gestures: right-click, <kbd>Press & Hold</kbd>, and <kbd>Shift-F10</kbd>.</li>
*
* <li>Components with UX-approved support for alternate context menu gestures like <kbd>PressHoldRelease</kbd> should call this method directly
* when those gestures are detected.</li>
* </ul>
*
* <p>Components needing to customize how the context menu is launched, or do any per-launch setup, should do so in the caller of this method,
* (which is one of the two callers listed above), often by customizing the params passed to this method
* (<code class="prettyprint">_OpenContextMenu</code>) per the guidance below. This setup should <i>not</i> be done in the following ways:
*
* <ul>
* <li>Components should not perform setup in a [beforeOpen]{@link oj.ojMenu#event:beforeOpen} listener, as this can cause a race
* condition where behavior depends on who got their listener registered first: the component or the app. The only correct component use
* of a <code class="prettyprint">beforeOpen</code> listener is when there's a need to detect whether <i>something else</i> launched the menu.</li>
*
* <li>Components should not override this method (<code class="prettyprint">_OpenContextMenu</code>), as this method is final. Instead, customize
* the params that are passed to it.</li>
* </ul>
*
* <p><b>Guidance on setting OpenOptions fields:</b>
*
* <p><b>Launcher:</b>
*
* <p>Depending on individual component needs, any focusable element within the component can be the appropriate
* [launcher]{@link oj.ojMenu#openOptions.launcher} for this launch.
*
* <p>Browser focus returns to the launcher on menu dismissal, so the launcher must at least be focusable. Typically a tabbable (not just
* focusable) element is safer, since it just focuses something the user could have focused on their own.
*
* <p>By default (i.e. if <code class="prettyprint">openOptions</code> is not passed, or if it lacks a <code class="prettyprint">launcher</code>
* field), the component init node is used as the launcher for this launch. If that is not focusable or is suboptimal for a given
* component, that component should pass something else. E.g. components with a "roving tabstop" (like Toolbar) should typically choose the
* current tabstop as their launcher.
*
* <p>The [:focusable]{@link http://api.jqueryui.com/focusable-selector/} and [:tabbable]{@link http://api.jqueryui.com/tabbable-selector/} selectors
* may come in handy for choosing a launcher, e.g. something like <code class="prettyprint">this.widget().find(".my-class:tabbable").first()</code>.
*
* <p><b>Position:</b>
*
* <p>By default, this method applies [positioning]{@link oj.ojMenu#openOptions.position} that differs from Menu's default in the following ways:
* (The specific settings are subject to change.)
*
* <ul>
* <li>For mouse and touch events, the menu is positioned relative to the event, not the launcher.</li>
*
* <li>For touch events, <code class="prettyprint">"my"</code> is set to <code class="prettyprint">"start>40 center"</code>,
* to avoid having the context menu obscured by the user's finger, and <code class="prettyprint">"collision"</code> is set to
* <code class="prettyprint">"flipfit"</code>, to avoid auto-scrolling on tablets.</li>
* </ul>
*
* <p>Usually, if <code class="prettyprint">position</code> needs to be customized at all, the only thing that needs changing is its
* <code class="prettyprint">"of"</code> field, and only for keyboard launches (since mouse/touch launches should almost certainly keep
* the default <code class="prettyprint">"event"</code> positioning). This situation arises anytime the element relative to which the menu
* should be positioned for keyboard launches is different than the <code class="prettyprint">launcher</code> element (the element to which
* focus should be returned upon dismissal). For this case, <code class="prettyprint">{ "position": {"of": eventType==="keyboard" ? someElement : "event"} }</code>
* can be passed as the <code class="prettyprint">openOptions</code> param.
*
* <p>Be careful not to clobber useful defaults by specifying too much. E.g. if you only want to customize <code class="prettyprint">"of"</code>,
* don't pass other fields like <code class="prettyprint">"my"</code>, since your value will be used for all modalities (mouse, touch, keyboard),
* replacing the modality-specific defaults that are usually correct. Likewise, don't forget the
* <code class="prettyprint">eventType==="keyboard"</code> check if you only want to customize <code class="prettyprint">"of"</code> for keyboard launches.
*
* <p><b>InitialFocus:</b>
*
* <p>This method forces [initialFocus]{@link oj.ojMenu#openOptions.initialFocus} to <code class="prettyprint">"menu"</code> for this
* launch, so the caller needn't specify it.
*
* @memberof oj.baseComponent
* @instance
* @protected
* @final
*
* @param {!Event} event What triggered the context menu launch. Must be non-<code class="prettyprint">null</code>.
* @param {string} eventType "mouse", "touch", or "keyboard". Must be non-<code class="prettyprint">null</code>. Passed explicitly since caller
* knows what it's listening for, and since events like <code class="prettyprint">contextmenu</code> and
* <code class="prettyprint">click</code> can be generated by various input modalities, making it potentially error-prone for
* this method to determine how they were generated.
* @param {Object=} openOptions Options to merge with this method's defaults, which are discussed above. The result will be passed to
* [Menu.open()]{@link oj.ojMenu#open}. May be <code class="prettyprint">null</code> or omitted. See also the
* <code class="prettyprint">shallow</code> param.
* @param {Object=} submenuOpenOptions Options to be passed through to [Menu.open()]{@link oj.ojMenu#open}. May be <code class="prettyprint">null</code>
* or omitted.
* @param {boolean=} shallow Whether to perform a deep or shallow merge of <code class="prettyprint">openOptions</code> with this method's default
* value. The default and most commonly correct / useful value is <code class="prettyprint">false</code>.
*
* <ul>
* <li>If <code class="prettyprint">true</code>, a shallow merge is performed, meaning that the caller's <code class="prettyprint">position</code>
* object, if passed, will completely replace this method's default <code class="prettyprint">position</code> object.</li>
*
* <li>If <code class="prettyprint">false</code> or omitted, a deep merge is performed. For example, if the caller wishes to tweak
* <code class="prettyprint">position.of</code> while keeping this method's defaults for <code class="prettyprint">position.my</code>,
* <code class="prettyprint">position.at</code>, etc., it can pass <code class="prettyprint">{"of": anOfValue}</code> as the
* <code class="prettyprint">position</code> value.</li>
* </ul>
*
* <p>The <code class="prettyprint">shallow</code> param is n/a for <code class="prettyprint">submenuOpenOptions</code>, since this method doesn't
* apply any defaults to that. (It's a direct pass-through.)
*/
_OpenContextMenu: function(event, eventType, openOptions, submenuOpenOptions, shallow)
{
// Note: our touch positioning is similar to that of the iOS touch callout (bubble with "Open in New Tab", etc.), which is offset from the pressHold location as follows:
// - to the right, vertically centered. (by default)
// - to the left, vertically centered. (if fits better)
// - above or below, horizontally centered. (if fits better)
// An offset like 40 prevents it from opening right under your finger, and is similar to iOS's offset. It also prevents the issue (on iOS7 at least)
// where touchend after the pressHold can dismiss the CM b/c the menu gets the touchend.
var position = {
"mouse": {
"my": "start top",
"at": "start bottom",
"of": event
},
"touch": {
"my": "start>40 center",
"at": "start bottom",
"of": event,
"collision": "flipfit"
},
"keyboard": {
"my": "start top",
"at": "start bottom",
"of": "launcher"
}
};
var defaults = {"launcher": this.element, "position": position[eventType]}; // used for fields caller omitted
var forcedOptions = {"initialFocus": "menu"};
openOptions = (shallow)
? $.extend(defaults, openOptions, forcedOptions)
: $.extend(true, defaults, openOptions, forcedOptions);
var menu = this._getContextMenu();
menu.__openingContextMenu = true; // Hack. See todo on this ivar in Menu.open().
menu.open(event, openOptions, submenuOpenOptions);
menu.__openingContextMenu = false;
},
/**
* <p>Overridden to set oj-hover class.
*
* @memberof oj.baseComponent
* @instance
* @private
*/
_hoverable: function( element )
{
// The JQUI superclass method has hard-coded style classes, so unfortunately
// we must copy that logic here with updated style classes, and NOT call _super().
// TBD: keep this logic updated if superclass method changes.
this['hoverable'] = this['hoverable'].add( element );
this._on( element, {
mouseenter: function( event ) {
if (!$( event.currentTarget ).hasClass( "oj-disabled" ))
{
$( event.currentTarget ).addClass( "oj-hover" );
}
},
mouseleave: function( event ) {
$( event.currentTarget ).removeClass( "oj-hover" );
}
});
},
/**
* <p>Overridden to set oj-focus class.
*
* @memberof oj.baseComponent
* @instance
* @private
*/
_focusable: function( element )
{
// The JQUI superclass method has hard-coded style classes, so unfortunately
// we must copy that logic here with updated style classes, and NOT call _super().
// TBD: keep this logic updated if superclass method changes.
this['focusable'] = this['focusable'].add( element );
this._on( element, {
focusin: function( event ) {
$( event.currentTarget ).addClass( "oj-focus" );
},
focusout: function( event ) {
$( event.currentTarget ).removeClass( "oj-focus" );
}
});
},
/**
* <p>Set oj-active class on mousedown and remove it on mouseup.
* oj-active is one of JET's 'marker' style classes. It emulates
* the css :active pseudo-class.
*
* @memberof oj.baseComponent
* @instance
* @private
*/
_activeable: function( element )
{
this['activeable'] = this['activeable'].add( element );
this._on( element, {
mousedown: function( event )
{
$( event.currentTarget ).addClass( "oj-active" );
},
mouseup: function( event )
{
$( event.currentTarget ).removeClass( "oj-active" );
}
});
},
/**
* Remove all listener references that were attached to the element
* which includes _activeable, _focusable and hoverable.
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_UnregisterChildNode: function(element)
{
if (element) {
$(element).off(this.eventNamespace);
if (this.bindings) {
this.bindings = $(this.bindings.not(element));
}
this['activeable'] = $(this['activeable'].not( element ));
this['focusable'] = $(this['focusable'].not( element ));
this['hoverable'] = $(this['hoverable'].not( element ));
}
},
/**
* <p>Retrieves a translated resource for a given key.
*
* @param {string} key
* @return {Object} resource associated with the key or null if none was found
* @memberof oj.baseComponent
* @instance
* @private
*/
// TODO: non-public methods need to start with "_". Pinged architect, who thinks this
// method should become protected post-V1, which would imply a capital _GetResource
getResource : function (key)
{
return this.option(_OJ_TRANSLATIONS_PREFIX + key);
},
/**
* <p>Determines whether the component is LTR or RTL.
*
* <p>Component responsibilities:
*
* <ul>
* <li>All components must determine directionality exclusively by calling this protected superclass method.
* (So that any future updates to the logic can be made in this one place.)</li>
* <li>Components that need to know the directionality must call this method at create-time
* and from <code class="prettyprint">refresh()</code>, and cache the value.
* <li>Components should not call this at other times, and should instead use the cached value. (This avoids constant DOM
* queries, and avoids any future issues with component reparenting (i.e. popups) if support for directional islands is added.)</li>
* </ul>
*
* <p>App responsibilities:
*
* <ul>
* <li>The app specifies directionality by setting the HTML <code class="prettyprint">"dir"</code> attribute on the
* <code class="prettyprint"><html></code> node. When omitted, the default is <code class="prettyprint">"ltr"</code>.
* (Per-component directionality / directional islands are not currently supported due to inadequate CSS support.)</li>
* <li>As with any DOM change, the app must <code class="prettyprint">refresh()</code> the component if the directionality changes dynamically.
* (This provides a hook for component housekeeping, and allows caching.)</li>
* </ul>
*
* @memberof oj.baseComponent
* @instance
* @protected
* @return {string} the reading direction, either <code class="prettyprint">"ltr"</code> or <code class="prettyprint">"rtl"</code>
* @default <code class="prettyprint">"ltr"</code>
*/
_GetReadingDirection: function( )
{
return oj.DomUtils.getReadingDirection();
},
/**
* <p>Notifies the component that its subtree has been connected to the document programmatically after the component has
* been created.
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_NotifyAttached: function()
{
this._propertyContext = null;
},
/**
* <p>Notifies the component that its subtree has been removed from the document programmatically after the component has
* been created.
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_NotifyDetached: function()
{
this._propertyContext = null;
},
/**
* <p>Notifies the component that its subtree has been made visible programmatically after the component has
* been created.
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_NotifyShown: function()
{
},
/**
* <p>Notifies the component that its subtree has been made hidden programmatically after the component has
* been created.
*
* @memberof oj.baseComponent
* @instance
* @protected
*/
_NotifyHidden: function()
{
},
/**
* <p>Determines whether this component is effectively disabled, i.e. it has its 'disabled' attribute set to true
* or it has been disabled by its ancestor component.
*
* @memberof oj.baseComponent
* @instance
* @protected
* @return {boolean} true if the component has been effectively disabled, false otherwise
*/
_IsEffectivelyDisabled: function()
{
return (this.options['disabled'] || this._ancestorDisabled) ? true : false;
},
/**
* <p>Sets the ancestor-provided disabled state on this component.
*
* @memberof oj.baseComponent
* @instance
* @private
* @param {boolean} disabled - true if this component is being disabled by its ancestor component, false otherwise
*/
__setAncestorComponentDisabled: function(disabled)
{
this._ancestorDisabled = disabled;
},
/**
* @memberof oj.baseComponent
* @instance
* @private
*/
_getTranslationSectionLoader: function()
{
var sectionNames = [];
var self = this;
var index = 0;
this._traverseWidgetHierarchy(
function(proto)
{
// retrive translation section name for the widget and all of its ancestors
// Since _GetTranslationsSectionName() is a protected method, we can only call it on the widget instance.
// For superclases, we will assume that their section names can only be their full widget name
var name = (index == 0) ? self._GetTranslationsSectionName() : proto.widgetFullName;
index++;
var section = oj.Translations.getComponentTranslations(name);
if (section != null && !$.isEmptyObject(section))
{
sectionNames.push(name);
}
}
);
var count = sectionNames.length;
if (count > 0)
{
return function()
{
// Optimize for the most common case where superclasses do not define translations
if (count == 1)
{
return oj.Translations.getComponentTranslations(sectionNames[0]);
}
else
{
var trs = {};
for (var i = count-1; i>=0; i--)
{
$.widget.extend(trs, oj.Translations.getComponentTranslations(sectionNames[i]));
}
return trs;
}
};
}
return null;
},
/**
* @memberof oj.baseComponent
* @instance
* @private
*/
_getDynamicPropertyContext: function()
{
if (!this._propertyContext)
{
var c = {};
this._propertyContext = c;
c['containers'] = _getSpecialContainerNames(this.element[0]);
}
return this._propertyContext;
},
/**
* @memberof oj.baseComponent
* @instance
* @private
*/
_setupDefaultOptions: function(originalDefaults, constructorOptions)
{
var options = this.options;
// Load component translations
var translationLoader = this._getTranslationSectionLoader();
var currVal = constructorOptions[_OJ_TRANSLATIONS_OPTION];
if (translationLoader != null && (currVal === undefined || $.isPlainObject(currVal) ))
{
_defineDynamicProperty(this, undefined, constructorOptions[_OJ_TRANSLATIONS_OPTION],
options, _OJ_TRANSLATIONS_OPTION, translationLoader);
}
// Load options specified with oj.Components.setDefaultOptions()
this._loadGlobalDefaultOptions(originalDefaults, constructorOptions)
},
/**
* @memberof oj.baseComponent
* @instance
* @private
*/
_loadGlobalDefaultOptions: function(originalDefaults, constructorOptions)
{
var options = this.options;
var defaults = {};
var widgetHierNames = [];
// walk up the widget hierarchy
this._traverseWidgetHierarchy(
function(proto)
{
widgetHierNames.push(proto.widgetName);
}
);
var allProperties = oj.Components.getDefaultOptions();
widgetHierNames.push('default');
// merge properties applicable to this component
for (var i = widgetHierNames.length-1; i>=0; i--)
{
var name = widgetHierNames[i];
var props = allProperties[name];
if (props !== undefined)
{
defaults = $.widget.extend(defaults, props);
}
}
if ($.isEmptyObject(defaults))
{
return;
}
var self = this;
var contextCallback = function()
{
return self._getDynamicPropertyContext();
};
for (var prop in defaults)
{
var val = constructorOptions[prop];
if (val === undefined || $.isPlainObject(val))
{
var defaultVal = defaults[prop];
if (defaultVal != null && defaultVal instanceof __ojDynamicGetter)
{
var callback = defaultVal.getCallback();
if ($.isFunction(callback))
{
_defineDynamicProperty(this, originalDefaults[prop], val, options, prop, callback, contextCallback);
}
else
{
oj.Logger.error("Dynamic getter for property %s is not a function", prop);
}
}
else
{
options[prop] = _mergeOptionLayers([originalDefaults[prop], defaultVal, val]);
}
}
}
},
/**
* @memberof oj.baseComponent
* @instance
* @private
*/
_traverseWidgetHierarchy: function(callback)
{
var proto = this.constructor.prototype;
while (proto != null && 'oj' === proto['namespace'])
{
callback(proto);
proto = Object.getPrototypeOf(proto);
}
}
});
// Remove base component from the jQuery prototype, so it could not be created
// directly by page authors
delete $['fn'][_BASE_COMPONENT];
/**
* <p>This method is our version of $.widget, i.e. the static initializer of a component such as ojButton.
* It calls that method, plus does any other static init we need.
*
* TODO:
* - Consider moving this method into its own file.
* - For base param, make the type oj.baseComponent rather than Object, but need to declare that as a type first. Review how that's done.
*
* @private
* @param {string} name typically of the form "oj.ojMenu"
* @param {Object} base NOT optional (unlike JQUI)
* @param {Object} prototype
* @param {boolean=} isHidden - if true, indicates that the component name should not
* be available on jQuery prototype
*/
oj.__registerWidget = function(name, base, prototype, isHidden)
{
$.widget( name, base, prototype );
if (isHidden)
{
var globalName = name.split('.')[1];
delete $['fn'][globalName];
}
// create single-OJ pseudo-selector for component, e.g. ":oj-menu", in addition to the ":oj-ojMenu" that $.widget() creates.
// for private components it will begin with an underscore, e.g., ":_oj-radio"
if (name.substring(0, 5) === "oj.oj" || name.substring(0, 6) === "oj._oj")
{
var nameArray = name.split( "." ); // ["oj", "ojMenu"], ["oj", "_ojRadio"]
var namespace = nameArray[ 0 ]; // "oj"
var simpleName = nameArray [ 1 ]; // "ojMenu", "_ojRadio"
var fullName = namespace + "-" + simpleName; // "oj-ojMenu", "oj-_ojRadio"
var isPrivate = simpleName.substring(0, 1) === "_";
// if private, make the single-oj pseudo-selector start with an underscore, like this -> "_oj-radio"
var modifiedFullName; // "oj-Menu", "_oj-Radio". Lowercased below.
if (isPrivate)
{
modifiedFullName = "_" + namespace + "-" + simpleName.substring(3);
}
else
{
modifiedFullName = namespace + "-" + simpleName.substring(2);
}
// Capitalization doesn't seem to matter with JQ pseudos, e.g. for the existing double-oj pseudo, both $(":oj-ojMenu") and $(":oj-ojmenu") work.
// So, follow JQUI's pattern of using toLowerCase here, which will lowercase not only the "M' in "Menu", but also any camelcased chars after that.
$.expr[ ":" ][ modifiedFullName.toLowerCase() ] = function( elem ) {
return !!$.data( elem, fullName );
};
}
};
/**
* @param {Object} self
* @param {Object|undefined} originalDefaultValue
* @param {?Object} constructorValue
* @param {!Object} options
* @param {string} prop
* @param {Function} getter
* @param {Function=} contextCallback
* @private
*/
function _defineDynamicProperty(self, originalDefaultValue, constructorValue, options, prop, getter, contextCallback)
{
var override = constructorValue;
var replaced = false;
var overriddenSubkeys = {};
delete options[prop];
Object.defineProperty(options, prop,
{
'get': function()
{
// Once the option is replaced, we no longer merge in defaults
if (replaced)
{
return override;
}
if (self._settingNestedKey != null)
{
// The getter is getting called from the option() method that will be mutating the current
// object. We need to return only the override portion in this case to avoid the defaults being
// reapplied as an override
return override;
}
var defaultVal = getter(contextCallback? contextCallback() : prop);
return _mergeOptionLayers([originalDefaultValue, defaultVal, override], overriddenSubkeys)
},
'set': function(value)
{
override = value;
if (self._settingNestedKey != null)
{
overriddenSubkeys[self._settingNestedKey] = true;
}
else // The entire option has been replaced
{
replaced = true;
}
},
'enumerable' : true
}
);
};
/**
* @private
*/
function _getSpecialContainerNames(elem)
{
var containers = [];
while (elem)
{
var ga = elem.getAttribute;
var name = ga ? ga.call(elem, oj.Components._OJ_CONTAINER_ATTR) : null;
if (name != null)
{
containers.push(name);
}
elem = elem.parentNode;
}
return containers;
};
/**
* @private
*/
function _storeWidgetName(element, widgetName)
{
var data = element.data(_OJ_WIDGET_NAMES_DATA);
if (!data)
{
data = [];
element.data(_OJ_WIDGET_NAMES_DATA, data);
}
if (data.indexOf(widgetName) < 0)
{
data.push(widgetName);
}
}
/**
* @private
*/
function _removeWidgetName(element, widgetName)
{
var data = element.data(_OJ_WIDGET_NAMES_DATA);
if (data)
{
var index = data.indexOf(widgetName);
if (index >= 0)
{
data.splice(index, 1);
if (data.length === 0)
{
element.removeData(_OJ_WIDGET_NAMES_DATA);
}
}
}
}
/**
* @private
* @param {Array} values - values to merge
* @param {Object=} overriddenSubkeys subkeys where the merging should not occur, i.e.
* the value from corresponsing subkey on the last element of values array should win
*/
function _mergeOptionLayers(values, overriddenSubkeys)
{
var result = undefined;
for (var i=0; i<values.length; i++)
{
var value = values[i];
if (value !== undefined )
{
if ($.isPlainObject(value))
{
var input = $.isPlainObject(result) ? [result, value] : [value];
// The last object (overrides) is always fully merged in
result = _mergeObjectsWithExclusions({}, input, (i == values.length - 1)? null: overriddenSubkeys, null);
}
else
{
result = value;
}
}
}
return result;
}
/**
* @private
*/
function _mergeObjectsWithExclusions(target, input, ignoreSubkeys, basePath)
{
var inputLength = input.length;
for (var inputIndex = 0; inputIndex < inputLength; inputIndex++)
{
var source = input[inputIndex];
var keys = Object.keys(source);
for (var i=0; i<keys.length; i++)
{
var key = keys[i];
var path = (ignoreSubkeys == null)? null: (basePath == null ? key : basePath + '.' + key);
// Ignore all sources when the current path is registered in ignoreSubkeys
if (ignoreSubkeys == null || !ignoreSubkeys[path])
{
var value = source[key];
if (value !== undefined)
{
if ($.isPlainObject(value))
{
var params = $.isPlainObject(target[key])? [target[key], value]: [value];
target[key] = _mergeObjectsWithExclusions({}, params, ignoreSubkeys, path);
}
else
{
target[key] = value;
}
}
}
}
}
return target;
}
/**
* @private
*/
var _OJ_TRANSLATIONS_OPTION = "translations";
/**
* @private
*/
var _OJ_TRANSLATIONS_PREFIX = _OJ_TRANSLATIONS_OPTION + ".";
/**
* @private
*/
function _returnTrue()
{
return true;
};
/**
* @private
*/
var _OJ_COMPONENT_EVENT_OVERRIDES =
{
preventDefault : function ()
{
this.isDefaultPrevented = _returnTrue;
},
stopPropagation : function ()
{
this.isPropagationStopped = _returnTrue;
},
stopImmediatePropagation : function ()
{
this.isImmediatePropagationStopped = _returnTrue;
}
};
/**
* Returns an object with context for the given child DOM node. This will always contain the subid for the node,
* defined as the 'subId' property on the context object. Additional component specific information may also be included.
*
* For more details on returned objects, see <a href="#contextobjects-section">context objects</a>.
*
* @ojfragment nodeContextDoc
* @memberof oj.baseComponent
*/
/**
* The child DOM node
*
* @ojfragment nodeContextParam
* @memberof oj.baseComponent
*/
/**
* The context for the DOM node, or <code class="prettyprint">null</code> when none is found.
*
* @ojfragment nodeContextReturn
* @memberof oj.baseComponent
*/
/**
* // Foo is ojInputNumber, ojInputDate, etc.
* // Returns {'subId': oj-foo-subid, 'property1': componentSpecificProperty, ...}
* var context = $( ".selector" ).ojFoo( "getContextByNode", nodeInsideComponent );
*
* @ojfragment nodeContextExample
* @memberof oj.baseComponent
*/
Source: src/main/javascript/oracle/oj/ojcomponentcore/jqueryui-base.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01