/**
* Copyright (c) 2014, Oracle and/or its affiliates.
* All rights reserved.
*/
/**
* @ojcomponent oj.ojConveyorBelt
* @augments oj.baseComponent
* @since 0.6
*
* @classdesc
* <h3 id="conveyorBeltOverview-section">
* JET ConveyorBelt Component
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#conveyorBeltOverview-section"></a>
* </h3>
*
* <p>Description: Container component that manages overflow for its child
* components and allows scrolling among them.
*
* <p>A JET ConveyorBelt can be created from any
* <code class="prettyprint"><div></code> element that contains
* multiple child elements at the same level to scroll among. The root must be
* a <code class="prettyprint"><div></code> element because the ConveyorBelt
* will create additional DOM elements between the root and the scrollable child
* elements.
* <p>The size of the ConveyorBelt must somehow be constrained in order for
* there to be overflow to manage, for example by specifying CSS
* <code class="prettyprint">max-width</code> or
* <code class="prettyprint">max-height</code>.
* <p>If the elements to be scrolled among are direct children of the
* ConveyorBelt, then ConveyorBelt will ensure that they are laid out
* appropriately for its orientation. However, if the elements to be scrolled
* among are contained by a single nested descendant element, the
* <code class="prettyprint">contentParent</code>, then it is up to calling code
* to ensure that the elements are laid out appropriately. For example,
* elements can be forced horizontal by using CSS
* <code class="prettyprint">white-space:nowrap</code>, or vertical by using
* <code class="prettyprint">display:block</code>, before creating the
* ConveyorBelt.
*
* <pre class="prettyprint">
* <code>
* <div id="conveyorBelt" style="max-width:100px;"
* data-bind="ojComponent: {component: 'ojConveyorBelt'}">
* <button id="button1">Alpha</button>
* <button id="button2">Beta</button>
* <button id="button3">Gamma</button>
* <button id="button4">Delta</button>
* <button id="button5">Epsilon</button>
* <button id="button6">Zeta</button>
* </div>
* </code></pre>
*
*
* <h3 id="touch-section">
* Touch End User Information
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#touch-section"></a>
* </h3>
*
* {@ojinclude "name":"touchDoc"}
*
*
* <h3 id="keyboard-section">
* Keyboard End User Information
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#keyboard-section"></a>
* </h3>
*
* {@ojinclude "name":"keyboardDoc"}
*
*
* <h3 id="keyboard-appdev-section">
* Keyboard Application Developer Information
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#keyboard-appdev-section"></a>
* </h3>
*
* <p>Providing keyboard support for the items in the conveyor belt is the
* responsibility of the application developer, if the items do not already
* support keyboard interaction. This could be done, for example, by specifying
* <code class="prettyprint">tabindex</code> on each item to enable tab
* navigation. Alternatively, this could be done by adding a keyboard listener
* and responding to key events, like pressing the arrow keys.
*
*
* <h3 id="accessibility-section">
* Accessibility
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#accessibility-section"></a>
* </h3>
*
* <p>ConveyorBelt itself does nothing special for accessibility.
* It is the responsibility of the application developer to make the items in
* the conveyor accessible. Sighted keyboard-only users need to be able to
* access the items in the conveyor just by using the keyboard.
* It is up to the child items of the ConveyorBelt to support keyboard
* navigation. If child items support tab navigation, the browser may scroll
* them into view when they receive focus. If child items support other forms
* of keyboard navigation, for example by using the arrow keys, it is up to the
* child items to scroll themselves into view. This may be done, for example,
* by calling the DOM function <code class="prettyprint">focus()</code> or
* <code class="prettyprint">scrollIntoView()</code> on the item.
* ConveyorBelt will be aware of tab based or programmatic scrolling and will
* honor it, updating itself to toggle visibility of the overflow indicators as
* needed.
*
*
* <h3 id="rtl-section">
* Reading direction
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#rtl-section"></a>
* </h3>
*
* <p>As with any JET component, in the unusual case that the directionality
* (LTR or RTL) changes post-init, the conveyorBelt must be
* <code class="prettyprint">refresh()</code>ed.
*
*
* <h3 id="pseudos-section">
* Pseudo-selectors
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#pseudos-section"></a>
* </h3>
*
* <p>The <code class="prettyprint">:oj-conveyorbelt</code> pseudo-selector can
* be used in jQuery expressions to select JET ConveyorBelt. For example:
*
* <pre class="prettyprint">
* <code>$( ":oj-conveyorbelt" ) // selects all JET ConveyorBelts on the page
* $myEventTarget.closest( ":oj-conveyorbelt" ) // selects the closest ancestor that is a JET ConveyorBelt
* </code></pre>
*
*
* <h3 id="jqui2jet-section">
* JET for jQuery UI developers
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#jqui2jet-section"></a>
* </h3>
*
* <p>Event names for all JET components are prefixed with "oj", instead of
* component-specific prefixes like "conveyorBelt".
*
* <!-- - - - - Above this point, the tags are for the class.
* Below this point, the tags are for the constructor (initializer). - - - - - - -->
*
* @desc Creates a JET ConveyorBelt.
* @example <caption>Initialize the conveyorBelt with no options specified:</caption>
* $( ".selector" ).ojConveyorBelt();
*
* @example <caption>Initialize the conveyorBelt with some options specified:</caption>
* $( ".selector" ).ojConveyorBelt( { "orientation": "vertical" } );
*
* @example <caption>Initialize the conveyorBelt via the JET
* <code class="prettyprint">ojComponent</code> binding:</caption>
* <div id="conveyorBelt" data-bind="ojComponent: { component: 'ojConveyorBelt', orientation: 'vertical'}">
*/
(function()
{
oj.__registerWidget("oj.ojConveyorBelt", $['oj']['baseComponent'],
{
defaultElement: "<div>",
widgetEventPrefix: "oj",
options:
{
/**
* Specify the orientation of the conveyorBelt.
*
* @expose
* @memberof! oj.ojConveyorBelt
* @instance
* @type {string}
* @ojvalue {string} "horizontal" Orient the conveyorBelt horizontally.
* @ojvalue {string} "vertical" Orient the conveyorBelt vertically.
* @default <code class="prettyprint">"horizontal"</code>
*
* @example <caption>Initialize the conveyorBelt with the
* <code class="prettyprint">orientation</code> option specified:</caption>
* $( ".selector" ).ojConveyorBelt( { "orientation": "vertical" } );
*
* @example <caption>Get or set the <code class="prettyprint">orientation</code>
* option after initialization:</caption>
* // getter
* var orientation = $( ".selector" ).ojConveyorBelt( "option", "orientation" );
*
* // setter
* $( ".selector" ).ojConveyorBelt( "option", "orientation", "vertical" );
*/
orientation: "horizontal",
/**
* Specify the selector of the descendant DOM element in the conveyorBelt
* that directly contains the items to scroll among.
*
* <p>This option is <code class="prettyprint">null</code> by default,
* meaning that the items to scroll among are direct children of the
* conveyorBelt DOM element. In some cases, the items to scroll among
* are not direct children of the conveyorBelt DOM element, but are instead
* nested in a descendant DOM element. In such cases, this option should be
* specified to point to the descendant DOM element whose direct children
* are the items to scroll among. For example, if the items to scroll
* among are buttons in a buttonset, the buttons are direct children of
* the DOM element representing the buttonset. The buttonset would be
* the direct child of the conveyorBelt. If the
* <code class="prettyprint">id</code> of the buttonset DOM element were
* <code class="prettyprint">'myContentDiv'</code>, then contentParent
* would be specified as <code class="prettyprint">'#myContentDiv'</code>.
*
* <p><b>WARNING:</b> The selector specified for this option should match
* only a single descendant DOM element. If multiple elements are matched,
* then only the first one will be used. Applications should not depend on
* this behavior because we reserve the right to change it in the future in
* order to allow and use multiple matching elements.
*
* @expose
* @memberof! oj.ojConveyorBelt
* @instance
* @type {?string}
* @default <code class="prettyprint">null</code>
*
* @example <caption>Initialize the conveyorBelt with the
* <code class="prettyprint">contentParent</code> option specified:</caption>
* // HTML
* <div> // conveyorBelt DOM element
* <div id="myContentDiv"> // contentParent DOM element
* <button>Item 1</button> // items to scroll among...
* <button>Item 2</button>
* <button>Item 3</button>
* <button>Item 4</button>
* <button>Item 5</button>
* </div>
* </div>
*
* // JS
* $( ".selector" ).ojConveyorBelt( { "contentParent": "#myContentDiv" } );
*
* @example <caption>Get or set the <code class="prettyprint">contentParent</code>
* option after initialization:</caption>
* // getter
* var contentParent = $( ".selector" ).ojConveyorBelt( "option", "contentParent" );
*
* // setter
* $( ".selector" ).ojConveyorBelt( "option", "contentParent", "#myContentDiv" );
*/
contentParent: null
/**
* To avoid tight coupling between a ConveyorBelt and its contents, JET
* ConveyorBelt does not support the <code class="prettyprint">disabled</code>
* option.
*
* <p><b>WARNING:</b> Applications should not depend on this behavior
* because we reserve the right to change it in the future in order to
* support <code class="prettyprint">disabled</code> and propagate it to
* child components of ConveyorBelt.
*
* @member
* @name disabled
* @memberof oj.ojConveyorBelt
* @instance
* @type {boolean}
* @default <code class="prettyprint">false</code>
*/
// disabled option declared in superclass, but we still want the above API doc
// Events
/**
* Triggered when the conveyorBelt is created.
*
* @event
* @name create
* @memberof oj.ojConveyorBelt
* @instance
* @property {Event} event <code class="prettyprint">jQuery</code> event object
* @property {Object} ui Currently empty
*
* @example <caption>Initialize the conveyorBelt with the <code class="prettyprint">create</code> callback specified:</caption>
* $( ".selector" ).ojConveyorBelt({
* "create": function( event, ui ) {}
* });
*
* @example <caption>Bind an event listener to the <code class="prettyprint">ojcreate</code> event:</caption>
* $( ".selector" ).on( "ojcreate", function( event, ui ) {} );
*/
// create event declared in superclass, but we still want the above API doc
},
/**
* After _ComponentCreate and _AfterCreate,
* the widget should be 100% set up. this._super should be called first.
* @override
* @protected
* @instance
* @memberof! oj.ojConveyorBelt
*/
_ComponentCreate : function () // Override of protected base class method.
{
//call superclass first
this._super();
var elem = this.element;
elem.addClass("oj-conveyorbelt oj-component");
//FIX BUG 18943900: log warning message when "disabled" option set
var options = this.options;
if (options.disabled)
{
oj.Logger.warn(_WARNING_DISABLED_OPTION);
}
//FIX BUG 20012366: remove override of _init() and call _setup() from here
this._setup(true);
},
// This method currently runs at create, init, and refresh time (since refresh() is called by _init()).
/**
* Refreshes the visual state of the conveyorBelt. JET components require a
* <code class="prettyprint">refresh()</code> or re-init after the DOM is
* programmatically changed underneath the component.
*
* <p>This method does not accept any arguments.
*
* @expose
* @memberof! oj.ojConveyorBelt
* @instance
*
* @example <caption>Invoke the <code class="prettyprint">refresh</code> method:</caption>
* $( ".selector" ).ojConveyorBelt( "refresh" );
*/
refresh: function() // Override of public base class method.
{
this._super();
//if RTL has changed, just destroy and recreate the ConveyorBeltCommon
var bRTL = (this._GetReadingDirection() === "rtl");
var bRecreate = (bRTL !== this._bRTL);
if (bRecreate)
{
this._destroyCBCommon();
}
this._setup(bRecreate);
},
/**
* Notifies the component that its subtree has been made visible
* programmatically after the component has been created.
* @memberof! oj.ojConveyorBelt
* @instance
* @protected
* @override
*/
_NotifyShown: function()
{
this._super();
//FIX BUG 19697736: perform a deferred layout
if (this._needsSetup)
{
this._setup(this._needsSetup[0]);
}
},
/**
* Notifies the component that its subtree has been connected to the document
* programmatically after the component has been created.
* @memberof! oj.ojConveyorBelt
* @instance
* @protected
* @override
*/
_NotifyAttached: function()
{
this._super();
//FIX BUG 19697736: perform a deferred layout
if (this._needsSetup)
{
this._setup(this._needsSetup[0]);
}
},
// isInit is true for init (create and re-init), false for refresh
/**
* Setup the conveyorBelt.
* @param {boolean} isInit true if _setup is called from _init(), false
* if called from refresh()
* @memberof! oj.ojConveyorBelt
* @instance
* @private
*/
_setup: function(isInit) // Private, not an override (not in base class).
{
//FIX BUG 19697736: if conveyor is detached or hidden, we can't layout
//correctly, so defer layout until conveyor is attached or shown
if (!this._canCalculateSizes())
{
//want a true value of isInit to take precedence over a false value
var oldIsInit = false;
if (this._needsSetup)
{
oldIsInit = this._needsSetup[0];
}
this._needsSetup = [isInit || oldIsInit];
return;
}
this._needsSetup = null;
this._bRTL = (this._GetReadingDirection() === "rtl");
var elem = this.element;
var options = this.options;
if (isInit)
{
if (!this._cbCommon)
{
var orientation = options.orientation;
var prevStyleClass = null;
var nextStyleClass = null;
var prevIcon = null;
var nextIcon = null;
var animateScrollFunc = null;
if (orientation !== "vertical")
{
prevStyleClass = "oj-enabled oj-conveyorbelt-overflow-indicator oj-start oj-default";
nextStyleClass = "oj-enabled oj-conveyorbelt-overflow-indicator oj-end oj-default";
prevIcon = this._createIcon("oj-conveyorbelt-overflow-icon oj-start");
nextIcon = this._createIcon("oj-conveyorbelt-overflow-icon oj-end");
animateScrollFunc = this._animateScrollLeft;
}
else
{
prevStyleClass = "oj-enabled oj-conveyorbelt-overflow-indicator oj-top oj-default";
nextStyleClass = "oj-enabled oj-conveyorbelt-overflow-indicator oj-bottom oj-default";
prevIcon = this._createIcon("oj-conveyorbelt-overflow-icon oj-top");
nextIcon = this._createIcon("oj-conveyorbelt-overflow-icon oj-bottom");
animateScrollFunc = this._animateScrollTop;
}
var buttonInfo = {};
buttonInfo.prevButtonStyleClass = prevStyleClass;
buttonInfo.nextButtonStyleClass = nextStyleClass;
buttonInfo.prevButtonIcon = prevIcon;
buttonInfo.nextButtonIcon = nextIcon;
var callbackInfo = {};
callbackInfo.addResizeListener = oj.DomUtils.addResizeListener;
callbackInfo.removeResizeListener = oj.DomUtils.removeResizeListener;
//disable scroll animation during testing
if (oj.Config.getAutomationMode() !== "enabled")
{
callbackInfo.scrollFunc = animateScrollFunc;
}
var contentParentElem = null;
if (options.contentParent)
{
//only use the first result returned from the contentParent selector
contentParentElem = $(options.contentParent)[0];
}
this._cbCommon = new ConveyorBeltCommon(
elem[0],
orientation,
contentParentElem,
this._bRTL,
buttonInfo,
callbackInfo);
}
}
var cbCommon = this._cbCommon;
cbCommon.setup(isInit);
if (isInit)
{
var children = elem.find(".oj-conveyorbelt-overflow-indicator");
this._setupButtonMouseStyles(children);
}
},
/**
* Destroy the conveyorBelt.
* @memberof! oj.ojConveyorBelt
* @instance
* @override
* @protected
*/
_destroy: function() // Override of protected base class method.
{
this._destroyCBCommon();
var elem = this.element;
elem.removeClass("oj-conveyorbelt oj-component");
//call superclass last
this._super();
},
/**
* Set an option on the conveyorBelt.
* @memberof! oj.ojConveyorBelt
* @instance
* @override
* @protected
*/
_setOption: function(key, value, flags) // Override of protected base class method.
// Method name needn't be quoted since is in externs.js.
{
var bRecreate = false;
switch (key)
{
//when changing containerParent or orientation, just destroy and recreate
//the ConveyorBeltCommon
case "containerParent":
case "orientation":
bRecreate = true;
break;
case "disabled":
//FIX BUG 18943900: log warning message when "disabled" option set
oj.Logger.warn(_WARNING_DISABLED_OPTION);
break;
}
//if recreating, destroy the ConveyorBeltCommon before calling superclass
//_setOption
if (bRecreate)
{
this._destroyCBCommon();
}
this._super(key, value, flags);
//if recreating, setup the new ConveyorBeltCommon after calling superclass
//_setOption
if (bRecreate)
{
this._setup(true);
}
},
/**
* Destroy the ConveyorBeltCommon.
* @memberof! oj.ojConveyorBelt
* @instance
* @private
*/
_destroyCBCommon: function()
{
var cbCommon = this._cbCommon;
if (cbCommon)
{
//FIX BUG 20018890: detach mouse listeners from overflow indicators
//before destroying cbCommon in order to avoid memory leaks
var elem = this.element;
var children = elem.find(".oj-conveyorbelt-overflow-indicator");
children.off(this.eventNamespace);
cbCommon.destroy();
}
this._cbCommon = null;
},
/**
* Determine whether the conveyorBelt can calculate sizes (when it is
* attached to the page DOM and not hidden).
* @returns {boolean} true if sizes can be calculated, false if not
* @memberof! oj.ojConveyorBelt
* @instance
* @private
*/
_canCalculateSizes: function()
{
var div = document.createElement("div");
var style = div.style;
style.width = "10px";
style.height = "10px";
var elem = this.element[0];
elem.appendChild(div);
var bCanCalcSizes = false;
try {
bCanCalcSizes = div.offsetWidth > 0 && div.offsetHeight > 0;
}
catch (e) {
//do nothing
}
elem.removeChild(div);
return bCanCalcSizes;
},
/**
* Setup mouse listeners to change button styles.
* @param {Object} element jQuery element to affect
* @memberof! oj.ojConveyorBelt
* @private
*/
_setupButtonMouseStyles: function(element)
{
element
.on("mousedown" + this.eventNamespace, function( event ) {
var currTarget = event.currentTarget;
$(currTarget).addClass("oj-active");
})
.on("mouseup" + this.eventNamespace, function( event ) {
var currTarget = event.currentTarget;
$(currTarget).removeClass("oj-active");
})
.on("mouseenter" + this.eventNamespace, function( event ) {
var currTarget = event.currentTarget;
$(currTarget).addClass("oj-hover");
$(currTarget).removeClass("oj-default");
})
.on("mouseleave" + this.eventNamespace, function( event ) {
var currTarget = event.currentTarget;
$(currTarget).removeClass("oj-hover");
$(currTarget).removeClass("oj-active");
$(currTarget).addClass("oj-default");
});
},
/**
* Create a DOM element for an icon.
* @param {string} iconStyleClass Style class for the icon
* @memberof! oj.ojConveyorBelt
* @private
*/
_createIcon: function(iconStyleClass)
{
var span = document.createElement("span");
span.setAttribute("class", "oj-component-icon " + iconStyleClass);
return span;
},
/**
* Animate setting the scrollLeft DOM property.
* @param {Element} elem DOM element to scroll
* @param {number} value Scroll value
* @param {number} duration Duration of animation, in ms
* @param {function()} onEndFunc Function to call when the animation ends
* @memberof! oj.ojConveyorBelt
* @private
*/
_animateScrollLeft: function(elem, value, duration, onEndFunc)
{
var props = {};
props["scrollLeft"] = value;
//use swing instead of easeInOutCubic because easeInOutCubic isn't found
//when running in the cookbook
$(elem).animate(props, duration, "swing", onEndFunc);
},
/**
* Animate setting the scrollTop DOM property.
* @param {Element} elem DOM element to scroll
* @param {number} value Scroll value
* @param {number} duration Duration of animation, in ms
* @param {function()} onEndFunc Function to call when the animation ends
* @memberof! oj.ojConveyorBelt
* @private
*/
_animateScrollTop: function(elem, value, duration, onEndFunc)
{
var props = {};
props["scrollTop"] = value;
//use swing instead of easeInOutCubic because easeInOutCubic isn't found
//when running in the cookbook
$(elem).animate(props, duration, "swing", onEndFunc);
},
/**
* Return the subcomponent node represented by the documented locator
* attribute values.
* Test authors should target sub elements using the following names:
* <ul>
* <li><b>oj-conveyorbelt-start-overflow-indicator</b>: the start overflow indicator of a
* horizontal ConveyorBelt</li>
* <li><b>oj-conveyorbelt-end-overflow-indicator</b>: the end overflow indicator of a
* horizontal ConveyorBelt</li>
* <li><b>oj-conveyorbelt-top-overflow-indicator</b>: the top overflow indicator of a
* vertical ConveyorBelt</li>
* <li><b>oj-conveyorbelt-bottom-overflow-indicator</b>: the bottom overflow indicator of
* a vertical ConveyorBelt</li>
* </ul>
* @expose
* @memberof! oj.ojConveyorBelt
* @instance
* @override
* @param {Object} locator An Object containing at minimum a subId property
* whose value is a string, documented by the component, that allows
* the component to look up the subcomponent associated with that
* string. It contains:<p>
* component: optional - in the future there may be more than one
* component contained within a page element<p>
* subId: the string, documented by the component, that the component
* expects in getNodeBySubId to locate a particular subcomponent
* @returns {Element|null} the subcomponent located by the subId string passed
* in locator, if found.<p>
*/
getNodeBySubId: function(locator)
{
if (locator == null)
{
return this.element ? this.element[0] : null;
}
var subId = locator['subId'];
if (subId === "oj-conveyorbelt-start-overflow-indicator") {
return this.widget().find(".oj-conveyorbelt-overflow-indicator.oj-start")[0];
}
if (subId === "oj-conveyorbelt-end-overflow-indicator") {
return this.widget().find(".oj-conveyorbelt-overflow-indicator.oj-end")[0];
}
if (subId === "oj-conveyorbelt-top-overflow-indicator") {
return this.widget().find(".oj-conveyorbelt-overflow-indicator.oj-top")[0];
}
if (subId === "oj-conveyorbelt-bottom-overflow-indicator") {
return this.widget().find(".oj-conveyorbelt-overflow-indicator.oj-bottom")[0];
}
// Non-null locators have to be handled by the component subclasses
return null;
}
// start jsdoc fragments /////////////////////////////////////////////////////
/**
* <table class="keyboard-table">
* <thead>
* <tr>
* <th>Target</th>
* <th>Gesture</th>
* <th>Action</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>ConveyorBelt</td>
* <td><kbd>Swipe</kbd></td>
* <td>Transition to an adjacent logical page of child items.</td>
* </tr>
* <tr>
* <td>Navigation Arrow</td>
* <td><kbd>Tap</kbd></td>
* <td>Transition to an adjacent logical page of child items.</td>
* </tr>
* </tbody>
* </table>
*
* @ojfragment touchDoc - Used in touch gesture section of classdesc, and standalone gesture doc
* @memberof oj.ojConveyorBelt
*/
/**
* <p>ConveyorBelt is for layout only and does not directly support keyboard
* interaction. It is up to the application to provide keyboard support.
*
* @ojfragment keyboardDoc - Used in keyboard section of classdesc, and standalone gesture doc
* @memberof oj.ojConveyorBelt
*/
// end jsdoc fragments ///////////////////////////////////////////////////////
}); // end of oj.__registerWidget
// start static members and functions //////////////////////////////////////////
//FIX BUG 18943900: log warning message when "disabled" option set
var _WARNING_DISABLED_OPTION = "JET ConveyorBelt: 'disabled' option not supported";
// end static members and functions ////////////////////////////////////////////
}()); // end of ConveyorBelt wrapper function
Source: src/main/javascript/oracle/oj/ojconveyorbelt/ojconveyorbelt.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01