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

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

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

/**
 * @preserve Copyright 2013 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

/**
 * @ojcomponent oj.ojAccordion
 * @augments oj.baseComponent
 * @since 0.6
 * 
 * @classdesc
 * <h3 id="accordionOverview-section">
 *   JET Accordion Component
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#accordionOverview-section"></a>
 * </h3>
 * 
 * <p>Description: Themeable, WAI-ARIA-compliant accordion with mouse and keyboard interactions for navigation.
 * 
 * <p>A JET Accordion can be created from any valid markup as long as the root element has one or more child elements and 
 * each child element must have two children: the first element for the header and the second element for the content.
 * 
 * <pre class="prettyprint">
 * <code>
 * <div id="accordion">
 *   <div id="c1">
 *     <h3>
 *       <img src="images/default.png"/>
 *       <span>Header 1</span>
 *     </h3>
 *     <p>Content 1.</p>
 *   </div>
 *   <div id="c3" data-bind="ojComponent: {component: 'ojAccordion', expanded:true}">
 *     <h3>Header 3</h3>
 *     <p>Content 3</p>
 *   </div>
 * </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="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 accordion 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-accordion</code> pseudo-selector can be used in jQuery expressions to select JET Accordion.  For example:
 * 
 * <pre class="prettyprint">
 * <code>$( ":oj-accordion" ) // selects all JET Accordion on the page
 * $myEventTarget.closest( ":oj-accordion" ) // selects the closest ancestor that is a JET Accordion
 * </code></pre>
 * 
 * 
 * <h3 id="jqui2jet-section">
 *   JET for jQuery UI developers
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#jqui2jet-section"></a>
 * </h3>
 * 
 * <p>Also, event names for all JET components are prefixed with "oj", instead of component-specific prefixes like "collapsible".  
 * 
 * <h3 id="theming-section">Theming</h3>
 * <p>Details about the include and import mechanisms described below can be found in the 
 * Theming chapter of the JET documentation.</p>
 * <ul>
 * <li>Include Mechanism Variable: <code>$includeAccordionClasses</code>
 * <pre class="prettyprint">
 * <code>$includeAllClasses: false;
 * $includeAccordionClasses: true;
 * @import "scss/oj/v1.0.1/alta/oj-alta";
 * </code></pre>
 * </li>
 * <li>Import Mechanism File: 
 * <code>scss/oj/[VERSION]/[THEME NAME]/widgets/_oj.[THEME NAME].accordion.scss</code> 
 * <pre class="prettyprint">
 * <code>@import "scss/oj/v1.0.1/alta/oj.alta.variables";
 * @import "scss/oj/v1.0.1/alta/widgets/oj.alta.accordion";
 * </code></pre></li>
 * </ul>
 * 
 * <!-- - - - - Above this point, the tags are for the class.
 *              Below this point, the tags are for the constructor (initializer). - - - - - - -->
 * 
 * @desc Creates a JET Accordion. 
 * @example <caption>Initialize the accordion with no options specified:</caption>
 * $( ".selector" ).ojAccordion();
 * 
 * @example <caption>Initialize the accordion with some options specified:</caption>
 * $( ".selector" ).ojAccordion( { "multiple": true } );
 * 
 * @example <caption>Initialize the accordion via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
 * <div id="accordion" data-bind="ojComponent: { component: 'ojAccordion', multiple: true}">
 * 
 */
