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
|
// 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 Base class for container renderers.
*
* @author attila@google.com (Attila Bodis)
*/
goog.provide('goog.ui.ContainerRenderer');
goog.require('goog.a11y.aria');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classlist');
goog.require('goog.string');
goog.require('goog.style');
goog.require('goog.ui.registry');
goog.require('goog.userAgent');
/**
* Default renderer for {@link goog.ui.Container}. Can be used as-is, but
* subclasses of Container will probably want to use renderers specifically
* tailored for them by extending this class.
* @param {string=} opt_ariaRole Optional ARIA role used for the element.
* @constructor
*/
goog.ui.ContainerRenderer = function(opt_ariaRole) {
// By default, the ARIA role is unspecified.
/** @private {string|undefined} */
this.ariaRole_ = opt_ariaRole;
};
goog.addSingletonGetter(goog.ui.ContainerRenderer);
/**
* Constructs a new renderer and sets the CSS class that the renderer will use
* as the base CSS class to apply to all elements rendered by that renderer.
* An example to use this function using a menu is:
*
* <pre>
* var myCustomRenderer = goog.ui.ContainerRenderer.getCustomRenderer(
* goog.ui.MenuRenderer, 'my-special-menu');
* var newMenu = new goog.ui.Menu(opt_domHelper, myCustomRenderer);
* </pre>
*
* Your styles for the menu can now be:
* <pre>
* .my-special-menu { }
* </pre>
*
* <em>instead</em> of
* <pre>
* .CSS_MY_SPECIAL_MENU .goog-menu { }
* </pre>
*
* You would want to use this functionality when you want an instance of a
* component to have specific styles different than the other components of the
* same type in your application. This avoids using descendant selectors to
* apply the specific styles to this component.
*
* @param {Function} ctor The constructor of the renderer you want to create.
* @param {string} cssClassName The name of the CSS class for this renderer.
* @return {goog.ui.ContainerRenderer} An instance of the desired renderer with
* its getCssClass() method overridden to return the supplied custom CSS
* class name.
*/
goog.ui.ContainerRenderer.getCustomRenderer = function(ctor, cssClassName) {
var renderer = new ctor();
/**
* Returns the CSS class to be applied to the root element of components
* rendered using this renderer.
* @return {string} Renderer-specific CSS class.
*/
renderer.getCssClass = function() { return cssClassName; };
return renderer;
};
/**
* Default CSS class to be applied to the root element of containers rendered
* by this renderer.
* @type {string}
*/
goog.ui.ContainerRenderer.CSS_CLASS = goog.getCssName('goog-container');
/**
* Returns the ARIA role to be applied to the container.
* See http://wiki/Main/ARIA for more info.
* @return {undefined|string} ARIA role.
*/
goog.ui.ContainerRenderer.prototype.getAriaRole = function() {
return this.ariaRole_;
};
/**
* Enables or disables the tab index of the element. Only elements with a
* valid tab index can receive focus.
* @param {Element} element Element whose tab index is to be changed.
* @param {boolean} enable Whether to add or remove the element's tab index.
*/
goog.ui.ContainerRenderer.prototype.enableTabIndex = function(element, enable) {
if (element) {
element.tabIndex = enable ? 0 : -1;
}
};
/**
* Creates and returns the container's root element. The default
* simply creates a DIV and applies the renderer's own CSS class name to it.
* To be overridden in subclasses.
* @param {goog.ui.Container} container Container to render.
* @return {Element} Root element for the container.
*/
goog.ui.ContainerRenderer.prototype.createDom = function(container) {
return container.getDomHelper().createDom(
goog.dom.TagName.DIV, this.getClassNames(container).join(' '));
};
/**
* Returns the DOM element into which child components are to be rendered,
* or null if the container hasn't been rendered yet.
* @param {Element} element Root element of the container whose content element
* is to be returned.
* @return {Element} Element to contain child elements (null if none).
*/
goog.ui.ContainerRenderer.prototype.getContentElement = function(element) {
return element;
};
/**
* Default implementation of {@code canDecorate}; returns true if the element
* is a DIV, false otherwise.
* @param {Element} element Element to decorate.
* @return {boolean} Whether the renderer can decorate the element.
*/
goog.ui.ContainerRenderer.prototype.canDecorate = function(element) {
return element.tagName == 'DIV';
};
/**
* Default implementation of {@code decorate} for {@link goog.ui.Container}s.
* Decorates the element with the container, and attempts to decorate its child
* elements. Returns the decorated element.
* @param {goog.ui.Container} container Container to decorate the element.
* @param {Element} element Element to decorate.
* @return {!Element} Decorated element.
*/
goog.ui.ContainerRenderer.prototype.decorate = function(container, element) {
// Set the container's ID to the decorated element's DOM ID, if any.
if (element.id) {
container.setId(element.id);
}
// Configure the container's state based on the CSS class names it has.
var baseClass = this.getCssClass();
var hasBaseClass = false;
var classNames = goog.dom.classlist.get(element);
if (classNames) {
goog.array.forEach(classNames, function(className) {
if (className == baseClass) {
hasBaseClass = true;
} else if (className) {
this.setStateFromClassName(container, className, baseClass);
}
}, this);
}
if (!hasBaseClass) {
// Make sure the container's root element has the renderer's own CSS class.
goog.dom.classlist.add(element, baseClass);
}
// Decorate the element's children, if applicable. This should happen after
// the container's own state has been initialized, since how children are
// decorated may depend on the state of the container.
this.decorateChildren(container, this.getContentElement(element));
return element;
};
/**
* Sets the container's state based on the given CSS class name, encountered
* during decoration. CSS class names that don't represent container states
* are ignored. Considered protected; subclasses should override this method
* to support more states and CSS class names.
* @param {goog.ui.Container} container Container to update.
* @param {string} className CSS class name.
* @param {string} baseClass Base class name used as the root of state-specific
* class names (typically the renderer's own class name).
* @protected
* @suppress {missingRequire} goog.ui.Container
*/
goog.ui.ContainerRenderer.prototype.setStateFromClassName = function(
container, className, baseClass) {
if (className == goog.getCssName(baseClass, 'disabled')) {
container.setEnabled(false);
} else if (className == goog.getCssName(baseClass, 'horizontal')) {
container.setOrientation(goog.ui.Container.Orientation.HORIZONTAL);
} else if (className == goog.getCssName(baseClass, 'vertical')) {
container.setOrientation(goog.ui.Container.Orientation.VERTICAL);
}
};
/**
* Takes a container and an element that may contain child elements, decorates
* the child elements, and adds the corresponding components to the container
* as child components. Any non-element child nodes (e.g. empty text nodes
* introduced by line breaks in the HTML source) are removed from the element.
* @param {goog.ui.Container} container Container whose children are to be
* discovered.
* @param {Element} element Element whose children are to be decorated.
* @param {Element=} opt_firstChild the first child to be decorated.
*/
goog.ui.ContainerRenderer.prototype.decorateChildren = function(
container, element, opt_firstChild) {
if (element) {
var node = opt_firstChild || element.firstChild, next;
// Tag soup HTML may result in a DOM where siblings have different parents.
while (node && node.parentNode == element) {
// Get the next sibling here, since the node may be replaced or removed.
next = node.nextSibling;
if (node.nodeType == goog.dom.NodeType.ELEMENT) {
// Decorate element node.
var child = this.getDecoratorForChild(/** @type {!Element} */ (node));
if (child) {
// addChild() may need to look at the element.
child.setElementInternal(/** @type {!Element} */ (node));
// If the container is disabled, mark the child disabled too. See
// bug 1263729. Note that this must precede the call to addChild().
if (!container.isEnabled()) {
child.setEnabled(false);
}
container.addChild(child);
child.decorate(/** @type {!Element} */ (node));
}
} else if (!node.nodeValue || goog.string.trim(node.nodeValue) == '') {
// Remove empty text node, otherwise madness ensues (e.g. controls that
// use goog-inline-block will flicker and shift on hover on Gecko).
element.removeChild(node);
}
node = next;
}
}
};
/**
* Inspects the element, and creates an instance of {@link goog.ui.Control} or
* an appropriate subclass best suited to decorate it. Returns the control (or
* null if no suitable class was found). This default implementation uses the
* element's CSS class to find the appropriate control class to instantiate.
* May be overridden in subclasses.
* @param {Element} element Element to decorate.
* @return {goog.ui.Control?} A new control suitable to decorate the element
* (null if none).
*/
goog.ui.ContainerRenderer.prototype.getDecoratorForChild = function(element) {
return /** @type {goog.ui.Control} */ (
goog.ui.registry.getDecorator(element));
};
/**
* Initializes the container's DOM when the container enters the document.
* Called from {@link goog.ui.Container#enterDocument}.
* @param {goog.ui.Container} container Container whose DOM is to be initialized
* as it enters the document.
*/
goog.ui.ContainerRenderer.prototype.initializeDom = function(container) {
var elem = container.getElement();
goog.asserts.assert(elem, 'The container DOM element cannot be null.');
// Make sure the container's element isn't selectable. On Gecko, recursively
// marking each child element unselectable is expensive and unnecessary, so
// only mark the root element unselectable.
goog.style.setUnselectable(elem, true, goog.userAgent.GECKO);
// IE doesn't support outline:none, so we have to use the hideFocus property.
if (goog.userAgent.IE) {
elem.hideFocus = true;
}
// Set the ARIA role.
var ariaRole = this.getAriaRole();
if (ariaRole) {
goog.a11y.aria.setRole(elem, ariaRole);
}
};
/**
* Returns the element within the container's DOM that should receive keyboard
* focus (null if none). The default implementation returns the container's
* root element.
* @param {goog.ui.Container} container Container whose key event target is
* to be returned.
* @return {Element} Key event target (null if none).
*/
goog.ui.ContainerRenderer.prototype.getKeyEventTarget = function(container) {
return container.getElement();
};
/**
* Returns the CSS class to be applied to the root element of containers
* rendered using this renderer.
* @return {string} Renderer-specific CSS class.
*/
goog.ui.ContainerRenderer.prototype.getCssClass = function() {
return goog.ui.ContainerRenderer.CSS_CLASS;
};
/**
* Returns all CSS class names applicable to the given container, based on its
* state. The array of class names returned includes the renderer's own CSS
* class, followed by a CSS class indicating the container's orientation,
* followed by any state-specific CSS classes.
* @param {goog.ui.Container} container Container whose CSS classes are to be
* returned.
* @return {!Array<string>} Array of CSS class names applicable to the
* container.
*/
goog.ui.ContainerRenderer.prototype.getClassNames = function(container) {
var baseClass = this.getCssClass();
var isHorizontal =
container.getOrientation() == goog.ui.Container.Orientation.HORIZONTAL;
var classNames = [
baseClass, (isHorizontal ? goog.getCssName(baseClass, 'horizontal') :
goog.getCssName(baseClass, 'vertical'))
];
if (!container.isEnabled()) {
classNames.push(goog.getCssName(baseClass, 'disabled'));
}
return classNames;
};
/**
* Returns the default orientation of containers rendered or decorated by this
* renderer. The base class implementation returns {@code VERTICAL}.
* @return {goog.ui.Container.Orientation} Default orientation for containers
* created or decorated by this renderer.
* @suppress {missingRequire} goog.ui.Container
*/
goog.ui.ContainerRenderer.prototype.getDefaultOrientation = function() {
return goog.ui.Container.Orientation.VERTICAL;
};
|