123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- /*
- * This content is licensed according to the W3C Software License at
- * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
- */
- var PopupMenu = function (domNode, controllerObj) {
- var elementChildren,
- msgPrefix = 'PopupMenu constructor argument domNode ';
- // Check whether domNode is a DOM element
- if (!domNode instanceof Element) {
- throw new TypeError(msgPrefix + 'is not a DOM Element.');
- }
- // Check whether domNode has child elements
- if (domNode.childElementCount === 0) {
- throw new Error(msgPrefix + 'has no element children.');
- }
- // Check whether domNode descendant elements have A elements
- var childElement = domNode.firstElementChild;
- while (childElement) {
- var menuitem = childElement.firstElementChild;
- if (menuitem && menuitem === 'A') {
- throw new Error(msgPrefix + 'has descendant elements that are not A elements.');
- }
- childElement = childElement.nextElementSibling;
- }
- this.isMenubar = false;
- this.domNode = domNode;
- this.controller = controllerObj;
- this.menuitems = []; // See PopupMenu init method
- this.firstChars = []; // See PopupMenu init method
- this.firstItem = null; // See PopupMenu init method
- this.lastItem = null; // See PopupMenu init method
- this.hasFocus = false; // See MenuItem handleFocus, handleBlur
- this.hasHover = false; // See PopupMenu handleMouseover, handleMouseout
- };
- /*
- * @method PopupMenu.prototype.init
- *
- * @desc
- * Add domNode event listeners for mouseover and mouseout. Traverse
- * domNode children to configure each menuitem and populate menuitems
- * array. Initialize firstItem and lastItem properties.
- */
- PopupMenu.prototype.init = function () {
- var childElement, menuElement, menuItem, textContent, numItems, label;
- // Configure the domNode itself
- this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this));
- this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this));
- // Traverse the element children of domNode: configure each with
- // menuitem role behavior and store reference in menuitems array.
- childElement = this.domNode.firstElementChild;
- while (childElement) {
- menuElement = childElement.firstElementChild;
- if (menuElement && menuElement.tagName === 'A') {
- menuItem = new MenuItem(menuElement, this);
- menuItem.init();
- this.menuitems.push(menuItem);
- textContent = menuElement.textContent.trim();
- this.firstChars.push(textContent.substring(0, 1).toLowerCase());
- }
- childElement = childElement.nextElementSibling;
- }
- // Use populated menuitems array to initialize firstItem and lastItem.
- numItems = this.menuitems.length;
- if (numItems > 0) {
- this.firstItem = this.menuitems[ 0 ];
- this.lastItem = this.menuitems[ numItems - 1 ];
- }
- };
- /* EVENT HANDLERS */
- PopupMenu.prototype.handleMouseover = function (event) {
- this.hasHover = true;
- };
- PopupMenu.prototype.handleMouseout = function (event) {
- this.hasHover = false;
- setTimeout(this.close.bind(this, false), 1);
- };
- /* FOCUS MANAGEMENT METHODS */
- PopupMenu.prototype.setFocusToController = function (command, flag) {
- if (typeof command !== 'string') {
- command = '';
- }
- function setFocusToMenubarItem (controller, close) {
- while (controller) {
- if (controller.isMenubarItem) {
- controller.domNode.focus();
- return controller;
- }
- else {
- if (close) {
- controller.menu.close(true);
- }
- controller.hasFocus = false;
- }
- controller = controller.menu.controller;
- }
- return false;
- }
- if (command === '') {
- if (this.controller && this.controller.domNode) {
- this.controller.domNode.focus();
- }
- return;
- }
- if (!this.controller.isMenubarItem) {
- this.controller.domNode.focus();
- this.close();
- if (command === 'next') {
- var menubarItem = setFocusToMenubarItem(this.controller, false);
- if (menubarItem) {
- menubarItem.menu.setFocusToNextItem(menubarItem, flag);
- }
- }
- }
- else {
- if (command === 'previous') {
- this.controller.menu.setFocusToPreviousItem(this.controller, flag);
- }
- else if (command === 'next') {
- this.controller.menu.setFocusToNextItem(this.controller, flag);
- }
- }
- };
- PopupMenu.prototype.setFocusToFirstItem = function () {
- this.firstItem.domNode.focus();
- };
- PopupMenu.prototype.setFocusToLastItem = function () {
- this.lastItem.domNode.focus();
- };
- PopupMenu.prototype.setFocusToPreviousItem = function (currentItem) {
- var index;
- if (currentItem === this.firstItem) {
- this.lastItem.domNode.focus();
- }
- else {
- index = this.menuitems.indexOf(currentItem);
- this.menuitems[ index - 1 ].domNode.focus();
- }
- };
- PopupMenu.prototype.setFocusToNextItem = function (currentItem) {
- var index;
- if (currentItem === this.lastItem) {
- this.firstItem.domNode.focus();
- }
- else {
- index = this.menuitems.indexOf(currentItem);
- this.menuitems[ index + 1 ].domNode.focus();
- }
- };
- PopupMenu.prototype.setFocusByFirstCharacter = function (currentItem, char) {
- var start, index, char = char.toLowerCase();
- // Get start index for search based on position of currentItem
- start = this.menuitems.indexOf(currentItem) + 1;
- if (start === this.menuitems.length) {
- start = 0;
- }
- // Check remaining slots in the menu
- index = this.getIndexFirstChars(start, char);
- // If not found in remaining slots, check from beginning
- if (index === -1) {
- index = this.getIndexFirstChars(0, char);
- }
- // If match was found...
- if (index > -1) {
- this.menuitems[ index ].domNode.focus();
- }
- };
- PopupMenu.prototype.getIndexFirstChars = function (startIndex, char) {
- for (var i = startIndex; i < this.firstChars.length; i++) {
- if (char === this.firstChars[ i ]) {
- return i;
- }
- }
- return -1;
- };
- /* MENU DISPLAY METHODS */
- PopupMenu.prototype.open = function () {
- // Get position and bounding rectangle of controller object's DOM node
- var rect = this.controller.domNode.getBoundingClientRect();
- // Set CSS properties
- if (!this.controller.isMenubarItem) {
- this.domNode.parentNode.style.position = 'relative';
- this.domNode.style.display = 'block';
- this.domNode.style.position = 'absolute';
- this.domNode.style.left = rect.width + 'px';
- this.domNode.style.zIndex = 100;
- }
- else {
- this.domNode.style.display = 'block';
- this.domNode.style.position = 'absolute';
- this.domNode.style.top = (rect.height - 1) + 'px';
- this.domNode.style.zIndex = 100;
- }
- this.controller.setExpanded(true);
- };
- PopupMenu.prototype.close = function (force) {
- var controllerHasHover = this.controller.hasHover;
- var hasFocus = this.hasFocus;
- for (var i = 0; i < this.menuitems.length; i++) {
- var mi = this.menuitems[i];
- if (mi.popupMenu) {
- hasFocus = hasFocus | mi.popupMenu.hasFocus;
- }
- }
- if (!this.controller.isMenubarItem) {
- controllerHasHover = false;
- }
- if (force || (!hasFocus && !this.hasHover && !controllerHasHover)) {
- this.domNode.style.display = 'none';
- this.domNode.style.zIndex = 0;
- this.controller.setExpanded(false);
- }
- };
|