1 | 'use strict';
|
2 |
|
3 | import $ from 'jquery';
|
4 | import { Keyboard } from './foundation.util.keyboard';
|
5 | import { Nest } from './foundation.util.nest';
|
6 | import { GetYoDigits, transitionend } from './foundation.util.core';
|
7 | import { Box } from './foundation.util.box';
|
8 | import { Plugin } from './foundation.plugin';
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | class Drilldown extends Plugin {
|
19 | |
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | _setup(element, options) {
|
27 | this.$element = element;
|
28 | this.options = $.extend({}, Drilldown.defaults, this.$element.data(), options);
|
29 | this.className = 'Drilldown';
|
30 |
|
31 | Nest.Feather(this.$element, 'drilldown');
|
32 |
|
33 | this._init();
|
34 |
|
35 | Keyboard.register('Drilldown', {
|
36 | 'ENTER': 'open',
|
37 | 'SPACE': 'open',
|
38 | 'ARROW_RIGHT': 'next',
|
39 | 'ARROW_UP': 'up',
|
40 | 'ARROW_DOWN': 'down',
|
41 | 'ARROW_LEFT': 'previous',
|
42 | 'ESCAPE': 'close',
|
43 | 'TAB': 'down',
|
44 | 'SHIFT_TAB': 'up'
|
45 | });
|
46 | }
|
47 |
|
48 | |
49 |
|
50 |
|
51 |
|
52 | _init() {
|
53 | if(this.options.autoApplyClass) {
|
54 | this.$element.addClass('drilldown');
|
55 | }
|
56 |
|
57 | this.$element.attr({
|
58 | 'role': 'tree',
|
59 | 'aria-multiselectable': false
|
60 | });
|
61 | this.$submenuAnchors = this.$element.find('li.is-drilldown-submenu-parent').children('a');
|
62 | this.$submenus = this.$submenuAnchors.parent('li').children('[data-submenu]').attr('role', 'group');
|
63 | this.$menuItems = this.$element.find('li').not('.js-drilldown-back').attr('role', 'treeitem').find('a');
|
64 | this.$element.attr('data-mutate', (this.$element.attr('data-drilldown') || GetYoDigits(6, 'drilldown')));
|
65 |
|
66 | this._prepareMenu();
|
67 | this._registerEvents();
|
68 |
|
69 | this._keyboardEvents();
|
70 | }
|
71 |
|
72 | |
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 | _prepareMenu() {
|
80 | var _this = this;
|
81 |
|
82 |
|
83 |
|
84 | this.$submenuAnchors.each(function(){
|
85 | var $link = $(this);
|
86 | var $sub = $link.parent();
|
87 | if(_this.options.parentLink){
|
88 | $link.clone().prependTo($sub.children('[data-submenu]')).wrap('<li class="is-submenu-parent-item is-submenu-item is-drilldown-submenu-item" role="menu-item"></li>');
|
89 | }
|
90 | $link.data('savedHref', $link.attr('href')).removeAttr('href').attr('tabindex', 0);
|
91 | $link.children('[data-submenu]')
|
92 | .attr({
|
93 | 'aria-hidden': true,
|
94 | 'tabindex': 0,
|
95 | 'role': 'group'
|
96 | });
|
97 | _this._events($link);
|
98 | });
|
99 | this.$submenus.each(function(){
|
100 | var $menu = $(this),
|
101 | $back = $menu.find('.js-drilldown-back');
|
102 | if(!$back.length){
|
103 | switch (_this.options.backButtonPosition) {
|
104 | case "bottom":
|
105 | $menu.append(_this.options.backButton);
|
106 | break;
|
107 | case "top":
|
108 | $menu.prepend(_this.options.backButton);
|
109 | break;
|
110 | default:
|
111 | console.error("Unsupported backButtonPosition value '" + _this.options.backButtonPosition + "'");
|
112 | }
|
113 | }
|
114 | _this._back($menu);
|
115 | });
|
116 |
|
117 | this.$submenus.addClass('invisible');
|
118 | if(!this.options.autoHeight) {
|
119 | this.$submenus.addClass('drilldown-submenu-cover-previous');
|
120 | }
|
121 |
|
122 |
|
123 | if(!this.$element.parent().hasClass('is-drilldown')){
|
124 | this.$wrapper = $(this.options.wrapper).addClass('is-drilldown');
|
125 | if(this.options.animateHeight) this.$wrapper.addClass('animate-height');
|
126 | this.$element.wrap(this.$wrapper);
|
127 | }
|
128 |
|
129 | this.$wrapper = this.$element.parent();
|
130 | this.$wrapper.css(this._getMaxDims());
|
131 | }
|
132 |
|
133 | _resize() {
|
134 | this.$wrapper.css({'max-width': 'none', 'min-height': 'none'});
|
135 |
|
136 | this.$wrapper.css(this._getMaxDims());
|
137 | }
|
138 |
|
139 | |
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | _events($elem) {
|
146 | var _this = this;
|
147 |
|
148 | $elem.off('click.zf.drilldown')
|
149 | .on('click.zf.drilldown', function(e){
|
150 | if($(e.target).parentsUntil('ul', 'li').hasClass('is-drilldown-submenu-parent')){
|
151 | e.stopImmediatePropagation();
|
152 | e.preventDefault();
|
153 | }
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | _this._show($elem.parent('li'));
|
159 |
|
160 | if(_this.options.closeOnClick){
|
161 | var $body = $('body');
|
162 | $body.off('.zf.drilldown').on('click.zf.drilldown', function(e){
|
163 | if (e.target === _this.$element[0] || $.contains(_this.$element[0], e.target)) { return; }
|
164 | e.preventDefault();
|
165 | _this._hideAll();
|
166 | $body.off('.zf.drilldown');
|
167 | });
|
168 | }
|
169 | });
|
170 | }
|
171 |
|
172 | |
173 |
|
174 |
|
175 |
|
176 |
|
177 | _registerEvents() {
|
178 | if(this.options.scrollTop){
|
179 | this._bindHandler = this._scrollTop.bind(this);
|
180 | this.$element.on('open.zf.drilldown hide.zf.drilldown closed.zf.drilldown',this._bindHandler);
|
181 | }
|
182 | this.$element.on('mutateme.zf.trigger', this._resize.bind(this));
|
183 | }
|
184 |
|
185 | |
186 |
|
187 |
|
188 |
|
189 |
|
190 | _scrollTop() {
|
191 | var _this = this;
|
192 | var $scrollTopElement = _this.options.scrollTopElement!=''?$(_this.options.scrollTopElement):_this.$element,
|
193 | scrollPos = parseInt($scrollTopElement.offset().top+_this.options.scrollTopOffset, 10);
|
194 | $('html, body').stop(true).animate({ scrollTop: scrollPos }, _this.options.animationDuration, _this.options.animationEasing,function(){
|
195 | |
196 |
|
197 |
|
198 |
|
199 | if(this===$('html')[0])_this.$element.trigger('scrollme.zf.drilldown');
|
200 | });
|
201 | }
|
202 |
|
203 | |
204 |
|
205 |
|
206 |
|
207 | _keyboardEvents() {
|
208 | var _this = this;
|
209 |
|
210 | this.$menuItems.add(this.$element.find('.js-drilldown-back > a, .is-submenu-parent-item > a')).on('keydown.zf.drilldown', function(e){
|
211 | var $element = $(this),
|
212 | $elements = $element.parent('li').parent('ul').children('li').children('a'),
|
213 | $prevElement,
|
214 | $nextElement;
|
215 |
|
216 | $elements.each(function(i) {
|
217 | if ($(this).is($element)) {
|
218 | $prevElement = $elements.eq(Math.max(0, i-1));
|
219 | $nextElement = $elements.eq(Math.min(i+1, $elements.length-1));
|
220 | return;
|
221 | }
|
222 | });
|
223 |
|
224 | Keyboard.handleKey(e, 'Drilldown', {
|
225 | next: function() {
|
226 | if ($element.is(_this.$submenuAnchors)) {
|
227 | _this._show($element.parent('li'));
|
228 | $element.parent('li').one(transitionend($element), function(){
|
229 | $element.parent('li').find('ul li a').filter(_this.$menuItems).first().focus();
|
230 | });
|
231 | return true;
|
232 | }
|
233 | },
|
234 | previous: function() {
|
235 | _this._hide($element.parent('li').parent('ul'));
|
236 | $element.parent('li').parent('ul').one(transitionend($element), function(){
|
237 | setTimeout(function() {
|
238 | $element.parent('li').parent('ul').parent('li').children('a').first().focus();
|
239 | }, 1);
|
240 | });
|
241 | return true;
|
242 | },
|
243 | up: function() {
|
244 | $prevElement.focus();
|
245 |
|
246 | return !$element.is(_this.$element.find('> li:first-child > a'));
|
247 | },
|
248 | down: function() {
|
249 | $nextElement.focus();
|
250 |
|
251 | return !$element.is(_this.$element.find('> li:last-child > a'));
|
252 | },
|
253 | close: function() {
|
254 |
|
255 | if (!$element.is(_this.$element.find('> li > a'))) {
|
256 | _this._hide($element.parent().parent());
|
257 | $element.parent().parent().siblings('a').focus();
|
258 | }
|
259 | },
|
260 | open: function() {
|
261 | if (!$element.is(_this.$menuItems)) {
|
262 | _this._hide($element.parent('li').parent('ul'));
|
263 | $element.parent('li').parent('ul').one(transitionend($element), function(){
|
264 | setTimeout(function() {
|
265 | $element.parent('li').parent('ul').parent('li').children('a').first().focus();
|
266 | }, 1);
|
267 | });
|
268 | return true;
|
269 | } else if ($element.is(_this.$submenuAnchors)) {
|
270 | _this._show($element.parent('li'));
|
271 | $element.parent('li').one(transitionend($element), function(){
|
272 | $element.parent('li').find('ul li a').filter(_this.$menuItems).first().focus();
|
273 | });
|
274 | return true;
|
275 | }
|
276 | },
|
277 | handled: function(preventDefault) {
|
278 | if (preventDefault) {
|
279 | e.preventDefault();
|
280 | }
|
281 | e.stopImmediatePropagation();
|
282 | }
|
283 | });
|
284 | });
|
285 | }
|
286 |
|
287 | |
288 |
|
289 |
|
290 |
|
291 |
|
292 | _hideAll() {
|
293 | var $elem = this.$element.find('.is-drilldown-submenu.is-active').addClass('is-closing');
|
294 | if(this.options.autoHeight) this.$wrapper.css({height:$elem.parent().closest('ul').data('calcHeight')});
|
295 | $elem.one(transitionend($elem), function(e){
|
296 | $elem.removeClass('is-active is-closing');
|
297 | });
|
298 | |
299 |
|
300 |
|
301 |
|
302 | this.$element.trigger('closed.zf.drilldown');
|
303 | }
|
304 |
|
305 | |
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 | _back($elem) {
|
312 | var _this = this;
|
313 | $elem.off('click.zf.drilldown');
|
314 | $elem.children('.js-drilldown-back')
|
315 | .on('click.zf.drilldown', function(e){
|
316 | e.stopImmediatePropagation();
|
317 |
|
318 | _this._hide($elem);
|
319 |
|
320 |
|
321 | let parentSubMenu = $elem.parent('li').parent('ul').parent('li');
|
322 | if (parentSubMenu.length) {
|
323 | _this._show(parentSubMenu);
|
324 | }
|
325 | });
|
326 | }
|
327 |
|
328 | |
329 |
|
330 |
|
331 |
|
332 |
|
333 | _menuLinkEvents() {
|
334 | var _this = this;
|
335 | this.$menuItems.not('.is-drilldown-submenu-parent')
|
336 | .off('click.zf.drilldown')
|
337 | .on('click.zf.drilldown', function(e){
|
338 |
|
339 | setTimeout(function(){
|
340 | _this._hideAll();
|
341 | }, 0);
|
342 | });
|
343 | }
|
344 |
|
345 | |
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 | _show($elem) {
|
352 | if(this.options.autoHeight) this.$wrapper.css({height:$elem.children('[data-submenu]').data('calcHeight')});
|
353 | $elem.attr('aria-expanded', true);
|
354 | $elem.children('[data-submenu]').addClass('is-active').removeClass('invisible').attr('aria-hidden', false);
|
355 | |
356 |
|
357 |
|
358 |
|
359 | this.$element.trigger('open.zf.drilldown', [$elem]);
|
360 | };
|
361 |
|
362 | |
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 | _hide($elem) {
|
369 | if(this.options.autoHeight) this.$wrapper.css({height:$elem.parent().closest('ul').data('calcHeight')});
|
370 | var _this = this;
|
371 | $elem.parent('li').attr('aria-expanded', false);
|
372 | $elem.attr('aria-hidden', true).addClass('is-closing')
|
373 | $elem.addClass('is-closing')
|
374 | .one(transitionend($elem), function(){
|
375 | $elem.removeClass('is-active is-closing');
|
376 | $elem.blur().addClass('invisible');
|
377 | });
|
378 | |
379 |
|
380 |
|
381 |
|
382 | $elem.trigger('hide.zf.drilldown', [$elem]);
|
383 | }
|
384 |
|
385 | |
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 | _getMaxDims() {
|
392 | var maxHeight = 0, result = {}, _this = this;
|
393 | this.$submenus.add(this.$element).each(function(){
|
394 | var numOfElems = $(this).children('li').length;
|
395 | var height = Box.GetDimensions(this).height;
|
396 | maxHeight = height > maxHeight ? height : maxHeight;
|
397 | if(_this.options.autoHeight) {
|
398 | $(this).data('calcHeight',height);
|
399 | if (!$(this).hasClass('is-drilldown-submenu')) result['height'] = height;
|
400 | }
|
401 | });
|
402 |
|
403 | if(!this.options.autoHeight) result['min-height'] = `${maxHeight}px`;
|
404 |
|
405 | result['max-width'] = `${this.$element[0].getBoundingClientRect().width}px`;
|
406 |
|
407 | return result;
|
408 | }
|
409 |
|
410 | |
411 |
|
412 |
|
413 |
|
414 | _destroy() {
|
415 | if(this.options.scrollTop) this.$element.off('.zf.drilldown',this._bindHandler);
|
416 | this._hideAll();
|
417 | this.$element.off('mutateme.zf.trigger');
|
418 | Nest.Burn(this.$element, 'drilldown');
|
419 | this.$element.unwrap()
|
420 | .find('.js-drilldown-back, .is-submenu-parent-item').remove()
|
421 | .end().find('.is-active, .is-closing, .is-drilldown-submenu').removeClass('is-active is-closing is-drilldown-submenu')
|
422 | .end().find('[data-submenu]').removeAttr('aria-hidden tabindex role');
|
423 | this.$submenuAnchors.each(function() {
|
424 | $(this).off('.zf.drilldown');
|
425 | });
|
426 |
|
427 | this.$submenus.removeClass('drilldown-submenu-cover-previous invisible');
|
428 |
|
429 | this.$element.find('a').each(function(){
|
430 | var $link = $(this);
|
431 | $link.removeAttr('tabindex');
|
432 | if($link.data('savedHref')){
|
433 | $link.attr('href', $link.data('savedHref')).removeData('savedHref');
|
434 | }else{ return; }
|
435 | });
|
436 | };
|
437 | }
|
438 |
|
439 | Drilldown.defaults = {
|
440 | |
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 | autoApplyClass: true,
|
448 | |
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 | backButton: '<li class="js-drilldown-back"><a tabindex="0">Back</a></li>',
|
455 | |
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 | backButtonPosition: 'top',
|
462 | |
463 |
|
464 |
|
465 |
|
466 |
|
467 |
|
468 | wrapper: '<div></div>',
|
469 | |
470 |
|
471 |
|
472 |
|
473 |
|
474 |
|
475 | parentLink: false,
|
476 | |
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 | closeOnClick: false,
|
483 | |
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 | autoHeight: false,
|
490 | |
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 | animateHeight: false,
|
497 | |
498 |
|
499 |
|
500 |
|
501 |
|
502 |
|
503 | scrollTop: false,
|
504 | |
505 |
|
506 |
|
507 |
|
508 |
|
509 |
|
510 | scrollTopElement: '',
|
511 | |
512 |
|
513 |
|
514 |
|
515 |
|
516 |
|
517 | scrollTopOffset: 0,
|
518 | |
519 |
|
520 |
|
521 |
|
522 |
|
523 |
|
524 | animationDuration: 500,
|
525 | |
526 |
|
527 |
|
528 |
|
529 |
|
530 |
|
531 |
|
532 | animationEasing: 'swing'
|
533 |
|
534 | };
|
535 |
|
536 | export {Drilldown};
|