text: // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //     http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  // Copyright 2008 Google Inc. All Rights Reserved.
  
  /**
   * @fileoverview Renderer for {@link goog.ui.MenuItem}s.
   *
   */
  
  goog.provide('goog.ui.MenuItemRenderer');
  
  goog.require('goog.dom');
  goog.require('goog.dom.a11y');
  goog.require('goog.dom.a11y.Role');
  goog.require('goog.dom.classes');
  goog.require('goog.ui.Component.State');
  goog.require('goog.ui.ControlContent');
  goog.require('goog.ui.ControlRenderer');
  
  
  /**
   * Default renderer for {@link goog.ui.MenuItem}s.  Each item has the following
   * structure:
   * <pre>
   *   <div class="goog-menuitem">
   *     <div class="goog-menuitem-content">
   *       ...(menu item contents)...
   *     </div>
   *   </div>
   * </pre>
   * @constructor
   * @extends {goog.ui.ControlRenderer}
   */
  goog.ui.MenuItemRenderer = function() {
    goog.ui.ControlRenderer.call(this);
  
    /**
     * Commonly used CSS class names, cached here for convenience (and to avoid
     * unnecessary string concatenation).
     * @type {!Array.<string>}
     * @private
     */
    this.classNameCache_ = [];
  };
  goog.inherits(goog.ui.MenuItemRenderer, goog.ui.ControlRenderer);
  goog.addSingletonGetter(goog.ui.MenuItemRenderer);
  
  
  /**
   * CSS class name the renderer applies to menu item elements.
   * @type {string}
   */
  goog.ui.MenuItemRenderer.CSS_CLASS = goog.getCssName('goog-menuitem');
  
  
  /**
   * Constants for referencing composite CSS classes.
   * @enum {number}
   * @private
   */
  goog.ui.MenuItemRenderer.CompositeCssClassIndex_ = {
    HOVER: 0,
    CHECKBOX: 1,
    CONTENT: 2
  };
  
  
  /**
   * Returns the composite CSS class by using the cached value or by constructing
   * the value from the base CSS class and the passed index.
   * @param {goog.ui.MenuItemRenderer.CompositeCssClassIndex_} index Index for the
   *     CSS class - could be highlight, checkbox or content in usual cases.
   * @return {string} The composite CSS class.
   * @private
   */
  goog.ui.MenuItemRenderer.prototype.getCompositeCssClass_ = function(index) {
    var result = this.classNameCache_[index];
    if (!result) {
      switch (index) {
        case goog.ui.MenuItemRenderer.CompositeCssClassIndex_.HOVER:
          result = goog.getCssName(this.getStructuralCssClass(), 'highlight');
          break;
        case goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CHECKBOX:
          result = goog.getCssName(this.getStructuralCssClass(), 'checkbox');
          break;
        case goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CONTENT:
          result = goog.getCssName(this.getStructuralCssClass(), 'content');
          break;
      }
      this.classNameCache_[index] = result;
    }
  
    return result;
  };
  
  
  /** @return {goog.dom.a11y.Role} The ARIA role. */
  goog.ui.MenuItemRenderer.prototype.getAriaRole = function() {
    return goog.dom.a11y.Role.MENU_ITEM;
  };
  
  
  /**
   * Overrides {@link goog.ui.ControlRenderer#createDom} by adding extra markup
   * and stying to the menu item's element if it is selectable or checkable.
   * @param {goog.ui.Control} item Menu item to render.
   * @return {Element} Root element for the item.
   * @override
   */
  goog.ui.MenuItemRenderer.prototype.createDom = function(item) {
    var element = item.getDomHelper().createDom(
        'div', this.getClassNames(item).join(' '),
        this.createContent(item.getContent(), item.getDomHelper()));
    this.setEnableCheckBoxStructure(item, element,
        item.isSupportedState(goog.ui.Component.State.SELECTED) ||
        item.isSupportedState(goog.ui.Component.State.CHECKED));
    return element;
  };
  
  
  /** @inheritDoc */
  goog.ui.MenuItemRenderer.prototype.getContentElement = function(element) {
    return /** @type {Element} */ (element && element.firstChild);
  };
  
  
  /**
   * Overrides {@link goog.ui.ControlRenderer#decorate} by initializing the
   * menu item to checkable based on whether the element to be decorated has
   * extra stying indicating that it should be.
   * @param {goog.ui.Control} item Menu item instance to decorate the element.
   * @param {Element} element Element to decorate.
   * @return {Element} Decorated element.
   * @override
   */
  goog.ui.MenuItemRenderer.prototype.decorate = function(item, element) {
    if (!this.hasContentStructure(element)) {
      element.appendChild(
          this.createContent(element.childNodes, item.getDomHelper()));
    }
    if (goog.dom.classes.has(element, goog.getCssName('goog-option'))) {
      item.setCheckable(true);
      this.setCheckable(item, element, true);
    }
    return goog.ui.MenuItemRenderer.superClass_.decorate.call(this, item,
        element);
  };
  
  /**
   * Takes a menu item's root element, and sets its content to the given text
   * caption or DOM structure.  Overrides the superclass immplementation by
   * making sure that the checkbox structure (for selectable/checkable menu
   * items) is preserved.
   * @param {Element} element The item's root element.
   * @param {goog.ui.ControlContent} content Text caption or DOM structure to be
   *     set as the item's content.
   * @override
   */
  goog.ui.MenuItemRenderer.prototype.setContent = function(element, content) {
    // Save the checkbox element, if present.
    var contentElement = this.getContentElement(element);
    var checkBoxElement = this.hasCheckBoxStructure(element) ?
        contentElement.firstChild : null;
    goog.ui.MenuItemRenderer.superClass_.setContent.call(this, element, content);
    if (checkBoxElement && !this.hasCheckBoxStructure(element)) {
      // The call to setContent() blew away the checkbox element; reattach it.
      contentElement.insertBefore(checkBoxElement,
          contentElement.firstChild || null);
    }
  };
  
  
  /**
   * Returns true if the element appears to have a proper menu item structure by
   * checking whether its first child has the appropriate structural class name.
   * @param {Element} element Element to check.
   * @return {boolean} Whether the element appears to have a proper menu item DOM.
   * @protected
   */
  goog.ui.MenuItemRenderer.prototype.hasContentStructure = function(element) {
    var child = goog.dom.getFirstElementChild(element);
    var contentClassName = this.getCompositeCssClass_(
        goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CONTENT);
    return !!child && child.className.indexOf(contentClassName) != -1;
  };
  
  
  /**
   * Wraps the given text caption or existing DOM node(s) in a structural element
   * containing the menu item's contents.
   * @param {goog.ui.ControlContent} content Menu item contents.
   * @param {goog.dom.DomHelper} dom DOM helper for document interaction.
   * @return {Element} Menu item content element.
   * @protected
   */
  goog.ui.MenuItemRenderer.prototype.createContent = function(content, dom) {
    var contentClassName = this.getCompositeCssClass_(
        goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CONTENT);
    return dom.createDom('div', contentClassName, content);
  };
  
  
  /**
   * Enables/disables radio button semantics on the menu item.
   * @param {goog.ui.Control} item Menu item to update.
   * @param {Element} element Menu item element to update (may be null if the
   *     item hasn't been rendered yet).
   * @param {boolean} selectable Whether the item should be selectable.
   */
  goog.ui.MenuItemRenderer.prototype.setSelectable = function(item, element,
      selectable) {
    if (element) {
      goog.dom.a11y.setRole(element, selectable ?
          goog.dom.a11y.Role.MENU_ITEM_RADIO : this.getAriaRole());
      this.setEnableCheckBoxStructure(item, element, selectable);
    }
  };
  
  
  /**
   * Enables/disables checkbox semantics on the menu item.
   * @param {goog.ui.Control} item Menu item to update.
   * @param {Element} element Menu item element to update (may be null if the
   *     item hasn't been rendered yet).
   * @param {boolean} checkable Whether the item should be checkable.
   */
  goog.ui.MenuItemRenderer.prototype.setCheckable = function(item, element,
      checkable) {
    if (element) {
      goog.dom.a11y.setRole(element, checkable ?
          goog.dom.a11y.Role.MENU_ITEM_CHECKBOX : this.getAriaRole());
      this.setEnableCheckBoxStructure(item, element, checkable);
    }
  };
  
  
  /**
   * Determines whether the item contains a checkbox element.
   * @param {Element} element Menu item root element.
   * @return {boolean} Whether the element contains a checkbox element.
   * @protected
   */
  goog.ui.MenuItemRenderer.prototype.hasCheckBoxStructure = function(element) {
    var contentElement = this.getContentElement(element);
    if (contentElement) {
      var child = contentElement.firstChild;
      var checkboxClassName = this.getCompositeCssClass_(
          goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CHECKBOX);
      return !!child && !!child.className &&
          child.className.indexOf(checkboxClassName) != -1;
    }
    return false;
  };
  
  
  /**
   * Adds or removes extra markup and CSS styling to the menu item to make it
   * selectable or non-selectable, depending on the value of the
   * {@code selectable} argument.
   * @param {goog.ui.Control} item Menu item to update.
   * @param {Element} element Menu item element to update.
   * @param {boolean} enable Whether to add or remove the checkbox structure.
   * @protected
   */
  goog.ui.MenuItemRenderer.prototype.setEnableCheckBoxStructure = function(item,
      element, enable) {
    if (enable != this.hasCheckBoxStructure(element)) {
      goog.dom.classes.enable(element, goog.getCssName('goog-option'), enable);
      var contentElement = this.getContentElement(element);
      if (enable) {
        // Insert checkbox structure.
        var checkboxClassName = this.getCompositeCssClass_(
            goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CHECKBOX);
        contentElement.insertBefore(
            item.getDomHelper().createDom('div', checkboxClassName),
            contentElement.firstChild || null);
      } else {
        // Remove checkbox structure.
        contentElement.removeChild(contentElement.firstChild);
      }
    }
  };
  
  
  /**
   * Takes a single {@link goog.ui.Component.State}, and returns the
   * corresponding CSS class name (null if none).  Overrides the superclass
   * implementation by using 'highlight' as opposed to 'hover' as the CSS
   * class name suffix for the HOVER state, for backwards compatibility.
   * @param {goog.ui.Component.State} state Component state.
   * @return {string|undefined} CSS class representing the given state
   *     (undefined if none).
   * @override
   */
  goog.ui.MenuItemRenderer.prototype.getClassForState = function(state) {
    switch (state) {
      case goog.ui.Component.State.HOVER:
        // We use 'highlight' as the suffix, for backwards compatibility.
        return this.getCompositeCssClass_(
            goog.ui.MenuItemRenderer.CompositeCssClassIndex_.HOVER);
      case goog.ui.Component.State.CHECKED:
      case goog.ui.Component.State.SELECTED:
      // We use 'goog-option-selected' as the class, for backwards compatibility.
        return goog.getCssName('goog-option-selected');
      default:
        return goog.ui.MenuItemRenderer.superClass_.getClassForState.call(this,
            state);
    }
  };
  
  
  /**
   * Takes a single CSS class name which may represent a component state, and
   * returns the corresponding component state (0x00 if none).  Overrides the
   * superclass implementation by treating 'goog-option-selected' as special,
   * for backwards compatibility.
   * @param {string} className CSS class name, possibly representing a component
   *     state.
   * @return {goog.ui.Component.State} state Component state corresponding
   *     to the given CSS class (0x00 if none).
   * @override
   */
  goog.ui.MenuItemRenderer.prototype.getStateFromClass = function(className) {
    var hoverClassName = this.getCompositeCssClass_(
        goog.ui.MenuItemRenderer.CompositeCssClassIndex_.HOVER);
    switch (className) {
      case goog.getCssName('goog-option-selected'):
        return goog.ui.Component.State.CHECKED;
      case hoverClassName:
        return goog.ui.Component.State.HOVER;
      default:
        return goog.ui.MenuItemRenderer.superClass_.getStateFromClass.call(this,
            className);
    }
  };
  
  
  /** @inheritDoc */
  goog.ui.MenuItemRenderer.prototype.getCssClass = function() {
    return goog.ui.MenuItemRenderer.CSS_CLASS;
  };
  