1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
|
// 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 The default renderer for a goog.dom.DimensionPicker. A
* dimension picker allows the user to visually select a row and column count.
* It looks like a palette but in order to minimize DOM load it is rendered.
* using CSS background tiling instead of as a grid of nodes.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.ui.DimensionPickerRenderer');
goog.require('goog.a11y.aria.Announcer');
goog.require('goog.a11y.aria.LivePriority');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.i18n.bidi');
goog.require('goog.style');
goog.require('goog.ui.ControlRenderer');
goog.require('goog.userAgent');
/**
* Default renderer for {@link goog.ui.DimensionPicker}s. Renders the
* palette as two divs, one with the un-highlighted background, and one with the
* highlighted background.
*
* @constructor
* @extends {goog.ui.ControlRenderer}
*/
goog.ui.DimensionPickerRenderer = function() {
goog.ui.ControlRenderer.call(this);
/** @private {goog.a11y.aria.Announcer} */
this.announcer_ = new goog.a11y.aria.Announcer();
};
goog.inherits(goog.ui.DimensionPickerRenderer, goog.ui.ControlRenderer);
goog.addSingletonGetter(goog.ui.DimensionPickerRenderer);
/**
* Default CSS class to be applied to the root element of components rendered
* by this renderer.
* @type {string}
*/
goog.ui.DimensionPickerRenderer.CSS_CLASS =
goog.getCssName('goog-dimension-picker');
/**
* Return the underlying div for the given outer element.
* @param {Element} element The root element.
* @return {Element} The underlying div.
* @private
*/
goog.ui.DimensionPickerRenderer.prototype.getUnderlyingDiv_ = function(
element) {
return /** @type {Element} */ (element.firstChild.childNodes[1]);
};
/**
* Return the highlight div for the given outer element.
* @param {Element} element The root element.
* @return {Element} The highlight div.
* @private
*/
goog.ui.DimensionPickerRenderer.prototype.getHighlightDiv_ = function(element) {
return /** @type {Element} */ (element.firstChild.lastChild);
};
/**
* Return the status message div for the given outer element.
* @param {Element} element The root element.
* @return {Element} The status message div.
* @private
*/
goog.ui.DimensionPickerRenderer.prototype.getStatusDiv_ = function(element) {
return /** @type {Element} */ (element.lastChild);
};
/**
* Return the invisible mouse catching div for the given outer element.
* @param {Element} element The root element.
* @return {Element} The invisible mouse catching div.
* @private
*/
goog.ui.DimensionPickerRenderer.prototype.getMouseCatcher_ = function(element) {
return /** @type {Element} */ (element.firstChild.firstChild);
};
/**
* Overrides {@link goog.ui.ControlRenderer#canDecorate} to allow decorating
* empty DIVs only.
* @param {Element} element The element to check.
* @return {boolean} Whether if the element is an empty div.
* @override
*/
goog.ui.DimensionPickerRenderer.prototype.canDecorate = function(element) {
return element.tagName == goog.dom.TagName.DIV && !element.firstChild;
};
/**
* Overrides {@link goog.ui.ControlRenderer#decorate} to decorate empty DIVs.
* @param {goog.ui.Control} control goog.ui.DimensionPicker to decorate.
* @param {Element} element The element to decorate.
* @return {Element} The decorated element.
* @override
*/
goog.ui.DimensionPickerRenderer.prototype.decorate = function(
control, element) {
var palette = /** @type {goog.ui.DimensionPicker} */ (control);
goog.ui.DimensionPickerRenderer.superClass_.decorate.call(
this, palette, element);
this.addElementContents_(palette, element);
this.updateSize(palette, element);
return element;
};
/**
* Scales various elements in order to update the palette's size.
* @param {goog.ui.DimensionPicker} palette The palette object.
* @param {Element} element The element to set the style of.
*/
goog.ui.DimensionPickerRenderer.prototype.updateSize = function(
palette, element) {
var size = palette.getSize();
element.style.width = size.width + 'em';
var underlyingDiv = this.getUnderlyingDiv_(element);
underlyingDiv.style.width = size.width + 'em';
underlyingDiv.style.height = size.height + 'em';
if (palette.isRightToLeft()) {
this.adjustParentDirection_(palette, element);
}
};
/**
* Adds the appropriate content elements to the given outer DIV.
* @param {goog.ui.DimensionPicker} palette The palette object.
* @param {Element} element The element to decorate.
* @private
*/
goog.ui.DimensionPickerRenderer.prototype.addElementContents_ = function(
palette, element) {
// First we create a single div containing three stacked divs. The bottom div
// catches mouse events. We can't use document level mouse move detection as
// we could lose events to iframes. This is especially important in Firefox 2
// in which TrogEdit creates iframes. The middle div uses a css tiled
// background image to represent deselected tiles. The top div uses a
// different css tiled background image to represent selected tiles.
var mouseCatcherDiv = palette.getDomHelper().createDom(
goog.dom.TagName.DIV,
goog.getCssName(this.getCssClass(), 'mousecatcher'));
var unhighlightedDiv =
palette.getDomHelper().createDom(goog.dom.TagName.DIV, {
'class': goog.getCssName(this.getCssClass(), 'unhighlighted'),
'style': 'width:100%;height:100%'
});
var highlightedDiv = palette.getDomHelper().createDom(
goog.dom.TagName.DIV, goog.getCssName(this.getCssClass(), 'highlighted'));
element.appendChild(
palette.getDomHelper().createDom(
goog.dom.TagName.DIV, {'style': 'width:100%;height:100%'},
mouseCatcherDiv, unhighlightedDiv, highlightedDiv));
// Lastly we add a div to store the text version of the current state.
element.appendChild(
palette.getDomHelper().createDom(
goog.dom.TagName.DIV, goog.getCssName(this.getCssClass(), 'status')));
};
/**
* Creates a div and adds the appropriate contents to it.
* @param {goog.ui.Control} control Picker to render.
* @return {!Element} Root element for the palette.
* @override
*/
goog.ui.DimensionPickerRenderer.prototype.createDom = function(control) {
var palette = /** @type {goog.ui.DimensionPicker} */ (control);
var classNames = this.getClassNames(palette);
// Hide the element from screen readers so they don't announce "1 of 1" for
// the perceived number of items in the palette.
var element = palette.getDomHelper().createDom(
goog.dom.TagName.DIV,
{'class': classNames ? classNames.join(' ') : '', 'aria-hidden': 'true'});
this.addElementContents_(palette, element);
this.updateSize(palette, element);
return element;
};
/**
* Initializes the control's DOM when the control enters the document. Called
* from {@link goog.ui.Control#enterDocument}.
* @param {goog.ui.Control} control Palette whose DOM is to be
* initialized as it enters the document.
* @override
*/
goog.ui.DimensionPickerRenderer.prototype.initializeDom = function(control) {
var palette = /** @type {goog.ui.DimensionPicker} */ (control);
goog.ui.DimensionPickerRenderer.superClass_.initializeDom.call(this, palette);
// Make the displayed highlighted size match the dimension picker's value.
var highlightedSize = palette.getValue();
this.setHighlightedSize(
palette, highlightedSize.width, highlightedSize.height);
this.positionMouseCatcher(palette);
};
/**
* Get the element to listen for mouse move events on.
* @param {goog.ui.DimensionPicker} palette The palette to listen on.
* @return {Element} The element to listen for mouse move events on.
*/
goog.ui.DimensionPickerRenderer.prototype.getMouseMoveElement = function(
palette) {
return /** @type {Element} */ (palette.getElement().firstChild);
};
/**
* Returns the x offset in to the grid for the given mouse x position.
* @param {goog.ui.DimensionPicker} palette The table size palette.
* @param {number} x The mouse event x position.
* @return {number} The x offset in to the grid.
*/
goog.ui.DimensionPickerRenderer.prototype.getGridOffsetX = function(
palette, x) {
// TODO(robbyw): Don't rely on magic 18 - measure each palette's em size.
return Math.min(palette.maxColumns, Math.ceil(x / 18));
};
/**
* Returns the y offset in to the grid for the given mouse y position.
* @param {goog.ui.DimensionPicker} palette The table size palette.
* @param {number} y The mouse event y position.
* @return {number} The y offset in to the grid.
*/
goog.ui.DimensionPickerRenderer.prototype.getGridOffsetY = function(
palette, y) {
return Math.min(palette.maxRows, Math.ceil(y / 18));
};
/**
* Sets the highlighted size. Does nothing if the palette hasn't been rendered.
* @param {goog.ui.DimensionPicker} palette The table size palette.
* @param {number} columns The number of columns to highlight.
* @param {number} rows The number of rows to highlight.
*/
goog.ui.DimensionPickerRenderer.prototype.setHighlightedSize = function(
palette, columns, rows) {
var element = palette.getElement();
// Can't update anything if DimensionPicker hasn't been rendered.
if (!element) {
return;
}
// Style the highlight div.
var style = this.getHighlightDiv_(element).style;
style.width = columns + 'em';
style.height = rows + 'em';
// Explicitly set style.right so the element grows to the left when increase
// in width.
if (palette.isRightToLeft()) {
style.right = '0';
}
/**
* @desc The dimension of the columns and rows currently selected in the
* dimension picker, as text that can be spoken by a screen reader.
*/
var MSG_DIMENSION_PICKER_HIGHLIGHTED_DIMENSIONS = goog.getMsg(
'{$numCols} by {$numRows}',
{'numCols': String(columns), 'numRows': String(rows)});
this.announcer_.say(
MSG_DIMENSION_PICKER_HIGHLIGHTED_DIMENSIONS,
goog.a11y.aria.LivePriority.ASSERTIVE);
// Update the size text.
goog.dom.setTextContent(
this.getStatusDiv_(element),
goog.i18n.bidi.enforceLtrInText(columns + ' x ' + rows));
};
/**
* Position the mouse catcher such that it receives mouse events past the
* selectedsize up to the maximum size. Takes care to not introduce scrollbars.
* Should be called on enter document and when the window changes size.
* @param {goog.ui.DimensionPicker} palette The table size palette.
*/
goog.ui.DimensionPickerRenderer.prototype.positionMouseCatcher = function(
palette) {
var mouseCatcher = this.getMouseCatcher_(palette.getElement());
var doc = goog.dom.getOwnerDocument(mouseCatcher);
var body = doc.body;
var position = goog.style.getRelativePosition(mouseCatcher, body);
// Hide the mouse catcher so it doesn't affect the body's scroll size.
mouseCatcher.style.display = 'none';
// Compute the maximum size the catcher can be without introducing scrolling.
var xAvailableEm = (palette.isRightToLeft() && position.x > 0) ?
Math.floor(position.x / 18) :
Math.floor((body.scrollWidth - position.x) / 18);
// Computing available height is more complicated - we need to check the
// window's inner height.
var height;
if (goog.userAgent.IE) {
// Offset 20px to make up for scrollbar size.
height = goog.style.getClientViewportElement(body).scrollHeight - 20;
} else {
var win = goog.dom.getWindow(doc);
// Offset 20px to make up for scrollbar size.
height = Math.max(win.innerHeight, body.scrollHeight) - 20;
}
var yAvailableEm = Math.floor((height - position.y) / 18);
// Resize and display the mouse catcher.
mouseCatcher.style.width = Math.min(palette.maxColumns, xAvailableEm) + 'em';
mouseCatcher.style.height = Math.min(palette.maxRows, yAvailableEm) + 'em';
mouseCatcher.style.display = '';
// Explicitly set style.right so the mouse catcher is positioned on the left
// side instead of right.
if (palette.isRightToLeft()) {
mouseCatcher.style.right = '0';
}
};
/**
* 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.DimensionPickerRenderer.prototype.getCssClass = function() {
return goog.ui.DimensionPickerRenderer.CSS_CLASS;
};
/**
* This function adjusts the positioning from 'left' and 'top' to 'right' and
* 'top' as appropriate for RTL control. This is so when the dimensionpicker
* grow in width, the containing element grow to the left instead of right.
* This won't be necessary if goog.ui.SubMenu rendering code would position RTL
* control with 'right' and 'top'.
* @private
*
* @param {goog.ui.DimensionPicker} palette The palette object.
* @param {Element} element The palette's element.
*/
goog.ui.DimensionPickerRenderer.prototype.adjustParentDirection_ = function(
palette, element) {
var parent = palette.getParent();
if (parent) {
var parentElement = parent.getElement();
// Anchors the containing element to the right so it grows to the left
// when it increase in width.
var right = goog.style.getStyle(parentElement, 'right');
if (right == '') {
var parentPos = goog.style.getPosition(parentElement);
var parentSize = goog.style.getSize(parentElement);
if (parentSize.width != 0 && parentPos.x != 0) {
var visibleRect =
goog.style.getBounds(goog.style.getClientViewportElement());
var visibleWidth = visibleRect.width;
right = visibleWidth - parentPos.x - parentSize.width;
goog.style.setStyle(parentElement, 'right', right + 'px');
}
}
// When a table is inserted, the containing elemet's position is
// recalculated the next time it shows, set left back to '' to prevent
// extra white space on the left.
var left = goog.style.getStyle(parentElement, 'left');
if (left != '') {
goog.style.setStyle(parentElement, 'left', '');
}
} else {
goog.style.setStyle(element, 'right', '0px');
}
};
|