// 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;
};