359 lines
12 KiB
JavaScript
359 lines
12 KiB
JavaScript
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// 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.
|
|
|
|
/**
|
|
* @fileoverview Renderer for {@link goog.ui.MenuItem}s.
|
|
*
|
|
* @author attila@google.com (Attila Bodis)
|
|
*/
|
|
|
|
goog.provide('goog.ui.MenuItemRenderer');
|
|
|
|
goog.require('goog.a11y.aria');
|
|
goog.require('goog.a11y.aria.Role');
|
|
goog.require('goog.dom');
|
|
goog.require('goog.dom.classes');
|
|
goog.require('goog.ui.Component');
|
|
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;
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.ui.MenuItemRenderer.prototype.getAriaRole = function() {
|
|
return goog.a11y.aria.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));
|
|
this.setAriaStates(item, element);
|
|
return element;
|
|
};
|
|
|
|
|
|
/** @override */
|
|
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'))) {
|
|
(/** @type {goog.ui.MenuItem} */ (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 && goog.dom.classes.has(child, contentClassName);
|
|
};
|
|
|
|
|
|
/**
|
|
* 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.a11y.aria.setRole(element,
|
|
selectable ?
|
|
goog.a11y.aria.Role.MENU_ITEM_RADIO :
|
|
/** @type {string} */ (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.a11y.aria.setRole(element,
|
|
checkable ?
|
|
goog.a11y.aria.Role.MENU_ITEM_CHECKBOX :
|
|
/** @type {string} */ (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 && goog.dom.classes.has(child, checkboxClassName);
|
|
}
|
|
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);
|
|
}
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.ui.MenuItemRenderer.prototype.getCssClass = function() {
|
|
return goog.ui.MenuItemRenderer.CSS_CLASS;
|
|
};
|