(function ()
{
  oj.__registerWidget("oj.ojAccordion", $['oj']['baseComponent'], 
  {
    widgetEventPrefix : "oj", 
    options : 
    {
      /** 
       * Allow multiple collapsibles to be open at the same time. 
       * Note: if multiple is true, the beforeCollapse/beforeExpand/collapse/expand events will not be fired by the accordion. They are however fired by the collapsibles.
       *
       * @expose 
       * @memberof! oj.ojAccordion
       * @instance
       * @type {boolean}
       * @default <code class="prettyprint">false</code>
       *
       * @example <caption>Initialize the accordion with the <code class="prettyprint">multiple</code> option specified:</caption>
       * $( ".selector" ).ojAccordion( { "multiple": true } );
       * 
       * @example <caption>Get or set the <code class="prettyprint">multiple</code> option, after initialization:</caption>
       * // getter
       * var multiple = $( ".selector" ).ojAccordion( "option", "multiple" );
       * 
       * // setter
       * $( ".selector" ).ojAccordion( "option", "multiple", true );
       */
      multiple : false,

      /** 
       * Array contains either ids or zero-based indices of the collapsibles that should be expanded.<p>
       * Setter value: array of either ids or indices.<p>
       * Getter value: array of either ids or indices. If an expanded collapsible has a page author provided id, that id is returned, otherwise that collapsible's index will be returned.
       *
       * @expose 
       * @memberof! oj.ojAccordion
       * @instance
       * @type {Array}
       * @default <code class="prettyprint">null</code>
       *
       * Note: expanded is default to null<p>
       * which means that accordion doesn't modify the state on the collapsible children.<p>
       * If specified, it overrides the children expanded setting.
       *
       * @example <caption>Initialize the accordion with the 
       * <code class="prettyprint">expanded</code> option specified:</caption>
       * $( ".selector" ).ojAccordion( { "expanded": ["collapsible1Div"] } );
       * 
       * @example <caption>Get or set the <code class="prettyprint">expanded</code> 
       * option after initialization:</caption>
       * // getter
       * var expanded = $( ".selector" ).ojAccordion( "option", "expanded" );
       * 
       * // setter
       * $( ".selector" ).ojAccordion( "option", "expanded", ["collapsible1Div"] );
       */
      expanded : null, 

      // callbacks
      /**
       * Triggered immediately before any collapsible in the accordion is expanded.
       * beforeExpand can be canceled to prevent the content from expanding by returning a false in the event listener.
       * If multiple is true, the beforeExpand event will not be fired by the accordion.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojAccordion
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.toCollapsible The collapsible being expanded.
       * @property {jQuery} ui.fromCollapsible The collapsible being collapsed. 
       * 
       * @example <caption>Initialize any collapsible in the accordion with the <code class="prettyprint">beforeExpand</code> callback specified:</caption>
       * $( ".selector" ).ojAccordion({
       *     "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,

      /**
       * Triggered after the accordion has been expanded (after animation completes).
       * The expand can be cancelled by calling <code class="prettyprint">event.preventDefault()</code>.
       * If multiple is true, the expand event will not be fired by the accordion.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojAccordion
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.toCollapsible The collapsible being expanded.
       * @property {jQuery} ui.fromCollapsible The collapsible being collapsed. 
       * 
       * @example <caption>Initialize the accordion with the <code class="prettyprint">expand</code> callback specified:</caption>
       * $( ".selector" ).ojAccordion({
       *     "expand": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojexpand</code> event:</caption>
       * $( ".selector" ).on( "ojexpand", function( event, ui ) {} );
       */
      expand : null, 

      /**
       * Triggered immediately before any collapsible in the accordion is collapsed.
       * beforeCollapse can be canceled to prevent the content from collapseing by returning a false in the event listener.
       * If multiple is true, the beforeCollapse event will not be fired by the accordion.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojAccordion
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.toCollapsible The collapsible being expanded.
       * @property {jQuery} ui.fromCollapsible The collapsible being collapsed. 
       * 
       * @example <caption>Initialize any collapsible in the accordion with the <code class="prettyprint">beforeCollapse</code> callback specified:</caption>
       * $( ".selector" ).ojAccordion({
       *     "beforeCollapse": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojbeforecollapse</code> event:</caption>
       * $( ".selector" ).on( "ojbeforecollapse", function( event, ui ) {} );
       */
      beforeCollapse : null,

      /**
       * Triggered after any collapsible in the accordion has been collapsed (after animation completes).
       * The collapse can be cancelled by calling <code class="prettyprint">event.preventDefault()</code>.
       * If multiple is true, the collapse event will not be fired by the accordion.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojAccordion
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.toCollapsible The collapsible being expanded.
       * @property {jQuery} ui.fromCollapsible The collapsible being collapsed. 
       * 
       * @example <caption>Initialize any collapsible in the accordion with the <code class="prettyprint">collapse</code> callback specified:</caption>
       * $( ".selector" ).ojAccordion({
       *     "collapse": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojcollapse</code> event:</caption>
       * $( ".selector" ).on( "ojcollapse", function( event, ui ) {} );
       */
      collapse : null

    },

    _ComponentCreate : function ()
    {
      this._super();
      //Bug 18633679 - Stop using ui-helper-reset in the layout widgets.
      this.element
        .addClass("oj-accordion oj-component")
        //aria
        .attr("role", "group");

      //Bug 19605546 - accordion expanded opt. val doesn't overrides its collaspsible expanded opt.
      this.options.expanded = this._expandedIndexToId(this.options.expanded);
      this._refresh();
    },

    // Override to set custom launcher
    _NotifyContextMenuGesture: function(menu, event, eventType)
    {
      // Setting the launcher to the "twisty" icon of the first collapsible in the accordion, since those twisties seem to be 
      // the only tabbable things in the accordion, and they seem to remain tabbable even if the collapsible is disabled.
      // Component owner should feel free to specify a different launcher if appropriate, e.g. could specify the "current" 
      // twisty rather than the first if desired.  See the superclass JSDoc for _OpenContextMenu for tips on choosing a launcher.
      this._OpenContextMenu(event, eventType, {"launcher": this.element.find(".oj-collapsible-header-icon").first()});
    },
    
    _destroy : function ()
    {
      // clean up main element
      this.element
        .removeClass("oj-accordion oj-component")
        .removeAttr("role");

      this.element.children()
        .removeClass("oj-accordion-collapsible");

      //remove collapsibles created by accordion
      this.element.children(".oj-accordion-created")
        .removeClass("oj-accordion-created")
        .ojCollapsible("destroy");
    },

    _setOption : function (key, value, flags)
    {
      var self = this;

      if (key === "multiple")
      {
        // Transition multiple to single.
        // Keep the first expanded one expanded and collapse the rest.
        if (value == false && this.options.multiple == true)
        {
          //Bug 19278184 - when "multiple" option value is changed to false, exception is displayed
          this.element.children(".oj-expanded")
            .first().siblings(".oj-collapsible")
            .ojCollapsible("collapse", false);
        }
      }
      else if (key === "expanded")
      {
        this._setExpandedOption(value);
        return;
      }

      this._super(key, value, flags);

//TODO: ignore disabled until disabled propagation is supported
/*
      if (key === "disabled")
      {
        value = !!value;

        if (value) {
          this.collapsibles.each(function ()
          {
            //TODO: Don't override if collapsible has disabled set
            if ($(this).ojCollapsible("option", key) == null)
              $(this).ojCollapsible("option", key, value);
          });
        }
        this.element.toggleClass("oj-disabled", value);
      }
*/
    },

    /**
     * Refreshes the visual state of the accordion. 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.ojAccordion
     * @instance
     * 
     * @example <caption>Invoke the <code class="prettyprint">refresh</code> method:</caption>
     * $( ".selector" ).ojAccordion( "refresh" );
     */
    refresh : function ()
    {
      this._super();
      this._refresh();
    },

    _refresh : function ()
    {
      this._makeCollapsible();

      //need to propagate the option to the collapsible children
      this._setOption("disabled", this.options.disabled);

      this._internalSetExpanded = true;
      this._setOption("expanded", this.options.expanded);
      this._internalSetExpanded = false;

      this._setupEvents();
    },

    _makeCollapsible : function ()
    {
      this.element.children(":oj-collapsible")
        .each(function ()
        {
          $(this).ojCollapsible("option", "expandArea", "header");
        });

      this.collapsibles = 
        this.element.children()
          .not(":oj-ojCollapsible")
            .ojCollapsible(
            {
              expandArea : "header"
            })
            .addClass("oj-accordion-created")
          .end()
          .addClass("oj-accordion-collapsible");
    },

    _setupEvents : function ()
    {
      var events = 
      {
        "keydown" : this._keydown, 
        "ojbeforeexpand" : this._beforeExpandHandler, 
        "ojexpand" : this._expandHandler,
        "ojbeforecollapse" : this._beforeCollapseHandler, 
        "ojcollapse" : this._collapseHandler
      };

      this._off(this.collapsibles);
      this._on(this.collapsibles, events);
    },

    _keydown : function (event)
    {
      if (event.altKey || event.ctrlKey)
        return;

      //ignore event if target is not a header
//TODO:
//      if ($(event.target).parentsUntil(event.currentTarget)[0] !== 
//          $(event.currentTarget).children()[0])
      if (! ($(event.target).hasClass("oj-collapsible-header")) &&
          ! ($(event.target).hasClass("oj-collapsible-header-icon")))
        return;

      var keyCode = $.ui.keyCode, 
          enabledCollapsibles = this.collapsibles.not(".oj-disabled"), 
          length = enabledCollapsibles.length, 
          target = $(event.target).closest(".oj-collapsible"), 
          currentIndex = enabledCollapsibles.index(target), 
          toFocus = false;

      if (currentIndex >= 0)
      {
        switch (event.keyCode)
        {
          case keyCode.RIGHT:
          case keyCode.DOWN:
            toFocus = enabledCollapsibles[(currentIndex + 1) % length];
            break;
          case keyCode.LEFT:
          case keyCode.UP:
            toFocus = enabledCollapsibles[(currentIndex - 1 + length) % length];
            break;
          case keyCode.HOME:
            toFocus = enabledCollapsibles[0];
            break;
          case keyCode.END:
            toFocus = enabledCollapsibles[length - 1];
            break;
        }
      }

      if (toFocus)
      {
        if (target)
        {
          $(target).trigger("ojfocusout");
        }
        $(toFocus).trigger("ojfocus");

        event.preventDefault();
      }
    },

    /* 
     * For single expansion
     *   returns a list of expanded collapsible widgets that are sibling 
     *   of the current event target
     * For multiple expansion
     *   returns an empty set.
     */
    _findTargetSiblings : function (event)
    {
      if (! this.options.multiple)
      {
        var closestCollapsible = $(event.target).closest(".oj-collapsible");

        if (closestCollapsible.parent().is(":oj-ojAccordion"))
          return closestCollapsible
            .siblings(".oj-collapsible.oj-expanded")
            .map(function()
            {
              return $(this).data("oj-ojCollapsible");
            });
      }
      return $();
    },

    /* 
     * Trigger "beforeCollapse" on all expanded siblings in
     * the before expand handler
     */
    _beforeExpandHandler : function (event, eventData)
    {
      if (! this._isTargetMyCollapsible(event))
        return true;

      var result, self = this;
      var newData;

      this._findTargetSiblings(event).each(function()
      {
        var collapsible = this.element;
        newData = self._initEventData(collapsible, $(event.target));

        var beforeCollapsedData = {
          /** @expose */
          header : collapsible.find(".oj-collapsible-header"),

          /** @expose */
          content : collapsible.find(".oj-collapsible-content")
        };

        result = this._trigger("beforeCollapse", event, beforeCollapsedData);
        return result;
      });

      if (! newData)
        newData = self._initEventData(null, $(event.target));

      if (! this.options.multiple)
        this._trigger("beforeExpand", event, newData);

      return result;
    },

    /* 
     * Collapse all expanded siblings and don't allow cancel
     */
    _expandHandler : function (event, eventData)
    {
      //Bug 19605546 - accordion expanded opt. val doesn't overrides its collaspsible expanded opt.
      //don't handle event during setExpandedOption
      if (! this._isTargetMyCollapsible(event) || this._duringSetExpandedOption)
        return;

      var newData;
      var self = this;

      this._findTargetSiblings(event).each(function ()
      {
        this.collapse(false, event, eventData);
        newData = self._initEventData(this.element, $(event.target));
      });

      if (! newData)
        newData = self._initEventData(null, $(event.target));

      if (! this.options.multiple)
        this._trigger("expand", event, newData);

      this._updateExpanded();

    },

    /* 
     * Trigger "beforecollapse" on all collapsed siblings in
     * the before collapse handler
     */
    _beforeCollapseHandler : function (event, eventData)
    {
      if (this._isTargetMyCollapsible(event) &&
          ! this.options.multiple)
      {
        return this._trigger("beforeCollapse", event, 
                             this._initCollapseEventData(event, eventData));
      }
      return true;
    },

    /* 
     * Collapse all collapsed siblings and don't allow cancel
     */
    _collapseHandler : function (event, eventData)
    {
      //Bug 19605546 - accordion expanded opt. val doesn't overrides its collaspsible expanded opt.
      //don't handle event during setExpandedOption
      if (! this._duringSetExpandedOption && this._isTargetMyCollapsible(event))
      {
        var newData = this._initCollapseEventData(event, eventData);

        if (! this.options.multiple)
          this._trigger("collapse", event, newData);

        this._updateExpanded();
      }
    },

    _initEventData : function (fromC, toC)
    {
      var eventData =
      {
        /** @expose */
        fromCollapsible : fromC, //the collapsible being collapsed.
        /** @expose */
        toCollapsible : toC      //the collapsible being expanded.
      };

      return eventData;
    },

    _initCollapseEventData : function (event, eventData)
    {
      var newData;
      if (eventData.toCollapsible)
      {
        newData = eventData;
      }
      else
      {
        if (event.originalEvent && event.originalEvent.target)
        {
          var collapsible = $(event.originalEvent.target);
          if (collapsible.hasClass("oj-collapsible"))
          {
            newData = this._initEventData($(event.target), collapsible);
          }
        }
        if (! newData)
          newData = this._initEventData($(event.target), null);
      }

      return newData;
    },

    /*
     * To filter out events from the nested accordion
     */
    _isTargetMyCollapsible : function (event)
    {
      return $(event.target).is(this.collapsibles);
    },

    _updateExpanded : function ()
    {
      var cid;
      var result = [];
      this.collapsibles.each(function (index)
      {
        //push collapsible id if provided, otherwise index
        if ($(this).ojCollapsible("option", "expanded"))
        {
          cid = $(this).attr("id");
          result.push(cid ? cid : index);
        }
      });

      //Bug 19605546 - accordion expanded opt. val doesn't overrides its collaspsible expanded opt.
      if (! oj.Object._compareSet(this.options.expanded, result))
        this.option('expanded', result, {'_context': {internalSet: true, writeback: true}});
    },

    //Bug 19605546 - accordion expanded opt. val doesn't overrides its collaspsible expanded opt.
    //Return a new sorted expanded array contains IDs if they are available
    _expandedIndexToId: function(expanded)
    {
      if (Array.isArray(expanded))
      {
        var id, newExp = []

        this.element.children().each (function (index)
        {
          id = $(this).attr("id");
          // use ID if available
          if (id)
          {
            if (expanded.indexOf(id) != -1)
              newExp.push(id);
            else if (expanded.indexOf(index) != -1)
              newExp.push(id);
          }
          // use index if ID not available
          else if (expanded.indexOf(index) != -1)
          {
            newExp.push(index);
          }
        });

        //if more than one expanded, keep the last collapsible
        if (! this.options.multiple && newExp.length > 1)
          newExp = [newExp[newExp.length-1]];

        return newExp;
      }

      return null;
    },

    _setExpandedOption: function(expanded)
    {
      //sort expanded array if it's from external setOption
      if (! this._internalSetExpanded)
        expanded = this._expandedIndexToId(expanded);
    
      // parent override children expanded setting
      if (expanded)
      {
        //loop thru collapsibles to collapse/expand based on expandedList
        var self = this,
            child,
            childId,
            parentExp,
            iexp = 0;

        this.collapsibles.each(function (index)
        {
          child = $(this);
          childId = child.attr("id");

          parentExp = false;
          if (childId) {
            if (childId == expanded[iexp])
              parentExp = true;
          }
          else {
            if (index == expanded[iexp])
              parentExp = true;
          }
          if (parentExp)
            iexp++;

          //Bug 19605546 - accordion expanded opt. val doesn't overrides its collaspsible expanded opt.
          //when the parent override the child setting:
          //1) log a warning
          //2) set child's expanded option with the writeback flag.
          if (child.ojCollapsible("option", "expanded") !== parentExp) {
            oj.Logger.warn("JET Accordion: override collapsible " + index + " expanded setting");

            //don't fire accordion "exanded" optionChange event here 
            self._duringSetExpandedOption = true;
            child.ojCollapsible("option", "expanded", parentExp);
            self._duringSetExpandedOption = false;
          }
        });
      }

      this._updateExpanded();
    },

    /**
     * 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-accordion-collapsible</b>: collapsible div </li>
     * <li><b>oj-accordion-header</b>: collapsible header </li>
     * <li><b>oj-accordion-content</b>: collapsible content </li>
     * <li><b>oj-accordion-header-icon</b>: collapsible disclosure icon </li>
     * </ul>
     *
     * In addition, accordion requires a zero-based index of the child collapsible.
     * @expose
     * @memberof! oj.ojAccordion
     * @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.<p>
     *        index: a number, the index of collapsible.
     * @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'],
          index = locator['index'];

      if ((typeof index !== 'number') ||
          index < 0 || index >= this.collapsibles.length)
        return null;

      var collapsible = this.collapsibles[index];

      switch (subId)
      {
      case 'oj-accordion-content':
        subId = 'oj-collapsible-content';
        break;

      case 'oj-accordion-header':
        subId = 'oj-collapsible-header';
        break;

      case 'oj-accordion-header-icon':
        subId = 'oj-collapsible-header-icon';
        break;

      case 'oj-accordion-collapsible':
        return collapsible;

      default:
        // Non-null locators have to be handled by the component subclasses
        return null;
      }
      return $(collapsible).ojCollapsible("getNodeBySubId", {"subId": subId});
    }

    // Fragments:

    /**
     * <table class="keyboard-table">
     *   <thead>
     *     <tr>
     *       <th>Target</th>
     *       <th>Gesture</th>
     *       <th>Action</th>
     *     </tr>
     *   </thead>
     *   <tbody>
     *     <tr>
     *       <td>Collapsible header</td>
     *       <td><kbd>Tap</kbd></td>
     *       <td>Toggle disclosure state.</tr>
     *     </tr>
     *   </tbody>
     * </table>
     *
     * @ojfragment touchDoc - Used in touch gesture section of classdesc, and standalone gesture doc
     * @memberof oj.ojAccordion
     */
        
    /**
     * <table class="keyboard-table">
     *   <thead>
     *     <tr>
     *       <th>Target</th>
     *       <th>Key</th>
     *       <th>Action</th>
     *     </tr>
     *   </thead>
     *   <tbody>
     *     <tr>
     *       <td>Collapsible header</td>
     *       <td><kbd>Space or Enter</kbd></td>
     *       <td>Toggle disclosure state.</tr>
     *     </tr>
     *     <tr>
     *       <td>Collapsible header</td>
     *       <td><kbd>Tab</kbd></td>
     *       <td>Navigate to next collapsible header and if none then the next component on page.</td>
     *     </tr>
     *     <tr>
     *       <td>Collapsible header</td>
     *       <td><kbd>Shift+Tab</kbd></td>
     *       <td>Navigate to previous collapsible header and if none then the previous component on page.</td>
     *     </tr>
     *     <tr>
     *       <td>Collapsible header</td>
     *       <td><kbd>UpArrow or LeftArrow</kbd> (<kbd>RightArrow</kbd> in RTL)</td>
     *       <td>Move focus to the previous collapsible header with wrap around.</tr>
     *     </tr>
     *     <tr>
     *       <td>Collapsible header</td>
     *       <td><kbd>DownArrow or RightArrow</kbd> (<kbd>LeftArrow</kbd> in RTL)</td>
     *       <td>Move focus to the next collapsible header with wrap around.</tr>
     *     </tr>
     *     <tr>
     *       <td>Collapsible header</td>
     *       <td><kbd>Home</kbd></td>
     *       <td>Move focus to the first collapsible header.</tr>
     *     </tr>
     *     <tr>
     *       <td>Collapsible header</td>
     *       <td><kbd>End</kbd></td>
     *       <td>Move focus to the last collapsible header.</tr>
     *     </tr>
     *   </tbody>
     * </table>
     *
     * <p>Disabled items can receive keyboard focus, but do not allow any other interaction.
     *
     * @ojfragment keyboardDoc - Used in keyboard section of classdesc, and standalone gesture doc
     * @memberof oj.ojAccordion
     */

  });
}
());