submenurenderer.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Renderer for {@link goog.ui.SubMenu}s.
  16. *
  17. */
  18. goog.provide('goog.ui.SubMenuRenderer');
  19. goog.require('goog.a11y.aria');
  20. goog.require('goog.a11y.aria.State');
  21. goog.require('goog.asserts');
  22. goog.require('goog.dom');
  23. goog.require('goog.dom.TagName');
  24. goog.require('goog.dom.classlist');
  25. goog.require('goog.style');
  26. goog.require('goog.ui.Menu');
  27. goog.require('goog.ui.MenuItemRenderer');
  28. /**
  29. * Default renderer for {@link goog.ui.SubMenu}s. Each item has the following
  30. * structure:
  31. *
  32. * <div class="goog-submenu">
  33. * ...(menuitem content)...
  34. * <div class="goog-menu">
  35. * ... (submenu content) ...
  36. * </div>
  37. * </div>
  38. *
  39. * @constructor
  40. * @extends {goog.ui.MenuItemRenderer}
  41. * @final
  42. */
  43. goog.ui.SubMenuRenderer = function() {
  44. goog.ui.MenuItemRenderer.call(this);
  45. };
  46. goog.inherits(goog.ui.SubMenuRenderer, goog.ui.MenuItemRenderer);
  47. goog.addSingletonGetter(goog.ui.SubMenuRenderer);
  48. /**
  49. * Default CSS class to be applied to the root element of components rendered
  50. * by this renderer.
  51. * @type {string}
  52. */
  53. goog.ui.SubMenuRenderer.CSS_CLASS = goog.getCssName('goog-submenu');
  54. /**
  55. * The CSS class for submenus that displays the submenu arrow.
  56. * @type {string}
  57. * @private
  58. */
  59. goog.ui.SubMenuRenderer.CSS_CLASS_SUBMENU_ =
  60. goog.getCssName('goog-submenu-arrow');
  61. /**
  62. * Overrides {@link goog.ui.MenuItemRenderer#createDom} by adding
  63. * the additional class 'goog-submenu' to the created element,
  64. * and passes the element to {@link goog.ui.SubMenuItemRenderer#addArrow_}
  65. * to add an child element that can be styled to show an arrow.
  66. * @param {goog.ui.Control} control goog.ui.SubMenu to render.
  67. * @return {!Element} Root element for the item.
  68. * @override
  69. */
  70. goog.ui.SubMenuRenderer.prototype.createDom = function(control) {
  71. var subMenu = /** @type {goog.ui.SubMenu} */ (control);
  72. var element =
  73. goog.ui.SubMenuRenderer.superClass_.createDom.call(this, subMenu);
  74. goog.asserts.assert(element);
  75. goog.dom.classlist.add(element, goog.ui.SubMenuRenderer.CSS_CLASS);
  76. this.addArrow_(subMenu, element);
  77. return element;
  78. };
  79. /**
  80. * Overrides {@link goog.ui.MenuItemRenderer#decorate} by adding
  81. * the additional class 'goog-submenu' to the decorated element,
  82. * and passing the element to {@link goog.ui.SubMenuItemRenderer#addArrow_}
  83. * to add a child element that can be styled to show an arrow.
  84. * Also searches the element for a child with the class goog-menu. If a
  85. * matching child element is found, creates a goog.ui.Menu, uses it to
  86. * decorate the child element, and passes that menu to subMenu.setMenu.
  87. * @param {goog.ui.Control} control goog.ui.SubMenu to render.
  88. * @param {Element} element Element to decorate.
  89. * @return {!Element} Root element for the item.
  90. * @override
  91. */
  92. goog.ui.SubMenuRenderer.prototype.decorate = function(control, element) {
  93. var subMenu = /** @type {goog.ui.SubMenu} */ (control);
  94. element =
  95. goog.ui.SubMenuRenderer.superClass_.decorate.call(this, subMenu, element);
  96. goog.asserts.assert(element);
  97. goog.dom.classlist.add(element, goog.ui.SubMenuRenderer.CSS_CLASS);
  98. this.addArrow_(subMenu, element);
  99. // Search for a child menu and decorate it.
  100. var childMenuEls = goog.dom.getElementsByTagNameAndClass(
  101. goog.dom.TagName.DIV, goog.getCssName('goog-menu'), element);
  102. if (childMenuEls.length) {
  103. var childMenu = new goog.ui.Menu(subMenu.getDomHelper());
  104. var childMenuEl = childMenuEls[0];
  105. // Hide the menu element before attaching it to the document body; see
  106. // bug 1089244.
  107. goog.style.setElementShown(childMenuEl, false);
  108. subMenu.getDomHelper().getDocument().body.appendChild(childMenuEl);
  109. childMenu.decorate(childMenuEl);
  110. subMenu.setMenu(childMenu, true);
  111. }
  112. return element;
  113. };
  114. /**
  115. * Takes a menu item's root element, and sets its content to the given text
  116. * caption or DOM structure. Overrides the superclass immplementation by
  117. * making sure that the submenu arrow structure is preserved.
  118. * @param {Element} element The item's root element.
  119. * @param {goog.ui.ControlContent} content Text caption or DOM structure to be
  120. * set as the item's content.
  121. * @override
  122. */
  123. goog.ui.SubMenuRenderer.prototype.setContent = function(element, content) {
  124. // Save the submenu arrow element, if present.
  125. var contentElement = this.getContentElement(element);
  126. var arrowElement = contentElement && contentElement.lastChild;
  127. goog.ui.SubMenuRenderer.superClass_.setContent.call(this, element, content);
  128. // If the arrowElement was there, is no longer there, and really was an arrow,
  129. // reappend it.
  130. if (arrowElement && contentElement.lastChild != arrowElement &&
  131. goog.dom.classlist.contains(
  132. /** @type {!Element} */ (arrowElement),
  133. goog.ui.SubMenuRenderer.CSS_CLASS_SUBMENU_)) {
  134. contentElement.appendChild(arrowElement);
  135. }
  136. };
  137. /**
  138. * Overrides {@link goog.ui.MenuItemRenderer#initializeDom} to tweak
  139. * the DOM structure for the span.goog-submenu-arrow element
  140. * depending on the text direction (LTR or RTL). When the SubMenu is RTL
  141. * the arrow will be given the additional class of goog-submenu-arrow-rtl,
  142. * and the arrow will be moved up to be the first child in the SubMenu's
  143. * element. Otherwise the arrow will have the class goog-submenu-arrow-ltr,
  144. * and be kept as the last child of the SubMenu's element.
  145. * @param {goog.ui.Control} control goog.ui.SubMenu whose DOM is to be
  146. * initialized as it enters the document.
  147. * @override
  148. */
  149. goog.ui.SubMenuRenderer.prototype.initializeDom = function(control) {
  150. var subMenu = /** @type {goog.ui.SubMenu} */ (control);
  151. goog.ui.SubMenuRenderer.superClass_.initializeDom.call(this, subMenu);
  152. var element = subMenu.getContentElement();
  153. var arrow = subMenu.getDomHelper().getElementsByTagNameAndClass(
  154. goog.dom.TagName.SPAN, goog.ui.SubMenuRenderer.CSS_CLASS_SUBMENU_,
  155. element)[0];
  156. goog.ui.SubMenuRenderer.setArrowTextContent_(subMenu, arrow);
  157. if (arrow != element.lastChild) {
  158. element.appendChild(arrow);
  159. }
  160. var subMenuElement = subMenu.getElement();
  161. goog.asserts.assert(
  162. subMenuElement, 'The sub menu DOM element cannot be null.');
  163. goog.a11y.aria.setState(
  164. subMenuElement, goog.a11y.aria.State.HASPOPUP, 'true');
  165. };
  166. /**
  167. * Appends a child node with the class goog.getCssName('goog-submenu-arrow') or
  168. * 'goog-submenu-arrow-rtl' which can be styled to show an arrow.
  169. * @param {goog.ui.SubMenu} subMenu SubMenu to render.
  170. * @param {Element} element Element to decorate.
  171. * @private
  172. */
  173. goog.ui.SubMenuRenderer.prototype.addArrow_ = function(subMenu, element) {
  174. var arrow = subMenu.getDomHelper().createDom(goog.dom.TagName.SPAN);
  175. arrow.className = goog.ui.SubMenuRenderer.CSS_CLASS_SUBMENU_;
  176. goog.ui.SubMenuRenderer.setArrowTextContent_(subMenu, arrow);
  177. this.getContentElement(element).appendChild(arrow);
  178. };
  179. /**
  180. * The unicode char for a left arrow.
  181. * @type {string}
  182. * @private
  183. */
  184. goog.ui.SubMenuRenderer.LEFT_ARROW_ = '\u25C4';
  185. /**
  186. * The unicode char for a right arrow.
  187. * @type {string}
  188. * @private
  189. */
  190. goog.ui.SubMenuRenderer.RIGHT_ARROW_ = '\u25BA';
  191. /**
  192. * Set the text content of an arrow.
  193. * @param {goog.ui.SubMenu} subMenu The sub menu that owns the arrow.
  194. * @param {Element} arrow The arrow element.
  195. * @private
  196. */
  197. goog.ui.SubMenuRenderer.setArrowTextContent_ = function(subMenu, arrow) {
  198. // Fix arrow rtl
  199. var leftArrow = goog.ui.SubMenuRenderer.LEFT_ARROW_;
  200. var rightArrow = goog.ui.SubMenuRenderer.RIGHT_ARROW_;
  201. goog.asserts.assert(arrow);
  202. if (subMenu.isRightToLeft()) {
  203. goog.dom.classlist.add(arrow, goog.getCssName('goog-submenu-arrow-rtl'));
  204. // Unicode character - Black left-pointing pointer iff aligned to end.
  205. goog.dom.setTextContent(
  206. arrow, subMenu.isAlignedToEnd() ? leftArrow : rightArrow);
  207. } else {
  208. goog.dom.classlist.remove(arrow, goog.getCssName('goog-submenu-arrow-rtl'));
  209. // Unicode character - Black right-pointing pointer iff aligned to end.
  210. goog.dom.setTextContent(
  211. arrow, subMenu.isAlignedToEnd() ? rightArrow : leftArrow);
  212. }
  213. };