373 lines
12 KiB
JavaScript
373 lines
12 KiB
JavaScript
// 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.a11y.aria');
|
|
goog.require('goog.a11y.aria.Role');
|
|
goog.require('goog.a11y.aria.State');
|
|
goog.require('goog.array');
|
|
goog.require('goog.dom');
|
|
goog.require('goog.dom.NodeIterator');
|
|
goog.require('goog.dom.NodeType');
|
|
goog.require('goog.dom.TagName');
|
|
goog.require('goog.dom.classes');
|
|
goog.require('goog.iter');
|
|
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:
|
|
*
|
|
* <div class="goog-palette">
|
|
* <table class="goog-palette-table">
|
|
* <tbody class="goog-palette-body">
|
|
* <tr class="goog-palette-row">
|
|
* <td class="goog-palette-cell">...Item 0...</td>
|
|
* <td class="goog-palette-cell">...Item 1...</td>
|
|
* ...
|
|
* </tr>
|
|
* <tr class="goog-palette-row">
|
|
* ...
|
|
* </tr>
|
|
* </tbody>
|
|
* </table>
|
|
* </div>
|
|
*
|
|
* @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.Control} palette goog.ui.Palette to render.
|
|
* @return {Element} Root element for the palette.
|
|
* @override
|
|
*/
|
|
goog.ui.PaletteRenderer.prototype.createDom = function(palette) {
|
|
var classNames = this.getClassNames(palette);
|
|
var element = palette.getDomHelper().createDom(
|
|
goog.dom.TagName.DIV, classNames ? classNames.join(' ') : null,
|
|
this.createGrid(/** @type {Array.<Node>} */(palette.getContent()),
|
|
palette.getSize(), palette.getDomHelper()));
|
|
goog.a11y.aria.setRole(element, goog.a11y.aria.Role.GRID);
|
|
return element;
|
|
};
|
|
|
|
|
|
/**
|
|
* 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.<Node>} 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.<Element>} 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(goog.dom.TagName.TABLE,
|
|
goog.getCssName(this.getCssClass(), 'table'),
|
|
dom.createDom(goog.dom.TagName.TBODY,
|
|
goog.getCssName(this.getCssClass(), 'body'), rows));
|
|
table.cellSpacing = 0;
|
|
table.cellPadding = 0;
|
|
return table;
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns a table row element (or equivalent) that wraps the given cells.
|
|
* @param {Array.<Element>} 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) {
|
|
var row = dom.createDom(goog.dom.TagName.TR,
|
|
goog.getCssName(this.getCssClass(), 'row'), cells);
|
|
goog.a11y.aria.setRole(row, goog.a11y.aria.Role.ROW);
|
|
return row;
|
|
};
|
|
|
|
|
|
/**
|
|
* 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(goog.dom.TagName.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.a11y.aria.setRole(cell, goog.a11y.aria.Role.GRIDCELL);
|
|
|
|
if (!goog.dom.getTextContent(cell) && !goog.a11y.aria.getLabel(cell)) {
|
|
var ariaLabelForCell = this.findAriaLabelForCell_(cell);
|
|
if (ariaLabelForCell) {
|
|
goog.a11y.aria.setLabel(cell, ariaLabelForCell);
|
|
}
|
|
}
|
|
return cell;
|
|
};
|
|
|
|
|
|
/**
|
|
* Descends the DOM and tries to find an aria label for a grid cell
|
|
* from the first child with a label or title.
|
|
* @param {!Element} cell The cell.
|
|
* @return {string} The label to use.
|
|
* @private
|
|
*/
|
|
goog.ui.PaletteRenderer.prototype.findAriaLabelForCell_ = function(cell) {
|
|
var iter = new goog.dom.NodeIterator(cell);
|
|
var label = '';
|
|
var node;
|
|
while (!label && (node = goog.iter.nextOrValue(iter, null))) {
|
|
if (node.nodeType == goog.dom.NodeType.ELEMENT) {
|
|
label = goog.a11y.aria.getLabel(/** @type {!Element} */ (node)) ||
|
|
node.title;
|
|
}
|
|
}
|
|
return label;
|
|
};
|
|
|
|
|
|
/**
|
|
* 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).
|
|
* @override
|
|
*/
|
|
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.Control} palette Ignored.
|
|
* @param {Element} element Ignored.
|
|
* @return {null} Always null.
|
|
* @override
|
|
*/
|
|
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 {goog.ui.ControlContent} content Array of items to replace existing
|
|
* palette items.
|
|
* @override
|
|
*/
|
|
goog.ui.PaletteRenderer.prototype.setContent = function(element, content) {
|
|
var items = /** @type {Array.<Node>} */ (content);
|
|
if (element) {
|
|
var tbody = goog.dom.getElementsByTagNameAndClass(goog.dom.TagName.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 == goog.dom.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 = this.getCellForItem(node);
|
|
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.
|
|
goog.a11y.aria.setState(palette.getElementStrict(),
|
|
goog.a11y.aria.State.ACTIVEDESCENDANT, cell.id);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {Node} node Item whose cell is to be returned.
|
|
* @return {Element} The grid cell for the palette item.
|
|
*/
|
|
goog.ui.PaletteRenderer.prototype.getCellForItem = function(node) {
|
|
return /** @type {Element} */ (node ? node.parentNode : null);
|
|
};
|
|
|
|
|
|
/**
|
|
* 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.
|
|
* @override
|
|
*/
|
|
goog.ui.PaletteRenderer.prototype.getCssClass = function() {
|
|
return goog.ui.PaletteRenderer.CSS_CLASS;
|
|
};
|