// 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.Palette}s. * * @author attila@google.com (Attila Bodis) */ goog.provide('goog.ui.PaletteRenderer'); goog.require('goog.array'); goog.require('goog.dom'); goog.require('goog.dom.NodeType'); goog.require('goog.dom.a11y'); goog.require('goog.dom.classes'); goog.require('goog.style'); goog.require('goog.ui.ControlRenderer'); goog.require('goog.userAgent'); /** * Default renderer for {@link goog.ui.Palette}s. Renders the palette as an * HTML table wrapped in a DIV, with one palette item per cell: * *
* * * * * * ... * * * ... * * *
...Item 0......Item 1...
*
* * @constructor * @extends {goog.ui.ControlRenderer} */ goog.ui.PaletteRenderer = function() { goog.ui.ControlRenderer.call(this); }; goog.inherits(goog.ui.PaletteRenderer, goog.ui.ControlRenderer); goog.addSingletonGetter(goog.ui.PaletteRenderer); /** * Globally unique ID sequence for cells rendered by this renderer class. * @type {number} * @private */ goog.ui.PaletteRenderer.cellId_ = 0; /** * Default CSS class to be applied to the root element of components rendered * by this renderer. * @type {string} */ goog.ui.PaletteRenderer.CSS_CLASS = goog.getCssName('goog-palette'); /** * Returns the palette items arranged in a table wrapped in a DIV, with the * renderer's own CSS class and additional state-specific classes applied to * it. * @param {goog.ui.Palette} palette Palette to render. * @return {Element} Root element for the palette. */ goog.ui.PaletteRenderer.prototype.createDom = function(palette) { var classNames = this.getClassNames(palette); return palette.getDomHelper().createDom( 'div', classNames ? classNames.join(' ') : null, this.createGrid(/** @type {Array.} */(palette.getContent()), palette.getSize(), palette.getDomHelper())); }; /** * Returns the given items in a table with {@code size.width} columns and * {@code size.height} rows. If the table is too big, empty cells will be * created as needed. If the table is too small, the items that don't fit * will not be rendered. * @param {Array.} items Palette items. * @param {goog.math.Size} size Palette size (columns x rows); both dimensions * must be specified as numbers. * @param {goog.dom.DomHelper} dom DOM helper for document interaction. * @return {Element} Palette table element. */ goog.ui.PaletteRenderer.prototype.createGrid = function(items, size, dom) { var rows = []; for (var row = 0, index = 0; row < size.height; row++) { var cells = []; for (var column = 0; column < size.width; column++) { var item = items && items[index++]; cells.push(this.createCell(item, dom)); } rows.push(this.createRow(cells, dom)); } return this.createTable(rows, dom); }; /** * Returns a table element (or equivalent) that wraps the given rows. * @param {Array.} rows Array of row elements. * @param {goog.dom.DomHelper} dom DOM helper for document interaction. * @return {Element} Palette table element. */ goog.ui.PaletteRenderer.prototype.createTable = function(rows, dom) { var table = dom.createDom('table', goog.getCssName(this.getCssClass(), 'table'), dom.createDom('tbody', goog.getCssName(this.getCssClass(), 'body'), rows)); table.cellSpacing = 0; table.cellPadding = 0; goog.dom.a11y.setRole(table, 'grid'); return table; }; /** * Returns a table row element (or equivalent) that wraps the given cells. * @param {Array.} cells Array of cell elements. * @param {goog.dom.DomHelper} dom DOM helper for document interaction. * @return {Element} Row element. */ goog.ui.PaletteRenderer.prototype.createRow = function(cells, dom) { return dom.createDom('tr', goog.getCssName(this.getCssClass(), 'row'), cells); }; /** * Returns a table cell element (or equivalent) that wraps the given palette * item (which must be a DOM node). * @param {Node|string} node Palette item. * @param {goog.dom.DomHelper} dom DOM helper for document interaction. * @return {Element} Cell element. */ goog.ui.PaletteRenderer.prototype.createCell = function(node, dom) { var cell = dom.createDom('td', { 'class': goog.getCssName(this.getCssClass(), 'cell'), // Cells must have an ID, for accessibility, so we generate one here. 'id': goog.getCssName(this.getCssClass(), 'cell-') + goog.ui.PaletteRenderer.cellId_++ }, node); goog.dom.a11y.setRole(cell, 'gridcell'); return cell; }; /** * Overrides {@link goog.ui.ControlRenderer#canDecorate} to always return false. * @param {Element} element Ignored. * @return {boolean} False, since palettes don't support the decorate flow (for * now). */ goog.ui.PaletteRenderer.prototype.canDecorate = function(element) { return false; }; /** * Overrides {@link goog.ui.ControlRenderer#decorate} to be a no-op, since * palettes don't support the decorate flow (for now). * @param {goog.ui.Palette} palette Ignored. * @param {Element} element Ignored. * @return {null} Always null. */ goog.ui.PaletteRenderer.prototype.decorate = function(palette, element) { return null; }; /** * Overrides {@link goog.ui.ControlRenderer#setContent} for palettes. Locates * the HTML table representing the palette grid, and replaces the contents of * each cell with a new element from the array of nodes passed as the second * argument. If the new content has too many items the table will have more * rows added to fit, if there are less items than the table has cells, then the * left over cells will be empty. * @param {Element} element Root element of the palette control. * @param {Array.} items Array of items to replace existing palette items. */ goog.ui.PaletteRenderer.prototype.setContent = function(element, items) { if (element) { var tbody = goog.dom.getElementsByTagNameAndClass( 'tbody', goog.getCssName(this.getCssClass(), 'body'), element)[0]; if (tbody) { var index = 0; goog.array.forEach(tbody.rows, function(row) { goog.array.forEach(row.cells, function(cell) { goog.dom.removeChildren(cell); if (items) { var item = items[index++]; if (item) { goog.dom.appendChild(cell, item); } } }); }); // Make space for any additional items. if (index < items.length) { var cells = []; var dom = goog.dom.getDomHelper(element); var width = tbody.rows[0].cells.length; while (index < items.length) { var item = items[index++]; cells.push(this.createCell(item, dom)); if (cells.length == width) { var row = this.createRow(cells, dom); goog.dom.appendChild(tbody, row); cells.length = 0; } } if (cells.length > 0) { while (cells.length < width) { cells.push(this.createCell('', dom)); } var row = this.createRow(cells, dom); goog.dom.appendChild(tbody, row); } } } // Make sure the new contents are still unselectable. goog.style.setUnselectable(element, true, goog.userAgent.GECKO); } }; /** * Returns the item corresponding to the given node, or null if the node is * neither a palette cell nor part of a palette item. * @param {goog.ui.Palette} palette Palette in which to look for the item. * @param {Node} node Node to look for. * @return {Node} The corresponding palette item (null if not found). */ goog.ui.PaletteRenderer.prototype.getContainingItem = function(palette, node) { var root = palette.getElement(); while (node && node.nodeType == goog.dom.NodeType.ELEMENT && node != root) { if (node.tagName == 'TD' && goog.dom.classes.has( /** @type {Element} */ (node), goog.getCssName(this.getCssClass(), 'cell'))) { return node.firstChild; } node = node.parentNode; } return null; }; /** * Updates the highlight styling of the palette cell containing the given node * based on the value of the Boolean argument. * @param {goog.ui.Palette} palette Palette containing the item. * @param {Node} node Item whose cell is to be highlighted or un-highlighted. * @param {boolean} highlight If true, the cell is highlighted; otherwise it is * un-highlighted. */ goog.ui.PaletteRenderer.prototype.highlightCell = function(palette, node, highlight) { if (node) { var cell = /** @type {Element} */ (node.parentNode); goog.dom.classes.enable(cell, goog.getCssName(this.getCssClass(), 'cell-hover'), highlight); // See http://www.w3.org/TR/2006/WD-aria-state-20061220/#activedescendent // for an explanation of the activedescendent. var table = /** @type {Element} */ (palette.getElement().firstChild); goog.dom.a11y.setState(table, 'activedescendent', cell.id); } }; /** * Updates the selection styling of the palette cell containing the given node * based on the value of the Boolean argument. * @param {goog.ui.Palette} palette Palette containing the item. * @param {Node} node Item whose cell is to be selected or deselected. * @param {boolean} select If true, the cell is selected; otherwise it is * deselected. */ goog.ui.PaletteRenderer.prototype.selectCell = function(palette, node, select) { if (node) { var cell = /** @type {Element} */ (node.parentNode); goog.dom.classes.enable(cell, goog.getCssName(this.getCssClass(), 'cell-selected'), select); } }; /** * Returns the CSS class to be applied to the root element of components * rendered using this renderer. * @return {string} Renderer-specific CSS class. */ goog.ui.PaletteRenderer.prototype.getCssClass = function() { return goog.ui.PaletteRenderer.CSS_CLASS; };