1AUI.add( 2 'liferay-menu', 3 function(A) { 4 var Lang = A.Lang; 5 6 var trim = Lang.trim; 7 8 var ATTR_CLASS_NAME = 'className'; 9 10 var AUTO = 'auto'; 11 12 var ARIA_ATTR_ROLE = 'role'; 13 14 var CSS_STATE_ACTIVE = 'aui-state-active'; 15 16 var CSS_EXTENDED = 'lfr-extended'; 17 18 var DEFAULT_ALIGN_POINTS = ['tl', 'bl']; 19 20 var EVENT_CLICK = 'click'; 21 22 var STR_B = 'b'; 23 24 var STR_L = 'l'; 25 26 var STR_R = 'r'; 27 28 var STR_T = 't'; 29 30 var MAP_ALIGN_HORIZONTAL_OVERLAY = { 31 right: STR_L, 32 left: STR_R 33 }; 34 35 var MAP_ALIGN_HORIZONTAL_TRIGGER = { 36 right: STR_R, 37 left: STR_L 38 }; 39 40 var MAP_ALIGN_VERTICAL_OVERLAY = { 41 down: STR_T, 42 up: STR_B 43 }; 44 45 var MAP_ALIGN_VERTICAL_TRIGGER = { 46 down: STR_B, 47 up: STR_T 48 }; 49 50 var MAP_LIVE_SEARCH = {}; 51 52 var REGEX_DIRECTION = /\bdirection-(down|left|right|up)\b/; 53 54 var REGEX_MAX_DISPLAY_ITEMS = /max-display-items-(\d+)/; 55 56 var SELECTOR_ANCHOR = 'a'; 57 58 var SELECTOR_TEXT = 'a, span'; 59 60 var SELECTOR_LIST_ITEM = 'li'; 61 62 var SELECTOR_SEARCH_CONTAINER = '.lfr-menu-list-search-container'; 63 64 var SELECTOR_SEARCH_INPUT = '.lfr-menu-list-search'; 65 66 var STR_BLANK = ''; 67 68 var TPL_MENU = '<div class="lfr-component lfr-menu-list" />'; 69 70 var TPL_SEARCH_BOX = '<div class="lfr-menu-list-search-container">' + 71 '<input autocomplete="off" aria-autocomplete="list" aria-expanded="true" aria-labelledby="{searchLabeledBy}" aria-owns="{searchOwns}" class="lfr-menu-list-search" id="{searchId}" role="combobox">' + 72 '</div>'; 73 74 var Menu = function() { 75 var instance = this; 76 77 instance._handles = []; 78 79 if (!Menu._INSTANCE) { 80 Menu._INSTANCE = instance; 81 } 82 }; 83 84 Menu.prototype = { 85 _closeActiveMenu: function() { 86 var instance = this; 87 88 var menu = instance._activeMenu; 89 90 if (menu) { 91 var handles = instance._handles; 92 93 A.Array.invoke(handles, 'detach'); 94 95 handles.length = 0; 96 97 instance._overlay.hide(); 98 99 var trigger = instance._activeTrigger; 100 101 instance._activeMenu = null; 102 instance._activeTrigger = null; 103 104 if (trigger.hasClass(CSS_EXTENDED)) { 105 trigger.removeClass(CSS_STATE_ACTIVE); 106 } 107 } 108 }, 109 110 _getAlignPoints: A.cached( 111 function(cssClass) { 112 var instance = this; 113 114 var alignPoints = DEFAULT_ALIGN_POINTS; 115 116 if (cssClass.indexOf(AUTO) == -1) { 117 var directionMatch = cssClass.match(REGEX_DIRECTION); 118 119 var direction = (directionMatch && directionMatch[1]) || AUTO; 120 121 var overlayHorizontal = MAP_ALIGN_HORIZONTAL_OVERLAY[direction] || STR_L; 122 var overlayVertical = MAP_ALIGN_VERTICAL_OVERLAY[direction] || STR_T; 123 124 var triggerHorizontal = MAP_ALIGN_HORIZONTAL_TRIGGER[direction] || STR_L; 125 var triggerVertical = MAP_ALIGN_VERTICAL_TRIGGER[direction] || STR_T; 126 127 alignPoints = [overlayVertical + overlayHorizontal, triggerVertical + triggerHorizontal]; 128 } 129 130 return alignPoints; 131 } 132 ), 133 134 _getMenu: function(trigger) { 135 var instance = this; 136 137 var overlay = instance._overlay; 138 139 if (!overlay) { 140 overlay = new A.OverlayBase( 141 { 142 align: { 143 node: trigger, 144 points: DEFAULT_ALIGN_POINTS 145 }, 146 constrain: true, 147 cssClass: 'lfr-menu-list', 148 hideClass: false, 149 preventOverlap: true, 150 zIndex: Liferay.zIndex.MENU 151 } 152 ).render(); 153 154 var boundingBox = overlay.get('boundingBox'); 155 156 boundingBox.addClass('lfr-component'); 157 158 instance._overlay = overlay; 159 } 160 else { 161 overlay.set('align.node', trigger); 162 } 163 164 var listContainer = trigger.getData('menuListContainer'); 165 var menu = trigger.getData('menu'); 166 var menuHeight = trigger.getData('menuHeight'); 167 168 var liveSearch = menu && MAP_LIVE_SEARCH[menu.guid()]; 169 170 if (liveSearch) { 171 liveSearch.search(STR_BLANK); 172 } 173 174 var listItems; 175 176 if (!menu || !listContainer) { 177 listContainer = trigger.one('ul'); 178 179 listItems = listContainer.all(SELECTOR_LIST_ITEM); 180 181 menu = A.Node.create(TPL_MENU); 182 183 listContainer.placeBefore(menu); 184
185 listItems.last().addClass('last'); 186 187 menu.append(listContainer); 188 189 trigger.setData('menuListContainer', listContainer); 190 trigger.setData('menu', menu); 191 192 instance._setARIARoles(trigger, menu, listContainer); 193 194 Liferay.Util.createFlyouts( 195 { 196 container: listContainer.getDOM() 197 } 198 ); 199 } 200 201 overlay.setStdModContent(A.WidgetStdMod.BODY, menu); 202 203 if (!menuHeight) { 204 menuHeight = instance._getMenuHeight(trigger, menu, listItems || listContainer.all(SELECTOR_LIST_ITEM)); 205 206 trigger.setData('menuHeight', menuHeight); 207 208 listContainer.addClass('lfr-menu-list-overflow'); 209 210 if (menuHeight != AUTO) { 211 listContainer.setStyle('maxHeight', menuHeight); 212 } 213 } 214 215 instance._getFocusManager(); 216 217 return menu; 218 }, 219 220 _getMenuHeight: function(trigger, menu, listItems) { 221 var instance = this; 222 223 var cssClass = trigger.attr(ATTR_CLASS_NAME); 224 225 var height = AUTO; 226 227 if (cssClass.indexOf('lfr-menu-expanded') == -1) { 228 var params = REGEX_MAX_DISPLAY_ITEMS.exec(cssClass); 229 230 var maxDisplayItems = params && parseInt(params[1], 10); 231 232 if (maxDisplayItems && listItems.size() > maxDisplayItems) { 233 instance._getLiveSearch(trigger, trigger.getData('menu')); 234 235 height = 0; 236 237 var heights = listItems.slice(0, maxDisplayItems).get('offsetHeight'); 238 239 for (var i = heights.length - 1; i >= 0; i--) { 240 height += heights[i]; 241 } 242 } 243 } 244 245 return height; 246 }, 247 248 _positionActiveMenu: function() { 249 var instance = this; 250 251 var menu = instance._activeMenu; 252 var trigger = instance._activeTrigger; 253 254 if (menu) { 255 var cssClass = trigger.attr(ATTR_CLASS_NAME); 256 257 var overlay = instance._overlay; 258 259 var align = overlay.get('align'); 260 261 align.points = instance._getAlignPoints(cssClass); 262 263 overlay.show(); 264 265 overlay.set('align', align); 266 267 if (Liferay.Browser.isIe() && Liferay.Browser.getMajorVersion() <= 7) { 268 var searchContainer = menu.one(SELECTOR_SEARCH_CONTAINER); 269 270 if (searchContainer) { 271 searchContainer.width(menu.innerWidth()); 272 273 menu.one(SELECTOR_SEARCH_INPUT).width('100%'); 274 } 275 } 276 277 if (cssClass.indexOf(CSS_EXTENDED) > -1) { 278 trigger.addClass(CSS_STATE_ACTIVE); 279 } 280 281 var focusManager = overlay.bodyNode.focusManager; 282 283 if (focusManager) { 284 focusManager.focus(0); 285 } 286 } 287 }, 288 289 _setARIARoles: function(trigger, menu, listContainer) { 290 var links = menu.all(SELECTOR_ANCHOR); 291 292 var searchContainer = menu.one(SELECTOR_SEARCH_CONTAINER); 293 294 var listNode = menu.one('ul'); 295 296 var ariaListNodeAttr = 'menu'; 297 var ariaLinksAttr = 'menuitem'; 298 299 if (searchContainer) { 300 ariaListNodeAttr = 'listbox'; 301 ariaListNodeAttr = 'option'; 302 } 303 304 listNode.setAttribute(ARIA_ATTR_ROLE, ariaListNodeAttr); 305 links.set(ARIA_ATTR_ROLE, ariaLinksAttr); 306 307 var anchor = trigger.one(SELECTOR_ANCHOR); 308 309 if (anchor) { 310 anchor.attr( 311 { 312 'aria-haspopup': true, 313 role: 'button' 314 } 315 ); 316 317 listNode.setAttribute('aria-labelledby', anchor.guid()); 318 } 319 } 320 }; 321 322 Menu.handleFocus = function(id) { 323 var node = A.one(id); 324 325 if (node) { 326 node.delegate('mouseenter', A.rbind(Menu._targetLink, node, 'focus'), SELECTOR_LIST_ITEM); 327 node.delegate('mouseleave', A.rbind(Menu._targetLink, node, 'blur'), SELECTOR_LIST_ITEM); 328 } 329 }; 330 331 var buffer = []; 332 333 Menu.register = function(id) { 334 var menuNode = document.getElementById(id); 335 336 if (!Menu._INSTANCE) { 337 new Menu(); 338 } 339 340 buffer.push(menuNode); 341 342 Menu._registerTask(); 343 }; 344 345 Menu._registerTask = A.debounce( 346 function() { 347 var instance = Menu._INSTANCE; 348 349 if (buffer.length) { 350 var nodes = A.all(buffer); 351 352 nodes.on(EVENT_CLICK, A.bind('_registerMenu', Menu)); 353 } 354 }, 355 100 356 ); 357 358 Menu._targetLink = function(event, action) { 359 var anchor = event.currentTarget.one(SELECTOR_ANCHOR); 360 361 if (anchor) { 362 anchor[action](); 363 } 364 }; 365 366 Liferay.provide( 367 Menu, 368 '_getFocusManager', 369 function() { 370 var instance = Menu._INSTANCE; 371 372 var focusManager = instance._focusManager; 373
374 if (!focusManager) { 375 var bodyNode = instance._overlay.bodyNode; 376 377 bodyNode.plug( 378 A.Plugin.NodeFocusManager, 379 { 380 circular: true, 381 descendants: 'li:not(.aui-helper-hidden) a,input', 382 focusClass: 'aui-focus', 383 keys: { 384 next: 'down:40', 385 previous: 'down:38' 386 } 387 } 388 ); 389 390 bodyNode.on( 391 'key', 392 function(event) { 393 var activeTrigger = instance._activeTrigger; 394 395 if (activeTrigger) { 396 var anchor = activeTrigger.one(SELECTOR_ANCHOR); 397 398 instance._closeActiveMenu(); 399 400 anchor.focus(); 401 } 402 }, 403 'down:27,9' 404 ); 405 406 focusManager = bodyNode.focusManager; 407 408 bodyNode.delegate( 409 'mouseenter', 410 function (event) { 411 if (focusManager.get('focused')) { 412 focusManager.focus(event.currentTarget.one(SELECTOR_ANCHOR)); 413 } 414 }, 415 SELECTOR_LIST_ITEM 416 ); 417 418 focusManager.after( 419 'activeDescendantChange', 420 function(event) { 421 var descendants = focusManager.get('descendants'); 422 423 var selectedItem = descendants.item(event.newVal); 424 425 bodyNode.one('ul').setAttribute('aria-activedescendant', selectedItem.guid()); 426 } 427 ); 428 429 instance._focusManager = focusManager; 430 } 431 432 focusManager.refresh(); 433 }, 434 ['node-focusmanager'], 435 true 436 ); 437 438 Liferay.provide( 439 Menu, 440 '_getLiveSearch', 441 function(trigger, menu) { 442 var instance = Menu._INSTANCE; 443 444 var id = menu.guid(); 445 446 var liveSearch = MAP_LIVE_SEARCH[id]; 447 448 if (!liveSearch) { 449 var searchId = A.guid(); 450 451 var listNode = menu.one('ul'); 452 453 var searchLabelNode = trigger.one(SELECTOR_ANCHOR) || trigger; 454 455 var searchBoxContent = Lang.sub( 456 TPL_SEARCH_BOX, 457 { 458 searchId: searchId, 459 searchLabeledBy: searchLabelNode.guid(), 460 searchOwns: listNode.guid() 461 } 462 ); 463 464 var inputSearch = A.Node.create(searchBoxContent); 465 466 inputSearch.swallowEvent('click'); 467 468 menu.prepend(inputSearch); 469 470 var options = { 471 data: function(node) { 472 return trim(node.one(SELECTOR_TEXT).text()); 473 }, 474 input: '#' + searchId, 475 nodes: '#' + listNode.guid() + ' > li' 476 }; 477 478 liveSearch = new A.LiveSearch(options); 479 480 var bodyNode = instance._overlay.bodyNode; 481 482 liveSearch.after( 483 'search', 484 function(event) { 485 var focusManager = bodyNode.focusManager; 486 487 if (focusManager) { 488 focusManager.refresh(); 489 } 490 } 491 ); 492 493 MAP_LIVE_SEARCH[id] = liveSearch; 494 } 495 }, 496 ['aui-live-search'], 497 true 498 ); 499 500 Liferay.provide( 501 Menu, 502 '_registerMenu', 503 function(event) { 504 var instance = Menu._INSTANCE; 505 506 var handles = instance._handles; 507 508 var trigger = event.currentTarget; 509 510 var activeTrigger = instance._activeTrigger; 511 512 if (activeTrigger && (activeTrigger != trigger)) { 513 activeTrigger.removeClass(CSS_STATE_ACTIVE); 514 } 515 516 if (!trigger.hasClass('disabled')) { 517 var menu = instance._getMenu(trigger); 518 519 instance._activeMenu = menu; 520 instance._activeTrigger = trigger; 521 522 if (!handles.length) { 523 handles.push( 524 A.getWin().on('resize', A.debounce(instance._positionActiveMenu, 200, instance)), 525 A.getDoc().on(EVENT_CLICK, instance._closeActiveMenu, instance) 526 ); 527 528 var DDM = A.DD && A.DD.DDM; 529 530 if (DDM) { 531 handles.push(DDM.on('ddm:start', instance._closeActiveMenu, instance)); 532 } 533 } 534 535 instance._positionActiveMenu(); 536 537 event.halt(); 538 } 539 }, 540 ['aui-overlay'] 541 ); 542 543 Liferay.Menu = Menu; 544 }, 545 '', 546 { 547 requires: ['aui-debounce', 'aui-node'] 548 } 549);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.