PageSourceSearch

https://mniopenresearch.org/js/shared_scripts/navbar.js

js mniopenresearch.org collected 2026-09-24 12:07:30 UTC 6,356 bytes, 236 lines download raw bytes

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.