Source: src/main/javascript/oracle/oj/ojselectcombobox/ojcombobox.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/**
 * Copyright (c) 2014, Oracle and/or its affiliates.
 * All rights reserved.
 */

  /**
   * @ojcomponent oj.ojCombobox
   * @augments oj.editableValue
   * @since 0.6
   *
   * @classdesc
   * <h3 id="comboboxOverview-section">
   *   JET Combobox Component
   *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#comboboxOverview-section"></a>
   * </h3>
   *
   * <p>Description: JET Combobox enhances a html input and datalist element into a Combobox that supports
   * single-select, multi-select, free text input, and search filtering.</p>
   *
   * <p>A JET Combobox can be created with the following markup. By default, it creates a single-select
   * Combobox. The 'multiple' option can be specified to change it to a multi-select Combobox.</p>
   *
   * <pre class="prettyprint">
   * <code>
   * <input list="items" data-bind="ojComponent: {component: 'ojCombobox', multiple: true}"/>
   * <datalist id="items">
   *   <option value="option 1">option 1</option>
   *   <option value="option 2">option 2</option>
   *   <option value="option 3">option 3</option>
   *   <option value="option 4">option 4</option>
   * </datalist>
   * </code></pre>
   *
   * <p>Please note that datalist is not supported in IE 9. To create a JET Combobox that works across browsers
   * including IE 9, please use the <code class="prettyprint">options</code> array to provide the option items.</p>
   *
   * <pre class="prettyprint">
   * <code>
   * <input data-bind="ojComponent: {component: 'ojCombobox', options: 
   *                                     [{value: 'option1', label: 'option1'}, {value: 'option2', label: 'option2'}]}"/>
   * </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="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 Combobox must be <code class="prettyprint">refresh()</code>ed.</p>
   *
   *
   * <h3 id="pseudos-section">
   *   Pseudo-selectors
   *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#pseudos-section"></a>
   * </h3>
   *
   * <p>The <code class="prettyprint">:oj-combobox</code> pseudo-selector can be used in jQuery expressions to select JET Combobox.  For example:</p>
   *
   * <pre class="prettyprint">
   * <code>$( ":oj-combobox" ) // selects all JET Combobox on the page
   * $myEventTarget.closest( ":oj-combobox" ) // selects the closest ancestor that is a JET Combobox
   * </code></pre>
   *
   * <h3 id="a11y-section">
   *   Accessibility
   *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#a11y-section"></a>
   * </h3>
   * <p>
   * It is up to the application developer to associate the label to the input component.
   * For combobox, you should put an <code>id</code> on the input, and then set 
   * the <code>for</code> attribute on the label to be the input's id.
   * </p>
   * <h3 id="label-section">
   *   Label and Combobox
   *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#label-section"></a>
   * </h3>
   * <p>
   * For accessibility, you should associate a label element with the input
   * by putting an <code>id</code> on the input, and then setting the 
   * <code>for</code> attribute on the label to be the input's id.
   * </p>
   * <p>
   * The component will decorate its associated label with required and help 
   * information, if the <code>required</code> and <code>help</code> options are set. 
   * </p>
   * <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 "Combobox".</p>
   *
   * @desc Creates a JET Combobox.
   * @example <caption>Initialize the Combobox with no options specified:</caption>
   * $( ".selector" ).ojCombobox();
   *
   * @example <caption>Initialize the Combobox with some options:</caption>
   * $( ".selector" ).ojCombobox( { "multiple": true, "placeholder": "Select multiple values." } );
   *
   * @example <caption>Initialize the Combobox via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
   * <div id="combobox" data-bind="ojComponent: { component: 'ojCombobox',
   *                                                    multiple: true}">
   */
  oj.__registerWidget("oj.ojCombobox", $['oj']['editableValue'],
  {
    defaultElement : "<input>",
    widgetEventPrefix : "oj",
    options :
    {
      /** 
       * A converter instance that duck types {@link oj.Converter}. Or an object literal containing 
       * the following properties. 
       * <p>
       * When <code class="prettyprint">converter</code> option changes due to programmatic 
       * intervention, the component performs various tasks based on the current state it is in. </br>
       * 
       * <h4>Steps Performed Always</h4>
       * <ul>
       * <li>Any cached converter instance is cleared and new converter created. The converter hint is 
       * pushed to messaging. E.g., notewindow displays the new hint(s).
       * </li>
       * </ul>
       * 
       * <h4>Running Validation</h4>
       * <ul>
       * <li>if component is valid when <code class="prettyprint">converter</code> option changes, the 
       * display value is refreshed.</li>
       * <li>if component is invalid and is showing messages -
       * <code class="prettyprint">messagesShown</code> option is non-empty, when 
       * <code class="prettyprint">converter</code> option changes, then all messages generated by the 
       * component are cleared and full validation run using its current display value. 
       * <ul>
       *   <li>if there are validation errors, then <code class="prettyprint">value</code> 
       *   option is not updated, and the errors pushed to <code class="prettyprint">messagesShown</code>
       *   option. The display value is not refreshed in this case. </li>
       *   <li>if no errors result from the validation, <code class="prettyprint">value</code> 
       *   option is updated; page author can listen to the <code class="prettyprint">optionChange</code> 
       *   event on the <code class="prettyprint">value</code> option to clear custom errors. The 
       *   display value is refreshed with the formatted value provided by converter.</li>
       * </ul>
       * </li>
       * <li>if component is invalid and has deferred messages -  
       * <code class="prettyprint">messagesHidden</code> option is non-empty, when 
       * <code class="prettyprint">converter</code> option changes, then the display value is 
       * refreshed with the formatted value provided by converter.</li>
       * </ul>
       * </p>
       * 
       * <h4>Clearing Messages</h4>
       * <ul>
       * <li>When component messages are cleared in the cases described above, messages created by 
       * the component that are present in both <code class="prettyprint">messagesHidden</code> and 
       * <code class="prettyprint">messagesShown</code> options are cleared.</li>
       * <li><code class="prettyprint">messagesCustom</code> option is not cleared. Page authors can 
       * choose to clear it explicitly when setting the converter option.</li>
       * </ul>
       * </p>
       * 
       * @property {string} type - the conveter type registered with the oj.ConverterFactory. 
       * Supported type is 'number'. See {@link oj.ConverterFactory} for details. <br/>
       * E.g., <code class="prettyprint">{converter: {type: 'number'}</code>
       * @property {Object=} options - optional Object literal of options that the converter expects. 
       * See {@link oj.IntlNumberConverter} for options supported by the number converter. 
       * E.g., <code class="prettyprint">{converter: {type: 'number', options: {style: 'decimal'}}</code>
       * 
       * @expose 
       * @access public
       * @instance
       * @memberof! oj.ojCombobox
       * @type {Object|undefined}
       */    
      converter: undefined,

    
      /**
       * The placeholder text to set on the element. Though it is possible to set placeholder 
       * attribute on the element itself, the component will only read the value when the component
       * is created. Subsequent changes to the element's placeholder attribute will not be picked up 
       * and page authors should update the option directly.
       * 
       *
       * @example <caption>Initialize the combobox with the <code class="prettyprint">placeholder</code> option specified:</caption>
       * $( ".selector" ).ojCombobox( { "placeholder": "Please select ..." } );
       * 
       * @default when the option is not set, the element's placeholder attribute is used if it exists. 
       * 
       * @expose 
       * @access public
       * @instance
       * @memberof! oj.ojCombobox
       * @type {string|null|undefined}
       */    
      placeholder: undefined,
      
      /**
       * The id of the html list for the Combobox.
       *
       * @example <caption>Initialize the combobox with the <code class="prettyprint">list</code> option specified:</caption>
       * $( ".selector" ).ojCombobox( { "list": "list" } );
       *
       * @example <caption>The <code class="prettyprint">list</code> points to a html <code class="prettyprint">ul</code> element.
       * The value for the list item should be specified with <code class="prettyprint">oj-data-value</code> field. By default, we use the first text node for search filtering. An optional <code class="prettyprint">oj-data-label</code> field can be added to the list item, in which case it will take precedence over the text node.</caption>
       * <ul id="list">
       * <li oj-data-value="li1">Item 1</li>
       * <li oj-data-value="li2">Item 2</li>
       * </ul>
       *
       * @expose
       * @memberof! oj.ojCombobox
       * @instance
       * @type {string|null|undefined}
       */
      list: undefined,
      
      /**
       * If multi-select is enabled for the combobox.
       *
       * @expose
       * @memberof! oj.ojCombobox
       * @instance
       * @type {boolean}
       * @default <code class="prettyprint">false</code>
       *
       * @example <caption>Initialize the Combobox with the <code class="prettyprint">multiple</code> option specified:</caption>
       * $( ".selector" ).ojCombobox( { "multiple": true } );
       */
      multiple : false,
      
      /**
       * The option items for the Combobox. Instead of providing the option items in a datalist, they can be specified as an array of objects containing value and label.
       * The value is used as the value of the option item and label as the label. Both should be of string type. Group data can be provided with label and a children 
       * array containing the option items. Option item can be set as disabled.
       *
       * @expose
       * @memberof! oj.ojCombobox
       * @instance
       * @type {Array}
       *
       * @example <caption>Initialize the Combobox with the <code class="prettyprint">options</code> specified:</caption>
       * $( ".selector" ).ojCombobox( { "options": [{value: 'option1', label: 'option1'}, {value: 'option2', label: 'option2', disabled: true}, {value: 'option3', label: 'option3'}] } );
       * @example <caption>Initialize the Combobox with group data:</caption>
       * $( ".selector" ).ojCombobox( { "options": [{label : 'group1', children: [{value: 'option1', label: 'option1'}, {value: 'option2', label: 'option2'}]}, {label: 'group2', children: [{value: 'option3', label: 'option3'}]} ] } );
       */
      options : null,
      
      /**
       * Specify the key names to use in the options array.
       *
       * @expose
       * @memberof! oj.ojCombobox
       * @instance
       * @type {Object}
       *
       * @example <caption>Initialize the Combobox with <code class="prettyprint">optionsKeys</code> specified. This allows the key names to be redefined in the options array.</caption>
       * $( ".selector" ).ojCombobox( { "optionsKeys": {value : "state_abbr", label : "state_name"} } );
       * @example <caption>Redefine keys for data with subgroups.</caption>
       * $( ".selector" ).ojCombobox( { "optionsKeys": {label : "regions", children : "states", childKeys : {value : "state_abbr", label : "state_name"}} } );
       */
      optionsKeys : null,
      
      /**
       * Triggered immediately before the combobox drop down is expanded. 
       *
       * @expose
       * @event
       * @memberof! oj.ojCombobox
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       *
       * @example <caption>Initialize the Combobox with the <code class="prettyprint">beforeExpand</code> callback specified:</caption>
       * $( ".selector" ).ojCombobox({
       *     "beforeExpand": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojbeforeexpand</code> event:</caption>
       * $( ".selector" ).on( "ojbeforeexpand", function( event, ui ) {} );
       */
      beforeExpand : null

      /** 
       * The type of value is an array, and an array will always be returned from the component.
       * For single-select the first element of the array will be used as the value. 
       * As a convenience we allow a string to be passed into the setter, 
       * but note that the value option can only be bound to a knockout observableArray.
       * 
       * @example <caption>Initialize the combobox with the <code class="prettyprint">value</code> option specified:</caption>
       * $(".selector").ojCombobox({'value': "option1,option2"});<br/>
       * 
       * @example <caption>Initialize the combobox with the <code class="prettyprint">value</code> option specified as Array for selecting multiple items:</caption>
       * $(".selector").ojCombobox({'value': ["option1", "option2"]});<br/>
       * @example <caption>Get or set the <code class="prettyprint">value</code> option, after initialization:</caption>
       * // Getter: returns value
       * $(".selector").ojCombobox("option", "value");
       * // Setter: sets value with array containing "option1"
       * $(".selector").ojCombobox("option", "value", ["option1"]);
       * // Setter: sets value with array containing "option1" and "option2"
       * $(".selector").ojCombobox("option", "value", ["option1", "option2"]);
       * // Setter: sets value with string "option1"
       * $(".selector").ojCombobox("option", "value", "option1"); 
       * 
       * @member 
       * @name  value
       * @access public
       * @instance
       * @default When the option is not set, the element's value property is used as its initial value 
       * if it exists. 
       * @memberof! oj.ojCombobox
       * @type {string|Array}
       */

    },

    /**
     * Returns a jQuery object containing the element visually representing the combobox.
     *
     * <p>This method does not accept any arguments.
     *
     * @expose
     * @memberof! oj.ojCombobox
     * @instance
     * @return {jQuery} the combobox
     */
    widget : function ()
    {
      return this.combobox.container;
    },

    /**
     * @override
     * @private
     */
    _ComponentCreate : function ()
    {
      this._super();
      this._setup();
    },

    _InitOptions : function (originalDefaults, constructorOptions)
    {
      var props = [{attribute: "disabled", defaultOptionValue: null, validateOption: true},
                   {attribute: "placeholder", defaultOptionValue: ""},
                   {attribute: "required", defaultOptionValue: false, 
                    coerceDomValue: true, validateOption: true},
                   {attribute: "title", defaultOptionValue: ""}
                   // {attribute: "value", defaultOptionValue: null}
                 ]; 
    
      this._super(originalDefaults, constructorOptions);
      oj.EditableValueUtils.initializeOptionsFromDom(props, constructorOptions, this);  

      // TODO: PAVI - Let's discuss
      if (this.options['value'] === undefined) {
          this.options['value'] = (this.element.attr('value') !== undefined) ? _ComboUtils.splitVal(this.element.val(), ",") : null;
      } else {
          //clone the value, otherwise _setDisplayValue will not be invoked on binding value to ko observableArray.
          //TODO: Need to revisit this once 18724975 is fixed.
          var value = this.options['value'];
          if (Array.isArray(value)) {
            value = value.slice(0);
          } else if(typeof value === "string") {
            if (this.options['multiple'] === true)
              value = _ComboUtils.splitVal(value, ","); 
            else
              value = [value];
          }
          this.options['value'] = value;
      }
    },

    _setup : function ()
    {
      var opts = {},
      multiple = this.options.multiple;
      
      opts.element = this.element;
      opts.ojContext = this;
      opts = $.extend(this.options, opts);

      this.combobox = multiple ? new _OjMultiCombobox() : new _OjSingleCombobox();

      this.combobox._init(opts);
    },

    /**
     * @override
     * @private
     */
    _destroy : function ()
    {
      this.combobox._destroy();
      this._super();
    },
    
    /**
     * Refreshes the combobox.
     *
     * <p>This method does not accept any arguments.
     * 
     * @expose 
     * @memberof! oj.ojCombobox
     * @instance
     */
    refresh : function ()
    {
      this._super();

      this.combobox._destroy();
      this._setup();
      this._SetRootAttributes();
      // re-apply oj-required on container
      this._Refresh("required", this.options['required']);
    },

    /**
     * Handles options specific to combobox.
     * @override
     * @protected
     * @memberof! oj.ojCombobox
     */
    _setOption : function (key, value, flags)
    {
      if (key === "value") {
          //clone the value, otherwise _setDisplayValue will not be invoked on binding value to ko observableArray.
          //TODO: Need to revisit this once 18724975 is fixed.
          if (Array.isArray(value)) {
              value = value.slice(0);
          } 
          else if(typeof value === "string") {
            if (this.options['multiple'] === true)
              value = _ComboUtils.splitVal(value, ",");
            else
              value = [value];
          }
      }
      this._super(key, value, flags);  
      
      if (key === "options") 
      {
        this.combobox.opts.options = value;
        this.combobox.opts = this.combobox._prepareOpts(this.combobox.opts);
      }
        
      if (key === "disabled")
      {
        if (value)
          this.combobox._disable();
        else
          this.combobox._enable();
      }
    },

    //19670748, dropdown popup should be closed on subtreeDetached notification.  
    _NotifyDetached : function() {
      this.combobox.close();
    },
    
    //19670748, dropdown popup should be closed on subtreeHidden notification.
    _NotifyHidden : function() {
      this.combobox.close(); 
    },

    /**
     * Updates display value of combobox.
     * @override
     * @protected
     * @memberof! oj.ojCombobox
     */
    _SetDisplayValue: function(displayValue)
    {
      this.combobox._initSelection();
    },
    
    /**
     * Set the placeholder.
     * @override
     * @protected
     * @memberof! oj.ojCombobox
     */
    _SetPlaceholder : function(value)
    {
      if (this.combobox) 
      {
        this.combobox.opts.placeholder = value;
        // TODO: pavitra - noticed that some combobox tests fail because the _setPlaceholder is 
        // undefined, when this method is called from _AfterCreate().
        if (this.combobox._setPlaceholder) 
        {
          this.combobox._setPlaceholder();
        }
      }
    },
    
    /**
     * Validates the component's value using the converter and all validators registered on 
     * the component. 
     * 
     * @example <caption>Validate component using its current value.</caption>
     * // validate display value. 
     * $(.selector).ojCombobox('validate');
     *
     * @expose 
     * @override
     * @memberof! oj.ojCombobox
     * @instance
     */
    validate : function ()
    {
      var displayValue = this.combobox.search.val();
      var newValue = null;
           
      if (this.options['multiple'] !== true) {
        if (displayValue === undefined || displayValue === null || displayValue === "")
          newValue = [];
        else
          newValue = [displayValue];
      } else {
        var existingValue = this.combobox.getVal();
        if (displayValue === undefined || displayValue === null || displayValue === "")
          newValue = existingValue;
        else
          newValue = existingValue.push(displayValue);
      }
     
      return this._SetValue(newValue, null, this._VALIDATE_METHOD_OPTIONS);
    },
    
    /**
     * Parses the value using the converter set and returns the parsed value. If parsing fails the 
     * error is written into the element 
     * 
     * @override
     * @protected
     * @memberof! oj.ojCombobox
     * @instance
     */
    _parseValue: function(submittedValue) 
    {
      var parsedVal = [];
      
      if (typeof submittedValue === "string") {
        if (this.options['multiple'] === true)
          submittedValue = _ComboUtils.splitVal(submittedValue, ",");
        else
          submittedValue = [submittedValue];
      }
      if (Array.isArray(submittedValue)) {
        for (var i = 0; i<submittedValue.length; i++) {
          var parsed = this._super(submittedValue[i]);
          parsedVal.push(parsed.toString());
        }   
      }   
      return parsedVal;      
    },
    
    /**
     * Returns the messaging launcher element  i.e., where user sets focus that triggers the popup. 
     * Usually this is the element input or select that will receive focus and on which the popup 
     * for messaging is initialized. 
     *
     * @override
     * @protected
     * @memberof! oj.ojCombobox
     * @return {Object} jquery element which represents the messaging launcher component
     */
    _GetMessagingLauncherElement : function ()
    {
      return this.combobox.search;
    }, 
    
    /**
     * Returns the jquery element that represents the content part of the component.
     * This is usually the component that user sets focus on (tabindex is set 0) and 
     * where aria attributes like aria-required, aria-labeledby etc. are set. This is
     * also the element where the new value is updated. Usually this is the same as
     * the _GetMessagingLauncherElement.
     *
     * @override
     * @protected
     * @memberof! oj.ojCombobox
     * @return {Object} jquery element which represents the content.
     */
    _GetContentElement : function ()
    {
      return this.combobox.search;
    },      
    
    /**
     * Returns the default styleclass for the component.
     * 
     * @return {string}
     * @expose
     * @memberof! oj.ojCombobox
     * @override
     * @protected
     */
    _GetDefaultStyleClass : function ()
    {
    return "oj-combobox";
    },
    
    _getDropdown : function ()
    {
      if (this.combobox && this.combobox._opened())
      {
        var dropdown = $(".oj-listbox-drop");
        for (var i=0; i<dropdown.length; i++)
        {
          if ($(dropdown[i]).attr("id") == "oj-listbox-drop" &&
            $(dropdown[i]).attr("data-oj-containerid") == this.combobox.containerId)
          return $(dropdown[i]);
        }
      }
      return null;
    },

    //////////////////     SUB-IDS     //////////////////

    /**
     * <p>Sub-ID for the input field
     * 
     * <p>See the <a href="#getNodeBySubId">getNodeBySubId</a> and 
     * <a href="#getSubIdByNode">getSubIdByNode</a> methods for details.
     * 
     * @ojsubid
     * @member
     * @name oj-combobox-input
     * @memberof oj.ojCombobox
     * @instance
     * 
     * @example <caption>Get the input field element</caption>
     * var node = $( ".selector" ).ojCombobox( "getNodeBySubId", {'subId': 'oj-combobox-input'} );
     */
 
    /**
     * <p>Sub-ID for the drop down arrow of single-select combobox. 
     * 
     * <p>See the <a href="#getNodeBySubId">getNodeBySubId</a> and 
     * <a href="#getSubIdByNode">getSubIdByNode</a> methods for details.
     * 
     * @ojsubid
     * @member
     * @name oj-combobox-arrow
     * @memberof oj.ojCombobox
     * @instance
     * 
     * @example <caption>Get the drop down arrow of the single-select combobox</caption>
     * var node = $( ".selector" ).ojCombobox( "getNodeBySubId", {'subId': 'oj-combobox-arrow'} );
     */

    /**
     * <p>Sub-ID for the dropdown box.
     * 
     * <p>See the <a href="#getNodeBySubId">getNodeBySubId</a> and 
     * <a href="#getSubIdByNode">getSubIdByNode</a> methods for details.
     * 
     * @ojsubid
     * @member
     * @name oj-combobox-drop
     * @memberof oj.ojCombobox
     * @instance
     * 
     * @example <caption>Get the dropdown box</caption>
     * var node = $( ".selector" ).ojCombobox( "getNodeBySubId", {'subId': 'oj-combobox-drop'} );
     */

    /**
     * <p>Sub-ID for the filtered result list.
     *
     * <p>See the <a href="#getNodeBySubId">getNodeBySubId</a> and 
     * <a href="#getSubIdByNode">getSubIdByNode</a> methods for details.
     * 
     * @ojsubid
     * @member
     * @name oj-combobox-results
     * @memberof oj.ojCombobox
     * @instance
     * 
     * @example <caption>Get the filtered result list</caption>
     * var node = $( ".selector" ).ojCombobox( "getNodeBySubId", {'subId': 'oj-combobox-results'} );
     */

    /**
     * <p>Sub-ID for the selected items of multi-select combobox. 
     * <p>This returns a list of the selected items.
     * <p>See the <a href="#getNodeBySubId">getNodeBySubId</a> and 
     * <a href="#getSubIdByNode">getSubIdByNode</a> methods for details.
     * 
     * @ojsubid
     * @member
     * @name oj-combobox-selection
     * @memberof oj.ojCombobox
     * @instance
     * 
     * @example <caption>Get the list of selected items</caption>
     * var node = $( ".selector" ).ojCombobox( "getNodeBySubId", {'subId': 'oj-combobox-selection'} );
     */
    
    getNodeBySubId: function(locator)
    {
      var node = null, subId;
	    if (locator == null)
	    {
        return this.combobox.container ? this.combobox.container[0] : null;
	    }
      else
      {
        node = this._super(locator);
      }
	    
      if (!node)
      {
        subId = locator['subId'];
        if (subId === "oj-combobox-drop")
          subId = "oj-listbox-drop";

        if (subId === "oj-combobox-results")
          subId = "oj-listbox-results";
          
        if (subId === "oj-combobox-selection")
          subId = "oj-combobox-selected-choice";

        var dropdown = this._getDropdown();
        
        switch (subId)
        {
          case "oj-combobox-input":
          case "oj-combobox-arrow":
            node = this.widget().find("." + subId)[0];
            break;
          case "oj-listbox-drop":
            if (dropdown) {
              node = dropdown[0];
            }
            break;
          case "oj-listbox-results":
            if (dropdown) {
              node = dropdown.find("." + subId)[0];
            }
            break;
          case "oj-combobox-selected-choice":
            node = this.widget().find("." + subId).toArray();
            break;
        }    
	    }

	    // Non-null locators have to be handled by the component subclasses
	    return node || null;
    }
    // Fragments:

	/**
     * <table class="keyboard-table">
     *   <thead>
     *     <tr>
	 *       <th>Target</th>
	 *       <th>Gesture</th>
	 *       <th>Action</th>
     *     </tr>
     *   </thead>
     *   <tbody>
     *     <tr>
	 *       <td>Input Field</td>
     *       <td><kbd>Tap</kbd></td>
     *       <td> If the drop down is not open, expand the drop down list. Otherwise, close the drop down list.</td>
     *     </tr>
     *     <tr>
	 *       <td>Arrow Button</td>
     *       <td><kbd>Tap</kbd></td>
     *       <td> If the drop down is not open, expand the drop down list. Otherwise, close the drop down list.</td>
     *     </tr>
     *     <tr>
	 *       <td>Option Item</td>
     *       <td><kbd>Tap</kbd></td>
     *       <td>Tap on a option item in the drop down list to select/add a new item.</td>
     *     </tr>
	 *     <tr>
	 *       <td>Selected Item with Clear Entry Button</td>
     *       <td><kbd>Tap</kbd></td>
     *       <td>Remove item from the selected items list by taping on the clear button next to the data item.</td>
     *     </tr>
     *
     *   </tbody>
     *  </table>
     *
     * <p>Disabled option items receive no highlight and are not selectable.</p>
	 *
	 *
	 * @ojfragment touchDoc - Used in touch gesture section of classdesc, and standalone gesture doc
	 * @memberof oj.ojCombobox
	 */

	/**
     * <table class="keyboard-table">
     *   <thead>
     *     <tr>
     *       <th>Key</th>
     *       <th>Use</th>
     *     </tr>
     *   </thead>
     *   <tbody>
     *     <tr>
     *       <td><kbd>Enter</kbd></td>
     *       <td> Select the highlighted choice from the drop down. 
     *         If it's a new entry, add to existing selections.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>UpArrow or DownArrow</kbd></td>
     *       <td> Highlight the option item on the drop down list in the direction of the arrow.
     *         If the drop down is not open, expand the drop down list.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>LeftArrow or RightArrow</kbd></td>
     *       <td> Move focus to the previous or next selected item in Multi-select Combobox.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Esc</kbd></td>
     *       <td> Collapse the drop down list. If the drop down is already closed, do nothing.</td>
     *     </tr>
     *
     *   </tbody>
     *  </table>
     *
     * <p>Disabled option items receive no highlight and are not selectable.</p>
	 *
	 * @ojfragment keyboardDoc - Used in keyboard section of classdesc, and standalone gesture doc
	 * @memberof oj.ojCombobox
	 */
  });