Path: blob/trunk/third_party/closure/goog/ui/paletterenderer.js
4576 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Renderer for {@link goog.ui.Palette}s.8*/910goog.provide('goog.ui.PaletteRenderer');1112goog.require('goog.a11y.aria');13goog.require('goog.a11y.aria.Role');14goog.require('goog.a11y.aria.State');15goog.require('goog.array');16goog.require('goog.asserts');17goog.require('goog.dom');18goog.require('goog.dom.NodeIterator');19goog.require('goog.dom.NodeType');20goog.require('goog.dom.TagName');21goog.require('goog.dom.classlist');22goog.require('goog.dom.dataset');23goog.require('goog.iter');24goog.require('goog.style');25goog.require('goog.ui.ControlRenderer');26goog.require('goog.userAgent');27goog.requireType('goog.math.Size');28goog.requireType('goog.ui.Control');29goog.requireType('goog.ui.ControlContent');30goog.requireType('goog.ui.Palette');31323334/**35* Default renderer for {@link goog.ui.Palette}s. Renders the palette as an36* HTML table wrapped in a DIV, with one palette item per cell:37*38* <div class="goog-palette">39* <table class="goog-palette-table">40* <tbody class="goog-palette-body">41* <tr class="goog-palette-row">42* <td class="goog-palette-cell">...Item 0...</td>43* <td class="goog-palette-cell">...Item 1...</td>44* ...45* </tr>46* <tr class="goog-palette-row">47* ...48* </tr>49* </tbody>50* </table>51* </div>52*53* @constructor54* @extends {goog.ui.ControlRenderer}55*/56goog.ui.PaletteRenderer = function() {57'use strict';58goog.ui.ControlRenderer.call(this);59};60goog.inherits(goog.ui.PaletteRenderer, goog.ui.ControlRenderer);61goog.addSingletonGetter(goog.ui.PaletteRenderer);626364/**65* Globally unique ID sequence for cells rendered by this renderer class.66* @type {number}67* @private68*/69goog.ui.PaletteRenderer.cellId_ = 0;707172/**73* Default CSS class to be applied to the root element of components rendered74* by this renderer.75* @type {string}76*/77goog.ui.PaletteRenderer.CSS_CLASS = goog.getCssName('goog-palette');787980/**81* Data attribute to store grid width from palette control.82* @const {string}83*/84goog.ui.PaletteRenderer.GRID_WIDTH_ATTRIBUTE = 'gridWidth';858687/**88* Returns the palette items arranged in a table wrapped in a DIV, with the89* renderer's own CSS class and additional state-specific classes applied to90* it.91* @param {goog.ui.Control} palette goog.ui.Palette to render.92* @return {!Element} Root element for the palette.93* @override94* @suppress {strictMissingProperties} Added to tighten compiler checks95*/96goog.ui.PaletteRenderer.prototype.createDom = function(palette) {97'use strict';98var classNames = this.getClassNames(palette);99/** @suppress {strictMissingProperties} Added to tighten compiler checks */100var element = palette.getDomHelper().createDom(101goog.dom.TagName.DIV, classNames,102this.createGrid(103/** @type {Array<Node>} */ (palette.getContent()), palette.getSize(),104palette.getDomHelper()));105// It's safe to store grid width here since `goog.ui.Palette#setSize` cannot106// be called after createDom.107goog.dom.dataset.set(108element, goog.ui.PaletteRenderer.GRID_WIDTH_ATTRIBUTE,109palette.getSize().width);110return element;111};112113114/**115* Returns the given items in a table with `size.width` columns and116* `size.height` rows. If the table is too big, empty cells will be117* created as needed. If the table is too small, the items that don't fit118* will not be rendered.119* @param {Array<Node>} items Palette items.120* @param {goog.math.Size} size Palette size (columns x rows); both dimensions121* must be specified as numbers.122* @param {goog.dom.DomHelper} dom DOM helper for document interaction.123* @return {!Element} Palette table element.124*/125goog.ui.PaletteRenderer.prototype.createGrid = function(items, size, dom) {126'use strict';127var rows = [];128for (var row = 0, index = 0; row < size.height; row++) {129var cells = [];130for (var column = 0; column < size.width; column++) {131var item = items && items[index++];132cells.push(this.createCell(item, dom));133}134rows.push(this.createRow(cells, dom));135}136137return this.createTable(rows, dom);138};139140141/**142* Returns a table element (or equivalent) that wraps the given rows.143* @param {Array<Element>} rows Array of row elements.144* @param {goog.dom.DomHelper} dom DOM helper for document interaction.145* @return {!Element} Palette table element.146*/147goog.ui.PaletteRenderer.prototype.createTable = function(rows, dom) {148'use strict';149var table = dom.createDom(150goog.dom.TagName.TABLE, goog.getCssName(this.getCssClass(), 'table'),151dom.createDom(152goog.dom.TagName.TBODY, goog.getCssName(this.getCssClass(), 'body'),153rows));154goog.a11y.aria.setRole(table, goog.a11y.aria.Role.GRID);155table.cellSpacing = '0';156table.cellPadding = '0';157return table;158};159160161/**162* Returns a table row element (or equivalent) that wraps the given cells.163* @param {Array<Element>} cells Array of cell elements.164* @param {goog.dom.DomHelper} dom DOM helper for document interaction.165* @return {!Element} Row element.166*/167goog.ui.PaletteRenderer.prototype.createRow = function(cells, dom) {168'use strict';169var row = dom.createDom(170goog.dom.TagName.TR, goog.getCssName(this.getCssClass(), 'row'), cells);171goog.a11y.aria.setRole(row, goog.a11y.aria.Role.ROW);172return row;173};174175176/**177* Returns a table cell element (or equivalent) that wraps the given palette178* item (which must be a DOM node).179* @param {Node|string} node Palette item.180* @param {goog.dom.DomHelper} dom DOM helper for document interaction.181* @return {!Element} Cell element.182*/183goog.ui.PaletteRenderer.prototype.createCell = function(node, dom) {184'use strict';185var cell = dom.createDom(186goog.dom.TagName.TD, {187'class': goog.getCssName(this.getCssClass(), 'cell'),188// Cells must have an ID, for accessibility, so we generate one here.189'id': goog.getCssName(this.getCssClass(), 'cell-') +190goog.ui.PaletteRenderer.cellId_++191},192node);193goog.a11y.aria.setRole(cell, goog.a11y.aria.Role.GRIDCELL);194// Initialize to an unselected state.195goog.a11y.aria.setState(cell, goog.a11y.aria.State.SELECTED, false);196this.maybeUpdateAriaLabel_(cell);197198return cell;199};200201202/**203* Updates the aria label of the cell if it doesn't have one. Descends the DOM204* and tries to find an aria label for a grid cell from the first child with a205* label or title.206* @param {!Element} cell The cell.207* @private208*/209goog.ui.PaletteRenderer.prototype.maybeUpdateAriaLabel_ = function(cell) {210'use strict';211if (goog.dom.getTextContent(cell) || goog.a11y.aria.getLabel(cell)) {212return;213}214var iter = new goog.dom.NodeIterator(cell);215var label = '';216var node;217while (!label && (node = goog.iter.nextOrValue(iter, null))) {218if (node.nodeType == goog.dom.NodeType.ELEMENT) {219/**220* @suppress {strictMissingProperties} Added to tighten compiler checks221*/222label =223goog.a11y.aria.getLabel(/** @type {!Element} */ (node)) || node.title;224}225}226if (label) {227goog.a11y.aria.setLabel(cell, label);228}229230return;231};232233234/**235* Overrides {@link goog.ui.ControlRenderer#canDecorate} to always return false.236* @param {Element} element Ignored.237* @return {boolean} False, since palettes don't support the decorate flow (for238* now).239* @override240*/241goog.ui.PaletteRenderer.prototype.canDecorate = function(element) {242'use strict';243return false;244};245246247/**248* Overrides {@link goog.ui.ControlRenderer#decorate} to be a no-op, since249* palettes don't support the decorate flow (for now).250* @param {goog.ui.Control} palette Ignored.251* @param {Element} element Ignored.252* @return {null} Always null.253* @override254*/255goog.ui.PaletteRenderer.prototype.decorate = function(palette, element) {256'use strict';257return null;258};259260261/**262* Overrides {@link goog.ui.ControlRenderer#setContent} for palettes. Locates263* the HTML table representing the palette grid, and replaces the contents of264* each cell with a new element from the array of nodes passed as the second265* argument. If the new content has too many items the table will have more266* rows added to fit, if there are less items than the table has cells, then the267* left over cells will be empty.268* @param {Element} element Root element of the palette control.269* @param {goog.ui.ControlContent} content Array of items to replace existing270* palette items.271* @override272* @suppress {strictPrimitiveOperators}273*/274goog.ui.PaletteRenderer.prototype.setContent = function(element, content) {275'use strict';276var items = /** @type {Array<Node>} */ (content);277if (element) {278var tbody = goog.dom.getElementsByTagNameAndClass(279goog.dom.TagName.TBODY, goog.getCssName(this.getCssClass(), 'body'),280element)[0];281if (tbody) {282var index = 0;283Array.prototype.forEach.call(tbody.rows, function(row) {284'use strict';285goog.array.forEach(row.cells, function(cell) {286'use strict';287goog.dom.removeChildren(cell);288goog.a11y.aria.removeState(cell, goog.a11y.aria.State.LABEL);289if (items) {290var item = items[index++];291if (item) {292goog.dom.appendChild(cell, item);293this.maybeUpdateAriaLabel_(cell);294}295}296}, this);297}, this);298299// Make space for any additional items.300if (index < items.length) {301var cells = [];302var dom = goog.dom.getDomHelper(element);303var width = goog.dom.dataset.get(304element, goog.ui.PaletteRenderer.GRID_WIDTH_ATTRIBUTE);305while (index < items.length) {306var item = items[index++];307cells.push(this.createCell(item, dom));308if (cells.length == width) {309var row = this.createRow(cells, dom);310goog.dom.appendChild(tbody, row);311cells.length = 0;312}313}314if (cells.length > 0) {315while (cells.length < width) {316cells.push(this.createCell('', dom));317}318var row = this.createRow(cells, dom);319goog.dom.appendChild(tbody, row);320}321}322}323// Make sure the new contents are still unselectable.324goog.style.setUnselectable(element, true, goog.userAgent.GECKO);325}326};327328329/**330* Returns the item corresponding to the given node, or null if the node is331* neither a palette cell nor part of a palette item.332* @param {goog.ui.Palette} palette Palette in which to look for the item.333* @param {Node} node Node to look for.334* @return {Node} The corresponding palette item (null if not found).335* @suppress {strictMissingProperties} Added to tighten compiler checks336*/337goog.ui.PaletteRenderer.prototype.getContainingItem = function(palette, node) {338'use strict';339var root = palette.getElement();340while (node && node.nodeType == goog.dom.NodeType.ELEMENT && node != root) {341if (node.tagName == goog.dom.TagName.TD &&342goog.dom.classlist.contains(343/** @type {!Element} */ (node),344goog.getCssName(this.getCssClass(), 'cell'))) {345return node.firstChild;346}347node = node.parentNode;348}349350return null;351};352353354/**355* Updates the highlight styling of the palette cell containing the given node356* based on the value of the Boolean argument.357* @param {goog.ui.Palette} palette Palette containing the item.358* @param {Node} node Item whose cell is to be highlighted or un-highlighted.359* @param {boolean} highlight If true, the cell is highlighted; otherwise it is360* un-highlighted.361*/362goog.ui.PaletteRenderer.prototype.highlightCell = function(363palette, node, highlight) {364'use strict';365if (node) {366var cell = this.getCellForItem(node);367goog.asserts.assert(cell);368goog.dom.classlist.enable(369cell, goog.getCssName(this.getCssClass(), 'cell-hover'), highlight);370// See https://www.w3.org/TR/wai-aria/#aria-activedescendant371// for an explanation of the activedescendant.372if (highlight) {373goog.a11y.aria.setState(374palette.getElementStrict(), goog.a11y.aria.State.ACTIVEDESCENDANT,375cell.id);376} else if (377cell.id ==378goog.a11y.aria.getState(379palette.getElementStrict(),380goog.a11y.aria.State.ACTIVEDESCENDANT)) {381goog.a11y.aria.removeState(382palette.getElementStrict(), goog.a11y.aria.State.ACTIVEDESCENDANT);383}384}385};386387388/**389* @param {Node} node Item whose cell is to be returned.390* @return {Element} The grid cell for the palette item.391*/392goog.ui.PaletteRenderer.prototype.getCellForItem = function(node) {393'use strict';394return /** @type {Element} */ (node ? node.parentNode : null);395};396397398/**399* Updates the selection styling of the palette cell containing the given node400* based on the value of the Boolean argument.401* @param {goog.ui.Palette} palette Palette containing the item.402* @param {Node} node Item whose cell is to be selected or deselected.403* @param {boolean} select If true, the cell is selected; otherwise it is404* deselected.405*/406goog.ui.PaletteRenderer.prototype.selectCell = function(palette, node, select) {407'use strict';408if (node) {409var cell = /** @type {!Element} */ (node.parentNode);410goog.dom.classlist.enable(411cell, goog.getCssName(this.getCssClass(), 'cell-selected'), select);412goog.a11y.aria.setState(cell, goog.a11y.aria.State.SELECTED, select);413}414};415416417/**418* Returns the CSS class to be applied to the root element of components419* rendered using this renderer.420* @return {string} Renderer-specific CSS class.421* @override422*/423goog.ui.PaletteRenderer.prototype.getCssClass = function() {424'use strict';425return goog.ui.PaletteRenderer.CSS_CLASS;426};427428429