| 1 | efrain | 1 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 2 | //
 | 
        
           |  |  | 3 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 4 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 5 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 6 | // (at your option) any later version.
 | 
        
           |  |  | 7 | //
 | 
        
           |  |  | 8 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 11 | // GNU General Public License for more details.
 | 
        
           |  |  | 12 | //
 | 
        
           |  |  | 13 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 14 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 15 |   | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * Initializes and handles events in the user menu.
 | 
        
           |  |  | 18 |  *
 | 
        
           |  |  | 19 |  * @module     core/usermenu
 | 
        
           |  |  | 20 |  * @copyright  2021 Moodle
 | 
        
           |  |  | 21 |  * @author     Mihail Geshoski <mihail@moodle.com>
 | 
        
           |  |  | 22 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 23 |  */
 | 
        
           |  |  | 24 |   | 
        
           |  |  | 25 | import $ from 'jquery';
 | 
        
           |  |  | 26 | import {space, enter} from 'core/key_codes';
 | 
        
           |  |  | 27 |   | 
        
           |  |  | 28 | /**
 | 
        
           |  |  | 29 |  * User menu constants.
 | 
        
           |  |  | 30 |  */
 | 
        
           |  |  | 31 | const selectors = {
 | 
        
           |  |  | 32 |     userMenu: '.usermenu',
 | 
        
           |  |  | 33 |     userMenuCarousel: '.usermenu #usermenu-carousel',
 | 
        
           |  |  | 34 |     userMenuCarouselItem: '.usermenu #usermenu-carousel .carousel-item',
 | 
        
           |  |  | 35 |     userMenuCarouselItemActive: '.usermenu #usermenu-carousel .carousel-item.active',
 | 
        
           |  |  | 36 |     userMenuCarouselNavigationLink: '.usermenu #usermenu-carousel .carousel-navigation-link',
 | 
        
           |  |  | 37 | };
 | 
        
           |  |  | 38 |   | 
        
           |  |  | 39 | /**
 | 
        
           |  |  | 40 |  * Register event listeners.
 | 
        
           |  |  | 41 |  */
 | 
        
           |  |  | 42 | const registerEventListeners = () => {
 | 
        
           |  |  | 43 |     const userMenu = document.querySelector(selectors.userMenu);
 | 
        
           |  |  | 44 |   | 
        
           |  |  | 45 |     // Handle the 'shown.bs.dropdown' event (Fired when the dropdown menu is fully displayed).
 | 
        
           |  |  | 46 |     $(selectors.userMenu).on('shown.bs.dropdown', () => {
 | 
        
           |  |  | 47 |         const activeCarouselItem = document.querySelector(selectors.userMenuCarouselItemActive);
 | 
        
           |  |  | 48 |         // Set the focus on the active carousel item.
 | 
        
           |  |  | 49 |         activeCarouselItem.focus();
 | 
        
           |  |  | 50 |   | 
        
           |  |  | 51 |         userMenu.querySelectorAll(selectors.userMenuCarouselItem).forEach(element => {
 | 
        
           |  |  | 52 |             // Resize all non-active carousel items to match the height and width of the current active (main)
 | 
        
           |  |  | 53 |             // carousel item to avoid sizing inconsistencies. This has to be done once the dropdown menu is fully
 | 
        
           |  |  | 54 |             // displayed ('shown.bs.dropdown') as the offsetWidth and offsetHeight cannot be obtained when the
 | 
        
           |  |  | 55 |             // element is hidden.
 | 
        
           |  |  | 56 |             if (!element.classList.contains('active')) {
 | 
        
           |  |  | 57 |                 element.style.width = activeCarouselItem.offsetWidth + 'px';
 | 
        
           |  |  | 58 |                 element.style.height = activeCarouselItem.offsetHeight + 'px';
 | 
        
           |  |  | 59 |             }
 | 
        
           |  |  | 60 |         });
 | 
        
           |  |  | 61 |     });
 | 
        
           |  |  | 62 |   | 
        
           |  |  | 63 |     // Handle click events in the user menu.
 | 
        
           |  |  | 64 |     userMenu.addEventListener('click', (e) => {
 | 
        
           |  |  | 65 |   | 
        
           |  |  | 66 |         // Handle click event on the carousel navigation (control) links in the user menu.
 | 
        
           |  |  | 67 |         if (e.target.matches(selectors.userMenuCarouselNavigationLink)) {
 | 
        
           |  |  | 68 |             carouselManagement(e);
 | 
        
           |  |  | 69 |         }
 | 
        
           |  |  | 70 |     });
 | 
        
           |  |  | 71 |   | 
        
           |  |  | 72 |     userMenu.addEventListener('keydown', e => {
 | 
        
           |  |  | 73 |         // Handle keydown event on the carousel navigation (control) links in the user menu.
 | 
        
           |  |  | 74 |         if ((e.keyCode === space ||
 | 
        
           |  |  | 75 |             e.keyCode === enter) &&
 | 
        
           |  |  | 76 |             e.target.matches(selectors.userMenuCarouselNavigationLink)) {
 | 
        
           |  |  | 77 |             e.preventDefault();
 | 
        
           |  |  | 78 |             carouselManagement(e);
 | 
        
           |  |  | 79 |         }
 | 
        
           |  |  | 80 |     });
 | 
        
           |  |  | 81 |   | 
        
           |  |  | 82 |     /**
 | 
        
           |  |  | 83 |      * We do the same actions here even if the caller was a click or button press.
 | 
        
           |  |  | 84 |      *
 | 
        
           |  |  | 85 |      * @param {Event} e The triggering element and key presses etc.
 | 
        
           |  |  | 86 |      */
 | 
        
           |  |  | 87 |     const carouselManagement = e => {
 | 
        
           |  |  | 88 |         // By default the user menu dropdown element closes on a click event. This behaviour is not desirable
 | 
        
           |  |  | 89 |         // as we need to be able to navigate through the carousel items (submenus of the user menu) within the
 | 
        
           |  |  | 90 |         // user menu. Therefore, we need to prevent the propagation of this event and then manually call the
 | 
        
           |  |  | 91 |         // carousel transition.
 | 
        
           |  |  | 92 |         e.stopPropagation();
 | 
        
           |  |  | 93 |         // The id of the targeted carousel item.
 | 
        
           |  |  | 94 |         const targetedCarouselItemId = e.target.dataset.carouselTargetId;
 | 
        
           |  |  | 95 |         const targetedCarouselItem = userMenu.querySelector('#' + targetedCarouselItemId);
 | 
        
           |  |  | 96 |         // Get the position (index) of the targeted carousel item within the parent container element.
 | 
        
           |  |  | 97 |         const index = Array.from(targetedCarouselItem.parentNode.children).indexOf(targetedCarouselItem);
 | 
        
           |  |  | 98 |         // Navigate to the targeted carousel item.
 | 
        
           |  |  | 99 |         $(selectors.userMenuCarousel).carousel(index);
 | 
        
           |  |  | 100 |   | 
        
           |  |  | 101 |     };
 | 
        
           |  |  | 102 |   | 
        
           |  |  | 103 |     // Handle the 'hide.bs.dropdown' event (Fired when the dropdown menu is being closed).
 | 
        
           |  |  | 104 |     $(selectors.userMenu).on('hide.bs.dropdown', () => {
 | 
        
           |  |  | 105 |         // Reset the state once the user menu dropdown is closed and return back to the first (main) carousel item
 | 
        
           |  |  | 106 |         // if necessary.
 | 
        
           |  |  | 107 |         $(selectors.userMenuCarousel).carousel(0);
 | 
        
           |  |  | 108 |     });
 | 
        
           |  |  | 109 |   | 
        
           |  |  | 110 |     // Handle the 'slid.bs.carousel' event (Fired when the carousel has completed its slide transition).
 | 
        
           |  |  | 111 |     $(selectors.userMenuCarousel).on('slid.bs.carousel', () => {
 | 
        
           |  |  | 112 |         const activeCarouselItem = userMenu.querySelector(selectors.userMenuCarouselItemActive);
 | 
        
           |  |  | 113 |         // Set the focus on the newly activated carousel item.
 | 
        
           |  |  | 114 |         activeCarouselItem.focus();
 | 
        
           |  |  | 115 |     });
 | 
        
           |  |  | 116 | };
 | 
        
           |  |  | 117 |   | 
        
           |  |  | 118 | /**
 | 
        
           |  |  | 119 |  * Initialize the user menu.
 | 
        
           |  |  | 120 |  */
 | 
        
           |  |  | 121 | const init = () => {
 | 
        
           |  |  | 122 |     registerEventListeners();
 | 
        
           |  |  | 123 | };
 | 
        
           |  |  | 124 |   | 
        
           |  |  | 125 | export default {
 | 
        
           |  |  | 126 |     init: init,
 | 
        
           |  |  | 127 | };
 |