PageSourceSearch

https://www.aea.asso.fr/js/mevia/modules/menu/scrollablenav.js?version=1790319560

js aea.asso.fr collected 2026-09-26 07:13:30 UTC 9,656 bytes, 286 lines download raw bytes

1/**
2 * Scrollablenav
3 * Library that scrolls the main menu if the space is not enough
4 *
5 */
6
7// Namespace. The menu partial can be rendered twice on a page, and `usescript`
8// emits one script tag per render: hold on to the namespace of the first
9// evaluation, whose closure owns the live DOM references.
10var scrollablenav = window.scrollablenav || {};
11
12(function (window, document, obj) {
13
14  // A second evaluation has nothing to redefine: the listeners already posted
15  // reach these methods through `obj`, so rebinding them to this closure —
16  // whose DOM references are still null — is what makes a `resize` dereference
17  // a missing scroll button.
18  if (obj.init) {
19    return;
20  }
21
22  // DOM elements
23  var $el = null;
24  var $menu = null;
25  var $menuElements = null;
26  var $menuFirstElement = null;
27  var $subContainer = null;
28  var $leftButton = null;
29  var $rightButton = null;
30  var menuStep = 0;
31  var menuTextColor = '';
32  // Scroll-button labels, passed in by the template: `__()` is not reachable here.
33  var navLabels = {};
34  var menuOffset = 0;
35
36  // Add the forEach to a NodeList, very handy.
37  if (window.NodeList && !NodeList.prototype.forEach) {
38    NodeList.prototype.forEach = function (callback, thisArg) {
39      thisArg = thisArg || window;
40      for (var i = 0; i < this.length; i++) {
41        callback.call(thisArg, this[i], i, this);
42      }
43    };
44  }
45
46  // this hides the all the subMenu frome $subContainer
47  obj._emptySubContainer = function () {
48    var subContainers = $subContainer.querySelectorAll('ul.level-1');
49    subContainers.forEach(function (container) {
50      container.style.display = 'none';
51    })
52  };
53
54  // this gets a parent <li> and its <ul> subMenu and appends it
55  // to the $subContainer
56  obj._showSubMenu = function ($cell, $subMenu, $innerSubMenu) {
57    var elRect = $cell.getBoundingClientRect();
58    var leftLimit = $leftButton.getBoundingClientRect().right || elRect.left;
59    var rightLimit = $rightButton.getBoundingClientRect().left || elRect.right;
60    var isFullyVisible = leftLimit - 50 < elRect.left && elRect.right < rightLimit + 50;
61    var navRect = $el.getBoundingClientRect();
62    var x = elRect.left - navRect.left;
63
64    var leftCache = x;
65    var isRightLimited = function ($el, offsetLeft) {
66      var $elementRect = $el.getBoundingClientRect();
67      return offsetLeft + $elementRect.width >= navRect.width;
68    };
69
70    var getRealWidth = function (element, offset) {
71      var hasChildren = element.querySelectorAll('ul');
72      element.style.display = "block";
73      element.style.left = -(element.getBoundingClientRect().width + offset) + "px";
74      if (hasChildren.length) {
75        element.classList.add('has-children');
76        for (var i = 0; i < hasChildren.length; i++) {
77          var children = hasChildren[i];
78          getRealWidth(children, offset)
79        }
80      }
81    };
82
83    $subMenu.style.top = 0;
84    $subMenu.style.display = "block";
85    $subMenu.style.left = x + "px";
86    leftCache = x;
87
88    if (isFullyVisible) {
89      $subContainer.appendChild($subMenu);
90    }
91
92    if (isRightLimited($subMenu, leftCache)) {
93      $subMenu.style.left = navRect.width - $subMenu.getBoundingClientRect().width + 16 + "px";
94
95      // if larger than the window put it on the left side of your parent
96    } else {
97      $subMenu.style.left = x + "px";
98      leftCache = x;
99    }
100    if (isRightLimited($subMenu, leftCache + 160) && $innerSubMenu) {
101      for (var j = 0; j < $innerSubMenu.length; j++) {
102        var element = $innerSubMenu[j];
103        var offset = -1;
104
105        getRealWidth(element, offset);
106      }
107      for (var k = 0; k < $innerSubMenu.length; k++) {
108        $innerSubMenu[k].style.display = 'none';
109      }
110    } else {
111      for (var i = 0; i < $innerSubMenu.length; i++) {
112        $innerSubMenu[i].style.left = '100%';
113      }
114    }
115  };
116
117  // add Listeners to DOM elements
118  obj._addListeners = function () {
119    $el.addEventListener("mouseleave", function () {
120      obj._emptySubContainer();
121    });
122
123    $leftButton.addEventListener("mouseenter", function () {
124      obj._emptySubContainer();
125    });
126
127    $rightButton.addEventListener("mouseenter", function () {
128      obj._emptySubContainer();
129    });
130
131    $leftButton.addEventListener('click', function () {
132      if (menuStep === 0) return;
133      var residualSpace = obj._residualSpace();
134      var distance = (menuStep === 2) ? residualSpace / 2 : 0;
135      $menuFirstElement.style.marginLeft = distance + 'px';
136      menuStep--;
137      obj._checkButtonVisibility();
138    });
139
140    $rightButton.addEventListener('click', function () {
141      if (menuStep > 1) return;
142      var residualSpace = obj._residualSpace();
143      var distance = (menuStep) ? residualSpace : residualSpace / 2;
144      $menuFirstElement.style.marginLeft = distance + 'px';
145      menuStep++;
146      obj._checkButtonVisibility();
147    });
148
149    $menuElements.forEach(function (cell) {
150      var subMenu = cell.querySelector('.dropdown-menu');
151      var $subMenu = null;
152      var $innerSubMenu = null;
153
154      if (subMenu !== null) {
155        $subMenu = cell.removeChild(subMenu);
156      }
157      cell.addEventListener("mouseenter", function () {
158        if ($subContainer.innerHTML !== "") {
159          obj._emptySubContainer();
160        }
161        if (subMenu !== null) {
162          $innerSubMenu = $subMenu.querySelectorAll('.dropdown-menu');
163
164          obj._showSubMenu(cell, $subMenu, $innerSubMenu);
165          $subMenu.addEventListener('click', obj._emptySubContainer);
166        }
167      });
168    });
169  };
170
171  // appends navigation to $el
172  obj._addNavButtons = function(menuTextColor) {
173    $leftButton = document.createElement('button');
174    $rightButton = document.createElement('button');
175
176    $leftButton.className = 'js-scrollable-nav--button js-scrollable-nav--button--prev';
177    $rightButton.className = 'js-scrollable-nav--button js-scrollable-nav--button--next';
178
179    // Icon-only, so the label goes on the button and the icon is hidden.
180    $leftButton.type = 'button';
181    $rightButton.type = 'button';
182    if (navLabels.prev) {
183      $leftButton.setAttribute('aria-label', navLabels.prev);
184    }
185    if (navLabels.next) {
186      $rightButton.setAttribute('aria-label', navLabels.next);
187    }
188
189    $leftButton.innerHTML = '<i class="af-font-icon-chevron-left huge" aria-hidden="true" style="color: ' + menuTextColor + '"></i>';
190    $rightButton.innerHTML = '<i class="af-font-icon-chevron-right huge" aria-hidden="true" style="color: ' + menuTextColor + '"></i>';
191
192    $el.appendChild($leftButton);
193    $el.appendChild($rightButton);
194  };
195
196  // tests if buttons must be hidden or not
197  obj._checkButtonVisibility = function () {
198    menuOffset = window.getComputedStyle($menu);
199    menuOffset = parseInt(menuOffset.marginLeft) + parseInt(menuOffset.marginRight);
200    setTimeout(function () {
201      if (obj._residualSpace() < 0 && obj._residualSpace() <= -menuOffset) {
202        $leftButton.classList.add('js-scrollable-nav--button-active');
203        $rightButton.classList.add('js-scrollable-nav--button-active')
204
205        $menu.style.overflow = 'hidden';
206
207        if (menuStep > 1) {
208          $rightButton.classList.remove('js-scrollable-nav--button-active');
209        } else if (menuStep === 0) {
210          $leftButton.classList.remove('js-scrollable-nav--button-active');
211        }
212      } else {
213        $rightButton.classList.remove('js-scrollable-nav--button-active');
214        $leftButton.classList.remove('js-scrollable-nav--button-active');
215        $menu.style.overflow = 'inherit';
216      }
217    }, 500);
218  };
219
220  // tests the difference in size from <ul> and the sum of its <li>'s elements
221  obj._residualSpace = function () {
222    var $lis = $menu.children;
223    var elementsWidth = 0;
224    for (var i = 0; i < $lis.length; i++) {
225      var $li = $lis[i];
226      var styles = window.getComputedStyle($li);
227      var marginRight = parseFloat(styles['marginRight']);
228      elementsWidth += $li.offsetWidth + marginRight;
229    }
230    return $menu.offsetWidth - elementsWidth;
231  };
232
233  // get color of an element
234  obj._getColor = function(el) {
235    el = (typeof el === 'string') ? document.querySelector(el) : el;
236    return window.getComputedStyle(el).color;
237  };
238
239  // get the color of menu's first element
240  obj._firstElementColor = function () {
241    var child = $menu.querySelector('a');
242    return obj._getColor(child);
243  };
244
245  obj.init = function (labels) {
246    navLabels = labels || navLabels;
247
248    $el = document.querySelector('.nav-collapse.collapse');
249    if ($el === null) {
250      return;
251    }
252    $el.classList.add('js-scrollable-nav');
253
254    $menu = $el.querySelector('#main');
255    if ($menu === null) {
256      return;
257    }
258
259    var isInit = $el.classList.contains('js-scrollable-nav-active');
260
261    if (!isInit) {
262      window.AF.menuIsInitialized = true;
263      $menuElements = $el.querySelectorAll('#main > li');
264      $menuFirstElement = $el.querySelector('#main > li:first-child');
265      $subContainer = document.createElement('div');
266
267      $el.classList.add('js-scrollable-nav-active');
268      menuTextColor = obj._firstElementColor();
269      $menu.style.overflow = 'hidden';
270      $subContainer.className = 'subContainer nav';
271      $el.appendChild($subContainer);
272
273      obj._addNavButtons(menuTextColor);
274      obj._checkButtonVisibility();
275      obj._addListeners();
276
277      // Inside the branch that builds the buttons: a page rendering the menu
278      // twice calls `init` twice on the same nav, and the second call has
279      // nothing to listen for.
280      window.addEventListener('resize', function () {
281        obj._checkButtonVisibility();
282      });
283    }
284  };
285
286}(window, document, scrollablenav));

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.