|
1 | 1 | /* |
2 | | - * heMenu v1.0.4 |
| 2 | + * heMenu v1.0.5 |
3 | 3 | * https://github.com/meceware/heMenu |
4 | 4 | * |
5 | 5 | * Made by Mehmet Celik (https://www.meceware.com/) |
|
29 | 29 | // If the global wrapper (window) is undefined, do nothing |
30 | 30 | if ('undefined' === typeof global.document) { |
31 | 31 | return; |
32 | | - } // Default options |
33 | | - |
| 32 | + } |
34 | 33 |
|
| 34 | + // Default options |
35 | 35 | const defaults = { |
36 | 36 | menu: '.hemenu-menu', |
37 | 37 | trigger: null, |
|
40 | 40 | theme: 'light', |
41 | 41 | position: 'left' |
42 | 42 | }; |
43 | | - |
44 | 43 | const forEach = (array, callback, scope) => { |
45 | 44 | for (let i = 0; i < array.length; i++) { |
46 | 45 | callback.call(scope, array[i]); // passes back stuff we need |
|
52 | 51 | if (element.matches(selector)) { |
53 | 52 | return element; |
54 | 53 | } |
55 | | - |
56 | 54 | element = element.parentElement || element.parentNode; |
57 | 55 | } |
58 | | - |
59 | 56 | return null; |
60 | 57 | }; |
61 | | - |
62 | 58 | class heMenu { |
63 | 59 | constructor(wrapper, options = {}) { |
64 | 60 | // const isIE11 = navigator.userAgent.indexOf( 'MSIE' ) > -1 || navigator.appVersion.indexOf( 'Trident/' ) > -1; |
65 | 61 | const getElement = (el, parent = document) => { |
66 | 62 | if (el) { |
67 | 63 | return 'string' === typeof el ? parent.querySelector(el) : el; |
68 | 64 | } |
69 | | - |
70 | 65 | return null; |
71 | | - }; // Extend options |
72 | | - |
| 66 | + }; |
73 | 67 |
|
| 68 | + // Extend options |
74 | 69 | Object.keys(defaults).forEach(key => { |
75 | 70 | if (!Object.prototype.hasOwnProperty.call(options, key)) { |
76 | 71 | options[key] = defaults[key]; |
77 | 72 | } |
78 | | - }); // Set options |
79 | | - |
80 | | - this.options = options; // Get the container |
| 73 | + }); |
81 | 74 |
|
82 | | - this.wrapper = getElement(wrapper); // Check if wrapper exists |
| 75 | + // Set options |
| 76 | + this.options = options; |
| 77 | + // Get the container |
| 78 | + this.wrapper = getElement(wrapper); |
83 | 79 |
|
| 80 | + // Check if wrapper exists |
84 | 81 | if (!this.wrapper) { |
85 | 82 | return; |
86 | | - } // Disable double initialization |
87 | | - |
| 83 | + } |
88 | 84 |
|
| 85 | + // Disable double initialization |
89 | 86 | if (this.wrapper.heMenu) { |
90 | 87 | return; |
91 | | - } // Set position |
92 | | - |
| 88 | + } |
93 | 89 |
|
| 90 | + // Set position |
94 | 91 | forEach(['hemenu', `hemenu-${this.options.position}`, `hemenu-theme-${this.options.theme}`], cls => { |
95 | 92 | this.wrapper.classList.add(cls); |
96 | | - }); // Get menu element |
| 93 | + }); |
97 | 94 |
|
98 | | - this.menu = getElement(this.options.menu, this.wrapper); // Check if the menu exists |
| 95 | + // Get menu element |
| 96 | + this.menu = getElement(this.options.menu, this.wrapper); |
99 | 97 |
|
| 98 | + // Check if the menu exists |
100 | 99 | if (this.menu) { |
101 | | - this.menu.classList.add('hemenu-navbar'); // Create title element and append it as the first child of the menu |
| 100 | + this.menu.classList.add('hemenu-navbar'); |
102 | 101 |
|
| 102 | + // Create title element and append it as the first child of the menu |
103 | 103 | this.menuTitle = document.createElement('div'); |
104 | 104 | this.menuTitle.classList.add('hemenu-menu-title'); |
105 | | - this.menu.parentElement.insertBefore(this.menuTitle, this.menu); // Add menu title click event |
| 105 | + this.menu.parentElement.insertBefore(this.menuTitle, this.menu); |
106 | 106 |
|
| 107 | + // Add menu title click event |
107 | 108 | this.menuTitle.addEventListener('click', (event => { |
108 | 109 | /** The opened ULs. */ |
109 | 110 | const panels = this.menu.querySelectorAll('.hemenu-menu-parent'); |
110 | 111 | /** The last opened UL. */ |
111 | | - |
112 | 112 | const panel = panels[panels.length - 1]; |
113 | | - |
114 | 113 | if (panel) { |
115 | 114 | this.openPanel(panel); |
116 | 115 | event.stopImmediatePropagation(); |
117 | 116 | return true; |
118 | 117 | } |
119 | | - |
120 | 118 | return false; |
121 | | - }).bind(this)); // Open selected panel initially |
| 119 | + }).bind(this)); |
122 | 120 |
|
| 121 | + // Open selected panel initially |
123 | 122 | const setSelectedList = (() => { |
124 | 123 | /** All selected LIs. */ |
125 | 124 | const listitems = this.menu.querySelectorAll(`.${this.options.selected}`); |
126 | 125 | /** The last selected LI. */ |
127 | | - |
128 | 126 | const listitem = listitems[listitems.length - 1]; |
129 | 127 | /** The opened UL. */ |
130 | | - |
131 | 128 | let panel = null; |
132 | | - |
133 | 129 | if (listitem) { |
134 | 130 | panel = closest(listitem, 'ul', this.menu); |
135 | 131 | } |
136 | | - |
137 | 132 | if (!panel) { |
138 | 133 | panel = this.menu.querySelector('ul'); |
139 | 134 | } |
140 | | - |
141 | 135 | this.openPanel(panel); |
142 | 136 | }).bind(this); |
| 137 | + setSelectedList(); |
143 | 138 |
|
144 | | - setSelectedList(); // Set li's with level |
145 | | - |
| 139 | + // Set li's with level |
146 | 140 | forEach(this.menu.querySelectorAll('ul li'), el => { |
147 | 141 | if (el.querySelector('ul')) { |
148 | 142 | el.classList.add('hemenu-menu-level'); |
|
151 | 145 | this.menu.addEventListener('click', (event => { |
152 | 146 | /** The clicked element */ |
153 | 147 | const target = event.target; |
154 | | - |
155 | 148 | if (target.matches('a')) { |
156 | 149 | return; |
157 | 150 | } |
158 | | - /** Parent listitem for the submenu. */ |
159 | 151 |
|
160 | | - |
161 | | - let listitem; // Find the parent listitem. |
162 | | - |
163 | | - if (closest(target, 'span', this.menu)) { |
| 152 | + /** Parent listitem for the submenu. */ |
| 153 | + let listitem; |
| 154 | + // Find the parent listitem. |
| 155 | + if (closest(target, 'span, div.hemenu-span', this.menu)) { |
164 | 156 | listitem = target.parentElement; |
165 | 157 | } else if (closest(target, 'li', this.menu)) { |
166 | 158 | listitem = target; |
167 | 159 | } else { |
168 | 160 | listitem = false; |
169 | 161 | } |
170 | | - |
171 | 162 | if (listitem) { |
172 | 163 | forEach(listitem.children, panel => { |
173 | 164 | if (panel.matches('ul')) { |
|
177 | 168 | event.stopImmediatePropagation(); |
178 | 169 | } |
179 | 170 | }).bind(this)); |
180 | | - } // Backdrop |
181 | | - |
| 171 | + } |
182 | 172 |
|
| 173 | + // Backdrop |
183 | 174 | const addBackdrop = (self = this) => { |
184 | 175 | // Create the backdrop. |
185 | 176 | const backdrop = document.createElement('div'); |
186 | | - backdrop.classList.add('hemenu-bg'); // Backdrop close event. |
| 177 | + backdrop.classList.add('hemenu-bg'); |
187 | 178 |
|
| 179 | + // Backdrop close event. |
188 | 180 | const close = event => { |
189 | 181 | self.close(); |
190 | 182 | event.preventDefault(); |
191 | 183 | event.stopImmediatePropagation(); |
192 | 184 | }; |
193 | | - |
194 | 185 | backdrop.addEventListener('touchstart', close.bind(self)); |
195 | 186 | backdrop.addEventListener('mousedown', close.bind(self)); |
196 | 187 | return backdrop; |
197 | 188 | }; |
| 189 | + this.wrapper.appendChild(addBackdrop()); |
198 | 190 |
|
199 | | - this.wrapper.appendChild(addBackdrop()); // Add trigger event |
200 | | - |
| 191 | + // Add trigger event |
201 | 192 | const triggerClick = event => { |
202 | 193 | event.preventDefault(); |
203 | 194 | this.open(); |
204 | 195 | }; |
205 | | - |
206 | 196 | const trigger = getElement(this.options.trigger); |
207 | | - |
208 | 197 | if (trigger) { |
209 | 198 | trigger.addEventListener('click', triggerClick.bind(this)); |
210 | | - } // Store the instance in the container |
211 | | - |
| 199 | + } |
212 | 200 |
|
| 201 | + // Store the instance in the container |
213 | 202 | this.wrapper.heMenu = this; |
214 | 203 | this.wrapper.setAttribute('data-hemenu-initialized', true); |
215 | 204 | } |
216 | | - |
217 | 205 | openPanel(panel) { |
218 | 206 | //Title above the panel to open. |
219 | | - let title = this.options.title; // Get panel title |
| 207 | + let title = this.options.title; |
220 | 208 |
|
| 209 | + // Get panel title |
221 | 210 | forEach(panel.parentElement.children, child => { |
222 | | - if (child.matches('a, span')) { |
| 211 | + if (child.matches('a, span, div.hemenu-span')) { |
223 | 212 | title = child.textContent; |
224 | 213 | } |
225 | | - }); // Set the title. |
226 | | - |
227 | | - this.menuTitle.textContent = title; // Unset all panels from being opened and parent. |
| 214 | + }); |
| 215 | + // Set the title. |
| 216 | + this.menuTitle.textContent = title; |
228 | 217 |
|
| 218 | + // Unset all panels from being opened and parent. |
229 | 219 | forEach(panel.querySelectorAll('.hemenu-menu-parent'), open => { |
230 | 220 | open.classList.remove('hemenu-menu-parent'); |
231 | 221 | }); |
232 | 222 | forEach(panel.querySelectorAll('.hemenu-menu-open'), open => { |
233 | 223 | open.classList.remove('hemenu-menu-open'); |
234 | | - }); // Set the current panel as being opened. |
| 224 | + }); |
235 | 225 |
|
| 226 | + // Set the current panel as being opened. |
236 | 227 | panel.classList.remove('hemenu-menu-parent'); |
237 | | - panel.classList.add('hemenu-menu-open'); // Set all parent panels as being parent. |
| 228 | + panel.classList.add('hemenu-menu-open'); |
238 | 229 |
|
| 230 | + // Set all parent panels as being parent. |
239 | 231 | let parent = panel; |
240 | 232 | let depth = 0; |
241 | | - |
242 | 233 | do { |
243 | 234 | parent = closest(parent.parentElement, 'ul', this.menu); |
244 | | - |
245 | 235 | if (parent) { |
246 | 236 | parent.classList.add('hemenu-menu-open'); |
247 | 237 | parent.classList.add('hemenu-menu-parent'); |
248 | 238 | depth++; |
249 | 239 | } |
250 | 240 | } while (parent); |
251 | | - |
252 | 241 | if (depth !== 0) { |
253 | 242 | this.menuTitle.classList.add('hemenu-title-back'); |
254 | 243 | } else { |
255 | 244 | this.menuTitle.classList.remove('hemenu-title-back'); |
256 | 245 | } |
257 | 246 | } |
258 | | - |
259 | 247 | open() { |
260 | 248 | this.wrapper.classList.add('hemenu-open'); |
261 | 249 | document.body.classList.add('hemenu-opened'); |
262 | 250 | } |
263 | | - |
264 | 251 | close() { |
265 | 252 | this.wrapper.classList.remove('hemenu-open'); |
266 | 253 | document.body.classList.remove('hemenu-opened'); |
267 | | - } // Destroys and unloads the player |
268 | | - |
| 254 | + } |
269 | 255 |
|
| 256 | + // Destroys and unloads the player |
270 | 257 | destroy() { |
271 | 258 | // We wrap this next part in try...catch in case the element is already gone for some reason |
272 | 259 | try { |
273 | 260 | if (!(this.wrapper.hasAttribute('data-hemenu-initialized') && this.wrapper.getAttribute('data-hemenu-initialized') === 'true' && this.wrapper.heMenu)) { |
274 | 261 | return; |
275 | 262 | } |
276 | | - |
277 | 263 | document.body.classList.remove('hemenu-opened'); |
278 | 264 | forEach([`hemenu-${this.options.position}`, `hemenu-theme-${this.options.theme}`, 'hemenu-open'], cls => { |
279 | 265 | this.wrapper.classList.remove(cls); |
280 | 266 | }); |
281 | 267 | this.wrapper.querySelector('.hemenu-bg').remove(); |
282 | 268 | this.wrapper.querySelector('.hemenu-menu-title').remove(); |
283 | | - |
284 | 269 | if (this.menu) { |
285 | 270 | this.menu.classList.remove('hemenu-navbar'); |
286 | 271 | this.menuTitle.remove(); |
|
293 | 278 | forEach(this.menu.querySelectorAll('.hemenu-menu-open'), el => { |
294 | 279 | el.classList.remove('hemenu-menu-open'); |
295 | 280 | }); |
296 | | - } // TODO: remove click events |
297 | | - |
| 281 | + } |
298 | 282 |
|
299 | | - this.wrapper.removeAttribute('data-hemenu-initialized'); // Delete the instance |
| 283 | + // TODO: remove click events |
300 | 284 |
|
| 285 | + this.wrapper.removeAttribute('data-hemenu-initialized'); |
| 286 | + // Delete the instance |
301 | 287 | delete this.menu.heMenu; |
302 | | - } catch (e) {// Nothing to do when error is invoked |
| 288 | + } catch (e) { |
| 289 | + // Nothing to do when error is invoked |
303 | 290 | } |
304 | 291 | } |
305 | | - |
306 | 292 | } |
307 | | - |
308 | 293 | heMenu.destroy = () => { |
309 | 294 | forEach(document.querySelectorAll('[data-hemenu-initialized]'), el => { |
310 | 295 | el.heMenu.destroy(); |
311 | 296 | }); |
312 | | - }; // Provide method for scanning the DOM and initializing heMenu from attribute |
313 | | - |
| 297 | + }; |
314 | 298 |
|
| 299 | + // Provide method for scanning the DOM and initializing heMenu from attribute |
315 | 300 | heMenu.scan = () => { |
316 | 301 | // API method for scanning the DOM and initializing vide instances from data-vide attribute |
317 | 302 | // Scan the DOM for elements that have data-hemenu attribute and initialize new heMenu instance based on that attribute |
|
323 | 308 | // this element already has an instance |
324 | 309 | return; |
325 | 310 | } |
326 | | - |
327 | 311 | try { |
328 | 312 | new heMenu(el, JSON.parse(decodeURIComponent(el.getAttribute('data-hemenu')))); |
329 | | - } catch (e) {// Nothin to do when an error is invoked |
| 313 | + } catch (e) { |
| 314 | + // Nothin to do when an error is invoked |
330 | 315 | } |
331 | 316 | }); |
332 | 317 | }; |
333 | | - |
334 | 318 | if (document.readyState !== 'loading') { |
335 | 319 | scan(); |
336 | 320 | } else { |
337 | 321 | document.addEventListener('DOMContentLoaded', scan); |
338 | 322 | } |
339 | 323 | }; |
340 | | - |
341 | 324 | heMenu.scan(); |
342 | | - window.heMenu = heMenu; // Return heMenu |
| 325 | + window.heMenu = heMenu; |
343 | 326 |
|
| 327 | + // Return heMenu |
344 | 328 | return heMenu; |
345 | 329 | })('undefined' !== typeof window ? window : undefined, document); |
346 | 330 |
|
|
0 commit comments