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.