PageSourceSearch

https://www.sasol.com/modules/contrib/tb_megamenu/js/tb-megamenu-frontend.js?v=1.x

js sasol.com collected 2026-10-02 04:39:16 UTC 18,991 bytes, 580 lines download raw bytes

1/**
2 * @file
3 * Defines Javascript behaviors for MegaMenu frontend.
4 */
5
6(function ($, Drupal, drupalSettings, once) {
7  "use strict";
8
9  Drupal.TBMegaMenu = Drupal.TBMegaMenu || {};
10
11  Drupal.TBMegaMenu.oldWindowWidth = 0;
12  Drupal.TBMegaMenu.displayedMenuMobile = false;
13  Drupal.TBMegaMenu.supportedScreens = [980];
14  Drupal.TBMegaMenu.focusableElements = 'a:not([disabled]), button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), details:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])';
15  Drupal.TBMegaMenu.menuResponsive = function () {
16    var windowWidth = window.innerWidth ? window.innerWidth : $(window).width();
17    var navCollapse = $('.tb-megamenu').children('.nav-collapse');
18    if (windowWidth < Drupal.TBMegaMenu.supportedScreens[0]) {
19      navCollapse.addClass('collapse');
20      if (Drupal.TBMegaMenu.displayedMenuMobile) {
21        navCollapse.css({height: 'auto', overflow: 'visible'});
22      }
23      else {
24        navCollapse.css({height: 0, overflow: 'hidden'});
25      }
26    }
27    else {
28      // If width of window is greater than 980 (supported screen).
29      navCollapse.removeClass('collapse');
30      if (navCollapse.height() <= 0) {
31        navCollapse.css({height: 'auto', overflow: 'visible'});
32      }
33    }
34  };
35
36  Drupal.TBMegaMenu.focusNextPrevElement = function (direction) {
37    // Add all the elements we want to include in our selection
38    var $current = $(document.activeElement);
39
40    if ($current.length) {
41      var $focusable = $(Drupal.TBMegaMenu.focusableElements).filter(function () {
42        var $this = $(this);
43        return $this.closest('.tb-megamenu-subnav').length === 0 && $this.is(':visible');
44      })
45
46      var index = $focusable.index($current);
47      if (index > -1) {
48        if (direction === 'next') {
49          var nextElement = $focusable[index + 1] || $focusable[0];
50        }
51        else {
52          var nextElement = $focusable[index - 1] || $focusable[0];
53        }
54
55        nextElement.focus();
56      }
57    }
58  }
59
60
61  Drupal.behaviors.tbMegaMenuAction = {
62    attach: function (context, settings) {
63
64      $(once('tb-megamenu', '.tb-megamenu', context)).each(function () {
65
66        /* Keyboard Control Setup */
67        // Semi-Global Variables
68        var navParent = document.querySelector('.tb-megamenu'),
69            linkArray = new Array(),
70            curPos = new Array(-1,-1,-1);
71
72        // Each Top-Level Link
73        $(this).find('.level-1').children('a, span').not('.mobile-only').each(function (i, toplink) {
74          linkArray[i] = new Array();
75
76          // Add Link to Array
77          linkArray[i][-1] = toplink;
78
79          // Determine Coordinates
80          $(toplink).data({ coordinate: [i, -1] });
81
82          // Each Column
83          $(toplink).next().children().children().children('.mega-col-nav').each(function (j, column) {
84            // Only add to the linkArray if menu items exist.
85            // TODO - this does not allow for tabbing to links in blocks, only menu item links.
86            if ($(column).find(Drupal.TBMegaMenu.focusableElements).length > 0) {
87
88              linkArray[i][j] = new Array();
89
90              // Each Link
91              $(column).find(Drupal.TBMegaMenu.focusableElements).each(function (k, sublink) {
92
93                // Add Link to Array
94                linkArray[i][j][k] = sublink;
95
96                // Determine Coordinates
97                $(sublink).data({ coordinate: [i, j, k] });
98              }); // each link
99            }
100
101          }); // each column
102
103        }); // each top-level link
104
105        // Update Position on Focus
106        $(this).find(Drupal.TBMegaMenu.focusableElements).focus(function () {
107          curPos = $(this).data('coordinate');
108        });
109
110        // Key Pressed
111        function keydownEvent(k) {
112          // Determine Key
113          switch(k.keyCode) {
114
115            // TAB
116            case 9:
117              k.preventDefault();
118              nav_tab(k);
119              break;
120
121            // RETURN
122            case 13:
123              nav_open_link();
124              break;
125
126            // ESC
127            case 27:
128              nav_esc();
129              break;
130
131            // LEFT
132            case 37:
133              k.preventDefault();
134              nav_left();
135              break;
136
137            // UP
138            case 38:
139              k.preventDefault();
140              nav_up();
141              break;
142
143            // RIGHT
144            case 39:
145              k.preventDefault();
146              nav_right();
147              break;
148
149            // DOWN
150            case 40:
151              k.preventDefault();
152              nav_down();
153              break;
154
155            // HOME
156            case 36:
157              nav_home();
158              break;
159
160            // END
161            case 35:
162              nav_end();
163              break;
164
165            // Else
166            default:
167              // Do nothing
168
169          } // determine key
170        } // keydownEvent
171
172        /* Keypress Functions */
173        // Tab
174        function nav_tab(k) {
175          if (nav_is_toplink()) {
176            if (k.shiftKey) {
177              nav_prev_toplink();
178            }
179            else {
180              nav_next_toplink();
181            }
182          }
183          else {
184            if (k.shiftKey) {
185              nav_up();
186            }
187            else {
188              nav_down();
189            }
190          }
191        }
192
193        // Open Link
194        function nav_open_link() {
195          linkArray[curPos[0]][curPos[1]][curPos[2]].click();
196        }
197
198        // Escape
199        function nav_esc() {
200          nav_close_megamenu();
201        }
202
203        // Left
204        function nav_left() {
205          if (nav_is_toplink()) {
206            nav_prev_toplink();
207          }
208          else {
209            nav_prev_column();
210          }
211        }
212
213        // Right
214        function nav_right() {
215          if (nav_is_toplink()) {
216            nav_next_toplink();
217          }
218          else {
219            nav_next_column();
220          }
221        }
222
223        // Up
224        function nav_up() {
225          if (nav_is_toplink()) {
226            nav_prev_toplink();
227          }
228          else {
229            if (linkArray[curPos[0]][curPos[1]][curPos[2] - 1]) {
230              // If the previous link in the array is hidden (ie, it's in a
231              // submenu that is not currently expanded), then skip to the next
232              // item in the array until we find one that's visible.
233              if ($(linkArray[curPos[0]][curPos[1]][curPos[2] - 1]).is(':visible')) {
234                linkArray[curPos[0]][curPos[1]][curPos[2] - 1].focus();
235              }
236              else {
237                curPos = [curPos[0], curPos[1], curPos[2] - 1];
238                nav_up();
239              }
240            }
241            else {
242              nav_prev_column();
243            }
244          }
245        }
246
247        // Down
248        function nav_down() {
249          if (nav_is_toplink()) {
250            nav_next_column();
251          }
252          else {
253            if (linkArray[curPos[0]][curPos[1]][curPos[2] + 1]) {
254              linkArray[curPos[0]][curPos[1]][curPos[2] + 1].focus();
255            }
256            else {
257              nav_next_column();
258            }
259          }
260        }
261
262        // Home Button
263        function nav_home() {
264          if (nav_is_toplink()) {
265            linkArray[0][-1].focus();
266          }
267          else {
268            linkArray[curPos[0]][0][0].focus();
269          }
270        }
271
272        // End Button
273        function nav_end() {
274          if (nav_is_toplink()) {
275            linkArray.slice(-1)[0][-1].focus();
276          }
277          else {
278            linkArray[curPos[0]].slice(-1)[0].slice(-1)[0].focus();
279          }
280        }
281
282        /* Helper Functions */
283        // Determine Link Level
284        function nav_is_toplink() {
285          return (curPos[1] < 0);
286        }
287
288        // Close Mega Menu
289        function nav_close_megamenu() {
290          $('.tb-megamenu .open').removeClass('open');
291          ariaCheck();
292        }
293
294        // Next Toplink
295        function nav_next_toplink() {
296          if (linkArray[curPos[0] + 1]) {
297            linkArray[curPos[0] + 1][-1].focus();
298          }
299          else {
300            nav_close_megamenu();
301
302            // Focus on the next element.
303            Drupal.TBMegaMenu.focusNextPrevElement('next');
304          }
305        }
306
307        // Previous Toplink
308        function nav_prev_toplink() {
309          if (linkArray[curPos[0] - 1]) {
310            linkArray[curPos[0] - 1][-1].focus();
311          }
312          else {
313            // Focus on the previous element.
314            Drupal.TBMegaMenu.focusNextPrevElement('prev');
315          }
316        }
317
318        // Previous Column
319        function nav_prev_column() {
320          if (linkArray[curPos[0]][curPos[1] - 1][0]) {
321            linkArray[curPos[0]][curPos[1] - 1][0].focus();
322          }
323          else {
324            nav_parent_toplink();
325          }
326        }
327
328        // Next Column
329        function nav_next_column() {
330          if (linkArray[curPos[0]][curPos[1] + 1]) {
331            linkArray[curPos[0]][curPos[1] + 1][0].focus();
332          }
333          else {
334            nav_parent_toplink();
335          }
336        }
337
338        // Go to Parent Toplink
339        function nav_parent_toplink() {
340          linkArray[curPos[0]][-1].focus();
341        }
342
343        var ariaCheck = function () {
344          $("li.tb-megamenu-item", this).each(function () {
345            if ($(this).is('.mega-group')) {
346              // Mega menu item has mega class (it's a true mega menu)
347              if (!$(this).parents().is('.open')) {
348                // Mega menu item has mega class and its ancestor is closed, so apply appropriate ARIA attributes
349                $(this).children().attr('aria-expanded', 'false');
350              }
351              else if ($(this).parents().is('.open')) {
352                // Mega menu item has mega class and its ancestor is o
352pen, so apply appropriate ARIA attributes
353                $(this).children().attr('aria-expanded', 'true');
354              }
355            }
356            else if ($(this).is('.dropdown') || $(this).is('.dropdown-submenu')) {
357              // Mega menu item has dropdown (it's a flyout menu)
358              if (!$(this).is('.open')) {
359                // Mega menu item has dropdown class and is closed, so apply appropriate ARIA attributes
360                $(this).children().attr('aria-expanded', 'false');
361              }
362              else if ($(this).is('.open')) {
363                // Mega menu item has dropdown class and is open, so apply appropriate ARIA attributes
364                $(this).children().attr('aria-expanded', 'true');
365              }
366            }
367            else {
368              // Mega menu item is neither a mega or dropdown class, so remove ARIA attributes (it doesn't have children)
369              $(this).children().removeAttr('aria-expanded');
370            }
371          });
372        };
373
374        var showMenu = function ($subMenu, mm_timeout) {
375          if ($subMenu.hasClass('mega')) {
376            $subMenu.addClass('animating');
377            clearTimeout($subMenu.data('animatingTimeout'));
378            $subMenu.data('animatingTimeout', setTimeout(function () {
379              $subMenu.removeClass('animating')
380            }, mm_timeout));
381            clearTimeout($subMenu.data('hoverTimeout'));
382            $subMenu.data('hoverTimeout', setTimeout(function () {
383              $subMenu.addClass('open');
384              ariaCheck();
385            }, 100));
386          }
387          else {
388            clearTimeout($subMenu.data('hoverTimeout'));
389            $subMenu.data('hoverTimeout',
390                setTimeout(function () {
391                  $subMenu.addClass('open');
392                  ariaCheck();
393                }, 100));
394          }
395        };
396
397        var hideMenu = function ($subMenu, mm_timeout) {
398          $subMenu.children('.dropdown-toggle').attr('aria-expanded', 'false');
399          if ($subMenu.hasClass('mega')) {
400            $subMenu.addClass('animating');
401            clearTimeout($subMenu.data('animatingTimeout'));
402            $subMenu.data('animatingTimeout', setTimeout(function () {
403              $subMenu.removeClass('animating')
404            }, mm_timeout));
405            clearTimeout($subMenu.data('hoverTimeout'));
406            $subMenu.data('hoverTimeout', setTimeout(function () {
407              $subMenu.removeClass('open');
408              ariaCheck();
409            }, 100));
410          }
411          else {
412            clearTimeout($subMenu.data('hoverTimeout'));
413            $subMenu.data('hoverTimeout', setTimeout(function () {
414              $subMenu.removeClass('open');
415              ariaCheck();
416            }, 100));
417          }
418        };
419
420        $('.tb-megamenu-button', this).click(function () {
421          if (parseInt($(this).parent().children('.nav-collapse').height())) {
422            $(this).parent().children('.nav-collapse').css({height: 0, overflow: 'hidden'});
423            Drupal.TBMegaMenu.displayedMenuMobile = false;
424          }
425          else {
426            $(this).parent().children('.nav-collapse').css({height: 'auto', overflow: 'visible'});
427            Drupal.TBMegaMenu.displayedMenuMobile = true;
428          }
429        });
430
431        var isTouch = window.matchMedia('(pointer: coarse)').matches;
432
433        if (!isTouch) {
434          var mm_duration = 0;
435
436          $('.tb-megamenu', context).each(function () {
437            if ($(this).data('duration')) {
438              mm_duration = $(this).data('duration');
439            }
440          });
441
442          var mm_timeout = mm_duration ? 100 + mm_duration : 500;
443          $('.nav > li, li.mega', context).bind('mouseenter', function (event) {
444            showMenu($(this), mm_timeout);
445          });
446
447          $('.nav > li > .dropdown-toggle, li.mega > .dropdown-toggle', context).bind('focus', function (event) {
448            var $this = $(this);
449            var $subMenu = $this.closest('li');
450            showMenu($subMenu, mm_timeout);
451            // If the focus moves outside of the subMenu, close it.
452            $(document).bind('focusin', function (event) {
453              if ($subMenu.has(event.target).length) {
454                return;
455              }
456              $(document).unbind(event);
457              hideMenu($subMenu, mm_timeout);
458            });
459          });
460
461          $('.nav > li, li.mega', context).bind('mouseleave', function (event) {
462            hideMenu($(this), mm_timeout);
463          });
464
465          /**
466           * Allow tabbing by appending the open class.
467           * Works in tandem with CSS changes that utilize opacity rather than
468           * display none
469           */
470          // If the selected anchor is not in the TB Megamenu, remove all "open"
471          // class occurrences
472          $('a, span').focus(function (event) {
473            if (!$(this).parent().hasClass('tb-megamenu-item') && !$(this).parents('.tb-megamenu-block').length) {
474              nav_close_megamenu();
475            }
476          });
477
478          $('.nav > li > a, li.mega > a').focus(function (event) {
479            // Remove all occurrences of "open" from other menu trees
480            var siblings = $(this).parents('.tb-megamenu-item').siblings();
481            // var siblings = $(this).closest('.tb-megamenu-item.level-1').siblings();
482            $.each(siblings, function (i, v) {
483              var cousins = $(v).find('.open');
484              $.each(cousins, function (index, value) {
485                $(value).removeClass('open');
486                ariaCheck($(this));
487              });
488              $(v).removeClass('open');
489              ariaCheck();
490            });
491            // Open the submenu if the selected item has one
492            if ($(this).next(".tb-megamenu-submenu").length > 0) {
493              if (!$(this).parent().hasClass("open")) {
494                $(this).parent().addClass("open");
495              }
496            }
497            // If the anchor's top-level parent is not open, open it
498            if (!$(this).closest('.tb-megamenu-item.dropdown').hasClass('open') && $(this).closest('.tb-megamenu-item.dropdown').find('.tb-megamenu-submenu').length > 0) {
499              $(this).closest('.tb-megamenu-item.dropdown').addClass('open');
500              ariaCheck();
501            }
502            // If anchor's parent submenus are not open, open them
503            var parents = $(this).parents('.tb-megamenu-item.dropdown-submenu');
504            $.each(parents, function (i, v) {
505              if (!$(v).hasClass('open')) {
506                $(v).addClass('open');
507                ariaCheck();
508              }
509            });
510          });
511        }
512
513        // Define actions for touch devices.
514        var createTouchMenu = function (items) {
515          items.children("a, span").each(function () {
516            var $item = $(this);
517            var tbitem = $(this).parent();
518
519            $item.click(function (event) {
520              // If the menu link has already been clicked once...
521              if ($item.hasClass("tb-megamenu-clicked")) {
522                var $uri = $item.attr("href");
523
524                // If the menu link has a URI, go to the link.
525                // <nolink> menu items will not have a URI.
526                if ($uri) {
527                  window.location.href = $uri;
528                }
529                else {
530                  $item.removeClass("tb-megamenu-clicked");
531                  hideMenu(tbitem, mm_timeout);
532                }
533              }
534              else {
535                event.preventDefault();
536
537                // Hide any already open menus.
538                nav_close_megamenu();
539                $(".tb-megamenu").find(".tb-megamenu-clicked").removeClass("tb-megamenu-clicked");
540
541                // Open the submenu.
542                $item.addClass("tb-megamenu-clicked");
543                showMenu(tbitem, mm_timeout);
544              }
545            });
546          });
547
548          // Anytime there's a click outside the menu, close the menu.
549          $(document).on('click', function (event) {
550            if ($(event.target).closest('.tb-megamenu-nav').length === 0) {
551              nav_close_megamenu();
552              $(".tb-megamenu").find(".tb-megamenu-clicked").removeClass("tb-megamenu-clicked");
553            };
554          })
555        };
556
557        if (isTouch) {
558          createTouchMenu($(".tb-megamenu ul.nav li.mega", context).has(".dropdown-menu"));
559        };
560
561        $(window).on('load resize', function () {
562          var windowWidth = window.innerWidth ? window.innerWidth : $(window).width();
563          if (windowWidth != Drupal.TBMegaMenu.oldWindowWidth) {
564            Drupal.TBMegaMenu.oldWindowWidth = windowWidth;
565            Drupal.TBMegaMenu.menuResponsive();
566
567            if (windowWidth >= Drupal.TBMegaMenu.supportedScreens[0]) {
568              navParent.addEventListener('keydown',keydownEvent);
569            }
570            else {
571              navParent.removeEventListener('keydown',keydownEvent);
572            }
573          }
574        });
575
576      });
577
578    }
579  }
580})(jQuery, Drupal, drupalSettings, once);

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.