Path: blob/trunk/third_party/closure/goog/ui/containerrenderer.js
4049 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Base class for container renderers.8*/910goog.provide('goog.ui.ContainerRenderer');1112goog.require('goog.a11y.aria');13goog.require('goog.asserts');14goog.require('goog.dom.NodeType');15goog.require('goog.dom.TagName');16goog.require('goog.dom.classlist');17goog.require('goog.string');18goog.require('goog.style');19goog.require('goog.ui.registry');20goog.require('goog.userAgent');21goog.requireType('goog.ui.Container');22goog.requireType('goog.ui.Container.Orientation');23goog.requireType('goog.ui.Control');24252627/**28* Default renderer for {@link goog.ui.Container}. Can be used as-is, but29* subclasses of Container will probably want to use renderers specifically30* tailored for them by extending this class.31* @param {string=} opt_ariaRole Optional ARIA role used for the element.32* @constructor33*/34goog.ui.ContainerRenderer = function(opt_ariaRole) {35'use strict';36// By default, the ARIA role is unspecified.37/** @private {string|undefined} */38this.ariaRole_ = opt_ariaRole;39};40goog.addSingletonGetter(goog.ui.ContainerRenderer);414243/**44* Constructs a new renderer and sets the CSS class that the renderer will use45* as the base CSS class to apply to all elements rendered by that renderer.46* An example to use this function using a menu is:47*48* <pre>49* var myCustomRenderer = goog.ui.ContainerRenderer.getCustomRenderer(50* goog.ui.MenuRenderer, 'my-special-menu');51* var newMenu = new goog.ui.Menu(opt_domHelper, myCustomRenderer);52* </pre>53*54* Your styles for the menu can now be:55* <pre>56* .my-special-menu { }57* </pre>58*59* <em>instead</em> of60* <pre>61* .CSS_MY_SPECIAL_MENU .goog-menu { }62* </pre>63*64* You would want to use this functionality when you want an instance of a65* component to have specific styles different than the other components of the66* same type in your application. This avoids using descendant selectors to67* apply the specific styles to this component.68*69* @param {Function} ctor The constructor of the renderer you want to create.70* @param {string} cssClassName The name of the CSS class for this renderer.71* @return {goog.ui.ContainerRenderer} An instance of the desired renderer with72* its getCssClass() method overridden to return the supplied custom CSS73* class name.74*/75goog.ui.ContainerRenderer.getCustomRenderer = function(ctor, cssClassName) {76'use strict';77var renderer = new ctor();7879/**80* Returns the CSS class to be applied to the root element of components81* rendered using this renderer.82* @return {string} Renderer-specific CSS class.83*/84renderer.getCssClass = function() {85'use strict';86return cssClassName;87};8889return renderer;90};919293/**94* Default CSS class to be applied to the root element of containers rendered95* by this renderer.96* @type {string}97*/98goog.ui.ContainerRenderer.CSS_CLASS = goog.getCssName('goog-container');99100101/**102* Returns the ARIA role to be applied to the container.103* See http://wiki/Main/ARIA for more info.104* @return {undefined|string} ARIA role.105*/106goog.ui.ContainerRenderer.prototype.getAriaRole = function() {107'use strict';108return this.ariaRole_;109};110111112/**113* Enables or disables the tab index of the element. Only elements with a114* valid tab index can receive focus.115* @param {Element} element Element whose tab index is to be changed.116* @param {boolean} enable Whether to add or remove the element's tab index.117* @suppress {strictMissingProperties}118*/119goog.ui.ContainerRenderer.prototype.enableTabIndex = function(element, enable) {120'use strict';121if (element) {122element.tabIndex = enable ? 0 : -1;123}124};125126127/**128* Creates and returns the container's root element. The default129* simply creates a DIV and applies the renderer's own CSS class name to it.130* To be overridden in subclasses.131* @param {goog.ui.Container} container Container to render.132* @return {Element} Root element for the container.133*/134goog.ui.ContainerRenderer.prototype.createDom = function(container) {135'use strict';136return container.getDomHelper().createDom(137goog.dom.TagName.DIV, this.getClassNames(container).join(' '));138};139140141/**142* Returns the DOM element into which child components are to be rendered,143* or null if the container hasn't been rendered yet.144* @param {Element} element Root element of the container whose content element145* is to be returned.146* @return {Element} Element to contain child elements (null if none).147*/148goog.ui.ContainerRenderer.prototype.getContentElement = function(element) {149'use strict';150return element;151};152153154/**155* Default implementation of `canDecorate`; returns true if the element156* is a DIV, false otherwise.157* @param {Element} element Element to decorate.158* @return {boolean} Whether the renderer can decorate the element.159*/160goog.ui.ContainerRenderer.prototype.canDecorate = function(element) {161'use strict';162return element.tagName == 'DIV';163};164165166/**167* Default implementation of `decorate` for {@link goog.ui.Container}s.168* Decorates the element with the container, and attempts to decorate its child169* elements. Returns the decorated element.170* @param {goog.ui.Container} container Container to decorate the element.171* @param {Element} element Element to decorate.172* @return {!Element} Decorated element.173*/174goog.ui.ContainerRenderer.prototype.decorate = function(container, element) {175'use strict';176// Set the container's ID to the decorated element's DOM ID, if any.177if (element.id) {178container.setId(element.id);179}180181// Configure the container's state based on the CSS class names it has.182var baseClass = this.getCssClass();183var hasBaseClass = false;184var classNames = goog.dom.classlist.get(element);185if (classNames) {186Array.prototype.forEach.call(classNames, function(className) {187'use strict';188if (className == baseClass) {189hasBaseClass = true;190} else {191if (className) {192this.setStateFromClassName(container, className, baseClass);193}194}195}, this);196}197198if (!hasBaseClass) {199// Make sure the container's root element has the renderer's own CSS class.200goog.dom.classlist.add(element, baseClass);201}202203// Decorate the element's children, if applicable. This should happen after204// the container's own state has been initialized, since how children are205// decorated may depend on the state of the container.206this.decorateChildren(container, this.getContentElement(element));207208return element;209};210211212/**213* Sets the container's state based on the given CSS class name, encountered214* during decoration. CSS class names that don't represent container states215* are ignored. Considered protected; subclasses should override this method216* to support more states and CSS class names.217* @param {goog.ui.Container} container Container to update.218* @param {string} className CSS class name.219* @param {string} baseClass Base class name used as the root of state-specific220* class names (typically the renderer's own class name).221* @protected222* @suppress {missingRequire} goog.ui.Container223*/224goog.ui.ContainerRenderer.prototype.setStateFromClassName = function(225container, className, baseClass) {226'use strict';227if (className == goog.getCssName(baseClass, 'disabled')) {228container.setEnabled(false);229} else if (className == goog.getCssName(baseClass, 'horizontal')) {230container.setOrientation(goog.ui.Container.Orientation.HORIZONTAL);231} else if (className == goog.getCssName(baseClass, 'vertical')) {232container.setOrientation(goog.ui.Container.Orientation.VERTICAL);233}234};235236237/**238* Takes a container and an element that may contain child elements, decorates239* the child elements, and adds the corresponding components to the container240* as child components. Any non-element child nodes (e.g. empty text nodes241* introduced by line breaks in the HTML source) are removed from the element.242* @param {goog.ui.Container} container Container whose children are to be243* discovered.244* @param {Element} element Element whose children are to be decorated.245* @param {Element=} opt_firstChild the first child to be decorated.246*/247goog.ui.ContainerRenderer.prototype.decorateChildren = function(248container, element, opt_firstChild) {249'use strict';250if (element) {251var node = opt_firstChild || element.firstChild, next;252// Tag soup HTML may result in a DOM where siblings have different parents.253while (node && node.parentNode == element) {254// Get the next sibling here, since the node may be replaced or removed.255next = node.nextSibling;256if (node.nodeType == goog.dom.NodeType.ELEMENT) {257// Decorate element node.258var child = this.getDecoratorForChild(/** @type {!Element} */ (node));259if (child) {260// addChild() may need to look at the element.261child.setElementInternal(/** @type {!Element} */ (node));262// If the container is disabled, mark the child disabled too. See263// bug 1263729. Note that this must precede the call to addChild().264if (!container.isEnabled()) {265child.setEnabled(false);266}267container.addChild(child);268child.decorate(/** @type {!Element} */ (node));269}270} else if (!node.nodeValue || goog.string.trim(node.nodeValue) == '') {271// Remove empty text node, otherwise madness ensues (e.g. controls that272// use goog-inline-block will flicker and shift on hover on Gecko).273element.removeChild(node);274}275node = next;276}277}278};279280281/**282* Inspects the element, and creates an instance of {@link goog.ui.Control} or283* an appropriate subclass best suited to decorate it. Returns the control (or284* null if no suitable class was found). This default implementation uses the285* element's CSS class to find the appropriate control class to instantiate.286* May be overridden in subclasses.287* @param {Element} element Element to decorate.288* @return {goog.ui.Control?} A new control suitable to decorate the element289* (null if none).290*/291goog.ui.ContainerRenderer.prototype.getDecoratorForChild = function(element) {292'use strict';293return /** @type {goog.ui.Control} */ (294goog.ui.registry.getDecorator(element));295};296297298/**299* Initializes the container's DOM when the container enters the document.300* Called from {@link goog.ui.Container#enterDocument}.301* @param {goog.ui.Container} container Container whose DOM is to be initialized302* as it enters the document.303*/304goog.ui.ContainerRenderer.prototype.initializeDom = function(container) {305'use strict';306var elem = container.getElement();307goog.asserts.assert(elem, 'The container DOM element cannot be null.');308// Make sure the container's element isn't selectable. On Gecko, recursively309// marking each child element unselectable is expensive and unnecessary, so310// only mark the root element unselectable.311goog.style.setUnselectable(elem, true, goog.userAgent.GECKO);312313// IE doesn't support outline:none, so we have to use the hideFocus property.314if (goog.userAgent.IE) {315elem.hideFocus = true;316}317318// Set the ARIA role.319var ariaRole = this.getAriaRole();320if (ariaRole) {321goog.a11y.aria.setRole(elem, ariaRole);322}323};324325326/**327* Returns the element within the container's DOM that should receive keyboard328* focus (null if none). The default implementation returns the container's329* root element.330* @param {goog.ui.Container} container Container whose key event target is331* to be returned.332* @return {Element} Key event target (null if none).333*/334goog.ui.ContainerRenderer.prototype.getKeyEventTarget = function(container) {335'use strict';336return container.getElement();337};338339340/**341* Returns the CSS class to be applied to the root element of containers342* rendered using this renderer.343* @return {string} Renderer-specific CSS class.344*/345goog.ui.ContainerRenderer.prototype.getCssClass = function() {346'use strict';347return goog.ui.ContainerRenderer.CSS_CLASS;348};349350351/**352* Returns all CSS class names applicable to the given container, based on its353* state. The array of class names returned includes the renderer's own CSS354* class, followed by a CSS class indicating the container's orientation,355* followed by any state-specific CSS classes.356* @param {goog.ui.Container} container Container whose CSS classes are to be357* returned.358* @return {!Array<string>} Array of CSS class names applicable to the359* container.360* @suppress {missingRequire} TODO(user): fix this361*/362goog.ui.ContainerRenderer.prototype.getClassNames = function(container) {363'use strict';364var baseClass = this.getCssClass();365var isHorizontal =366container.getOrientation() == goog.ui.Container.Orientation.HORIZONTAL;367var classNames = [368baseClass, (isHorizontal ? goog.getCssName(baseClass, 'horizontal') :369goog.getCssName(baseClass, 'vertical'))370];371if (!container.isEnabled()) {372classNames.push(goog.getCssName(baseClass, 'disabled'));373}374return classNames;375};376377378/**379* Returns the default orientation of containers rendered or decorated by this380* renderer. The base class implementation returns `VERTICAL`.381* @return {goog.ui.Container.Orientation} Default orientation for containers382* created or decorated by this renderer.383* @suppress {missingRequire} goog.ui.Container384*/385goog.ui.ContainerRenderer.prototype.getDefaultOrientation = function() {386'use strict';387return goog.ui.Container.Orientation.VERTICAL;388};389390391