Path: blob/trunk/third_party/closure/goog/ui/custombuttonrenderer.js
4049 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview A custom button renderer that uses CSS voodoo to render a8* button-like object with fake rounded corners.9*/1011goog.provide('goog.ui.CustomButtonRenderer');1213goog.require('goog.a11y.aria.Role');14goog.require('goog.asserts');15goog.require('goog.dom.NodeType');16goog.require('goog.dom.TagName');17goog.require('goog.dom.classlist');18goog.require('goog.string');19goog.require('goog.ui.ButtonRenderer');20goog.require('goog.ui.INLINE_BLOCK_CLASSNAME');21goog.requireType('goog.dom.DomHelper');22goog.requireType('goog.ui.Button');23goog.requireType('goog.ui.Control');24goog.requireType('goog.ui.ControlContent');25262728/**29* Custom renderer for {@link goog.ui.Button}s. Custom buttons can contain30* almost arbitrary HTML content, will flow like inline elements, but can be31* styled like block-level elements.32*33* @constructor34* @extends {goog.ui.ButtonRenderer}35*/36goog.ui.CustomButtonRenderer = function() {37'use strict';38goog.ui.ButtonRenderer.call(this);39};40goog.inherits(goog.ui.CustomButtonRenderer, goog.ui.ButtonRenderer);41goog.addSingletonGetter(goog.ui.CustomButtonRenderer);424344/**45* Default CSS class to be applied to the root element of components rendered46* by this renderer.47* @type {string}48*/49goog.ui.CustomButtonRenderer.CSS_CLASS = goog.getCssName('goog-custom-button');505152/**53* Returns the button's contents wrapped in the following DOM structure:54*55* <div class="goog-inline-block goog-custom-button">56* <div class="goog-inline-block goog-custom-button-outer-box">57* <div class="goog-inline-block goog-custom-button-inner-box">58* Contents...59* </div>60* </div>61* </div>62*63* Overrides {@link goog.ui.ButtonRenderer#createDom}.64* @param {goog.ui.Control} control goog.ui.Button to render.65* @return {!Element} Root element for the button.66* @override67*/68goog.ui.CustomButtonRenderer.prototype.createDom = function(control) {69'use strict';70var button = /** @type {goog.ui.Button} */ (control);71var classNames = this.getClassNames(button);72var buttonElement = button.getDomHelper().createDom(73goog.dom.TagName.DIV,74goog.ui.INLINE_BLOCK_CLASSNAME + ' ' + classNames.join(' '),75this.createButton(button.getContent(), button.getDomHelper()));76this.setTooltip(buttonElement, /** @type {string}*/ (button.getTooltip()));7778return buttonElement;79};808182/**83* Returns the ARIA role to be applied to custom buttons.84* @return {goog.a11y.aria.Role|undefined} ARIA role.85* @override86*/87goog.ui.CustomButtonRenderer.prototype.getAriaRole = function() {88'use strict';89return goog.a11y.aria.Role.BUTTON;90};919293/**94* Takes the button's root element and returns the parent element of the95* button's contents. Overrides the superclass implementation by taking96* the nested DIV structure of custom buttons into account.97* @param {Element} element Root element of the button whose content98* element is to be returned.99* @return {Element} The button's content element (if any).100* @override101*/102goog.ui.CustomButtonRenderer.prototype.getContentElement = function(element) {103'use strict';104return element && element.firstChild &&105/** @type {Element} */ (element.firstChild.firstChild);106};107108109/**110* Takes a text caption or existing DOM structure, and returns the content111* wrapped in a pseudo-rounded-corner box. Creates the following DOM structure:112*113* <div class="goog-inline-block goog-custom-button-outer-box">114* <div class="goog-inline-block goog-custom-button-inner-box">115* Contents...116* </div>117* </div>118*119* Used by both {@link #createDom} and {@link #decorate}. To be overridden120* by subclasses.121* @param {goog.ui.ControlContent} content Text caption or DOM structure to wrap122* in a box.123* @param {goog.dom.DomHelper} dom DOM helper, used for document interaction.124* @return {!Element} Pseudo-rounded-corner box containing the content.125*/126goog.ui.CustomButtonRenderer.prototype.createButton = function(content, dom) {127'use strict';128return dom.createDom(129goog.dom.TagName.DIV,130goog.ui.INLINE_BLOCK_CLASSNAME + ' ' +131goog.getCssName(this.getCssClass(), 'outer-box'),132dom.createDom(133goog.dom.TagName.DIV,134goog.ui.INLINE_BLOCK_CLASSNAME + ' ' +135goog.getCssName(this.getCssClass(), 'inner-box'),136content));137};138139140/**141* Returns true if this renderer can decorate the element. Overrides142* {@link goog.ui.ButtonRenderer#canDecorate} by returning true if the143* element is a DIV, false otherwise.144* @param {Element} element Element to decorate.145* @return {boolean} Whether the renderer can decorate the element.146* @override147*/148goog.ui.CustomButtonRenderer.prototype.canDecorate = function(element) {149'use strict';150return element.tagName == goog.dom.TagName.DIV;151};152153154/**155* Check if the button's element has a box structure.156* @param {goog.ui.Button} button Button instance whose structure is being157* checked.158* @param {Element} element Element of the button.159* @return {boolean} Whether the element has a box structure.160* @protected161*/162goog.ui.CustomButtonRenderer.prototype.hasBoxStructure = function(163button, element) {164'use strict';165var outer = button.getDomHelper().getFirstElementChild(element);166var outerClassName = goog.getCssName(this.getCssClass(), 'outer-box');167if (outer && goog.dom.classlist.contains(outer, outerClassName)) {168var inner = button.getDomHelper().getFirstElementChild(outer);169var innerClassName = goog.getCssName(this.getCssClass(), 'inner-box');170if (inner && goog.dom.classlist.contains(inner, innerClassName)) {171// We have a proper box structure.172return true;173}174}175return false;176};177178179/**180* Takes an existing element and decorates it with the custom button control.181* Initializes the control's ID, content, tooltip, value, and state based182* on the ID of the element, its child nodes, and its CSS classes, respectively.183* Returns the element. Overrides {@link goog.ui.ButtonRenderer#decorate}.184* @param {goog.ui.Control} control Button instance to decorate the element.185* @param {Element} element Element to decorate.186* @return {Element} Decorated element.187* @override188*/189goog.ui.CustomButtonRenderer.prototype.decorate = function(control, element) {190'use strict';191goog.asserts.assert(element);192193var button = /** @type {goog.ui.Button} */ (control);194// Trim text nodes in the element's child node list; otherwise madness195// ensues (i.e. on Gecko, buttons will flicker and shift when moused over).196goog.ui.CustomButtonRenderer.trimTextNodes_(element, true);197goog.ui.CustomButtonRenderer.trimTextNodes_(element, false);198199// Create the buttom dom if it has not been created.200if (!this.hasBoxStructure(button, element)) {201element.appendChild(202/** @type {!Node} */ (203this.createButton(element.childNodes, button.getDomHelper())));204}205206goog.dom.classlist.addAll(207element, [goog.ui.INLINE_BLOCK_CLASSNAME, this.getCssClass()]);208return goog.ui.CustomButtonRenderer.superClass_.decorate.call(209this, button, element);210};211212213/**214* Returns the CSS class to be applied to the root element of components215* rendered using this renderer.216* @return {string} Renderer-specific CSS class.217* @override218*/219goog.ui.CustomButtonRenderer.prototype.getCssClass = function() {220'use strict';221return goog.ui.CustomButtonRenderer.CSS_CLASS;222};223224225/**226* Takes an element and removes leading or trailing whitespace from the start227* or the end of its list of child nodes. The Boolean argument determines228* whether to trim from the start or the end of the node list. Empty text229* nodes are removed, and the first non-empty text node is trimmed from the230* left or the right as appropriate. For example,231*232* <div class="goog-inline-block">233* #text ""234* #text "\n Hello "235* <span>...</span>236* #text " World! \n"237* #text ""238* </div>239*240* becomes241*242* <div class="goog-inline-block">243* #text "Hello "244* <span>...</span>245* #text " World!"246* </div>247*248* This is essential for Gecko, where leading/trailing whitespace messes with249* the layout of elements with -moz-inline-box (used in goog-inline-block), and250* optional but harmless for non-Gecko.251*252* @param {Element} element Element whose child node list is to be trimmed.253* @param {boolean} fromStart Whether to trim from the start or from the end.254* @private255*/256goog.ui.CustomButtonRenderer.trimTextNodes_ = function(element, fromStart) {257'use strict';258if (element) {259var node = fromStart ? element.firstChild : element.lastChild, next;260// Tag soup HTML may result in a DOM where siblings have different parents.261while (node && node.parentNode == element) {262// Get the next/previous sibling here, since the node may be removed.263next = fromStart ? node.nextSibling : node.previousSibling;264if (node.nodeType == goog.dom.NodeType.TEXT) {265// Found a text node.266var text = node.nodeValue;267if (goog.string.trim(text) == '') {268// Found an empty text node; remove it.269element.removeChild(node);270} else {271// Found a non-empty text node; trim from the start/end, then exit.272node.nodeValue = fromStart ? goog.string.trimLeft(text) :273goog.string.trimRight(text);274break;275}276} else {277// Found a non-text node; done.278break;279}280node = next;281}282}283};284285286