1'use strict'; 2 3/*! 4 * A global namespace for F1000 JavaScript 5 * Should eventually be in a separate file 6 */ 7var F1000 = F1000 || {}; 8 9 10/*! 11 * Menu with items, and optionally sub-menus 12 * Keyboard control 13 */ 14F1000.Navbar = function($navbar, options) { 15 16 this.NAVBAR = '.js-navbar'; 17 this.MAIN_MENU = '.js-main-menu'; 18 this.NAVBAR_EXPANDED_CLASS = 'has-expanded-navbar'; 19 this.NAVBAR_BLOCK = '.js-navbar-block'; 20 this.NAVBAR_SPACERS = '.js-navbar-space'; 21 this.NAVBAR_TOGGLE = '.js-navbar-toggle'; 22 this.NAVBAR_SEARCH_TOGGLE = '.js-navbar-search-toggle'; 23 this.NAVBAR_CLEAR_TOGGLE = '.js-navbar-clear-toggle'; 24 25 this.keyCodes = { 26 ENTER: 13, 27 ESCAPE: 27, 28 SPACE: 32, 29 UP_ARROW: 38, 30 DOWN_ARROW: 40, 31 LEFT_ARROW: 37, 32 RIGHT_ARROW: 39, 33 END: 35, 34 HOME: 36 35 }; 36 37 var self = this; 38 self.init($navbar, options); 39 40 41 return this; 42} 43 44 45F1000.Navbar.prototype = { 46 47 init: function($navbar, options) { 48 this.$navbar = $navbar; 49 this.$menus = F1000._.find(this.$navbar, this.MAIN_MENU); 50 this.$menuBlock = F1000._.find(this.$navbar, this.NAVBAR_BLOCK); 51 this.$toggles = F1000._.find(this.$navbar, this.NAVBAR_TOGGLE); 52 this.$searchToggles = F1000._.find(this.$navbar, this.NAVBAR_SEARCH_TOGGLE); 53 this.$clearToggles = F1000._.find(this.$navbar, this.NAVBAR_CLEAR_TOGGLE); 54 this.$spacers = F1000._.find(document, this.NAVBAR_SPACERS); 55 56 this.menus = this.$menus.map(this.initMenu.bind(this)); 57 this.height = this.getHeight(this.$navbar); 58 this.isExpanded = false; 59 this.updateSpacers(); 60 61 window.addEventListener('resize', this.onWindowResize.bind(this)); 62 F1000._.addEventListener(this.$toggles, 'click', this.onToggleClicked.bind(this)); 63 F1000._.addEventListener(this.$searchToggles, 'click', this.onSearchClicked.bind(this)); 64 F1000._.addEventListener(this.$clearToggles, 'click', this.onClearClicked.bind(this)); 65 66 this._focusOnTransitionEnd = this.focusOnTransitionEnd.bind(this); 67 }, 68 69 getHeight: function($elem) { 70 return $elem.offsetHeight; 71 }, 72 73 setHeight: function(height) { 74 return function($elem) { 75 F1000._.css($elem, { height: height + 'px' } ); 76 } 77 }, 78 79 setToggleExpanded: function($elem) { 80 $elem.setAttribute('aria-expanded', true); 81 $elem.classList.add('is-submenu-open'); 82 83 return $elem; 84 }, 85 86 setToggleCollapsed: function($elem) { 87 $elem.setAttribute('aria-expanded', false); 88 $elem.classList.remove('is-submenu-open'); 89 90 return $elem; 91 }, 92 93 setBlockExpanded: function($elem) { 94 $elem.classList.add('is-expanded'); 95 $elem.classList.remove('is-collapsed'); 96 }, 97 98 setBlockCollapsed: function($elem) { 99 $elem.classList.add('is-collapsed'); 100 $elem.classList.remove('is-expanded'); 101 }, 102 103 updateSpacers: function() { 104 this.$spacers.map(this.setHeight(this.height)); 105 }, 106 107 initMenu: function($menu) { 108 return new F1000.Menu($menu); 109 }, 110 111 expand: function() { 112 if(this.isExpanded) { 113 return this; 114 } 115 116 this.$toggles.map(this.setToggleExpanded); 117 this.$menuBlock.map(this.setBlockExpanded); 118 this.setBlockExpanded(this.$navbar); 119 document.body.classList.add(this.NAVBAR_EXPANDED_CLASS); 120 this.isExpanded = true; 121 window.addEventListener('keydown', this.onKeyDown.bind(this)); 122 F1000._.addEventListener(this.$menuBlock, 'click', this.onMenuBlockClicked.bind(this)); 123 }, 124 125 collapse: function() { 126 if(!this.isExpanded) { 127 return this; 128 } 129 130 this.$toggles.map(this.setToggleCollapsed); 131 this.$menuBlock.map(this.setBlockCollapsed); 132 this.setBlockCollapsed(this.$navbar); 133 document.body.classList.remove(this.NAVBAR_EXPANDED_CLASS); 134 this.isExpanded = false; 135 window.removeEventListener('keydown', this.onKeyDown.bind(this)); 136 F1000._.removeEventListener(this.$menuBlock, 'keydown', this.onKeyDown.bind(this)); 137 }, 138 139 toggle: function() { 140 if(this.isExpanded) { 141 this.collapse(); 142 } else { 143 this.expand(); 144 } 145 }, 146 147 onWindowResize: function(event) { 148 this.height = this.getHeight(this.$navbar); 149 this.updateSpacers(); 150 }, 151 152 onSearchClicked: function(event) { 153 var target = event.currentTarget.getAttribute('data-target'); 154 var $target = document.getElementById(target); 155 this.setBlockExpanded($target); 156 157 var $targetToggles = this.$toggles 158 .filter(function($toggle) { 159 return $toggle.getAttribute('data-target') === target 160 }) 161 .map(this.setToggleExpanded); 162 }, 163 164 onClearClicked: function(event) { 165 var target = event.currentTarget.getAttribute('data-target'); 166 var $target = document.getElementById(target); 167 this.setBlockCollapsed($target); 168 169 var $targetToggles = this.$toggles 170 .filter(function($toggle) { 171 return $toggle.getAttribute('data-target') === target 172 }) 173 .map(this.setToggleCollapsed); 174 }, 175 176 onToggleClicked: function(event) { 177 event.preventDefault(); 178 179 if(event.currentTarget.hasAttribute('data-focus')) {
180 var $focusTarget = F1000._.find(document, event.currentTarget.getAttribute('data-focus')); 181 182 if($focusTarget.length > 0) { 183 if(this.isExpanded) { 184 $focusTarget[0].focus(); 185 } else { 186 this.$focusTarget = $focusTarget[0]; 187 this.$menuBlock[0].addEventListener('transitionend', this._focusOnTransitionEnd); 188 this.expand(); 189 } 190 } 191 192 } else { 193 this.toggle(); 194 } 195 }, 196 197 /** 198 * Close the navbar when pressing escape 199 */ 200 onKeyDown: function(event) { 201 202 switch(event.keyCode) { 203 204 // HOME - go to first item 205 case this.keyCodes.ESCAPE: 206 this.collapse(); 207 break; 208 } 209 }, 210 211 /** 212 * Close the navbar when clicking outside the menu 213 */ 214 onMenuBlockClicked: function(event) { 215 if(event.target === this.$menuBlock[0]) { 216 this.collapse(); 217 } 218 }, 219 220 /** 221 * Focus an item after navbar has completed expand transition 222 * This is used to focus the search input from the navbar. 223 * If we focus the input directly, some browsers will scroll the menu into 224 * view instantly, so instead we wait for the transition to complete. 225 */ 226 focusOnTransitionEnd: function(event) { 227 this.$menuBlock[0].removeEventListener('transitionend', this._focusOnTransitionEnd); 228 229 if(!this.$focusTarget) { 230 return; 231 } 232 233 this.$focusTarget.focus(); 234 this.$focusTarget = null; 235 } 236}
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.