Source: src/main/javascript/oracle/oj/ojconveyorbelt/ojconveyorbelt.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/**
 * 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