comparison js/foundation/foundation.dropdown.js @ 0:7abe02bf29ec

initial commit
author Alex Krolick <whokilledtheelectricmonk@gmail.com>
date Sat, 07 Nov 2015 18:04:42 -0800
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:7abe02bf29ec
1 ;(function ($, window, document, undefined) {
2 'use strict';
3
4 Foundation.libs.dropdown = {
5 name : 'dropdown',
6
7 version : '5.5.3',
8
9 settings : {
10 active_class : 'open',
11 disabled_class : 'disabled',
12 mega_class : 'mega',
13 align : 'bottom',
14 is_hover : false,
15 hover_timeout : 150,
16 opened : function () {},
17 closed : function () {}
18 },
19
20 init : function (scope, method, options) {
21 Foundation.inherit(this, 'throttle');
22
23 $.extend(true, this.settings, method, options);
24 this.bindings(method, options);
25 },
26
27 events : function (scope) {
28 var self = this,
29 S = self.S;
30
31 S(this.scope)
32 .off('.dropdown')
33 .on('click.fndtn.dropdown', '[' + this.attr_name() + ']', function (e) {
34 var settings = S(this).data(self.attr_name(true) + '-init') || self.settings;
35 if (!settings.is_hover || Modernizr.touch) {
36 e.preventDefault();
37 if (S(this).parent('[data-reveal-id]').length) {
38 e.stopPropagation();
39 }
40 self.toggle($(this));
41 }
42 })
43 .on('mouseenter.fndtn.dropdown', '[' + this.attr_name() + '], [' + this.attr_name() + '-content]', function (e) {
44 var $this = S(this),
45 dropdown,
46 target;
47
48 clearTimeout(self.timeout);
49
50 if ($this.data(self.data_attr())) {
51 dropdown = S('#' + $this.data(self.data_attr()));
52 target = $this;
53 } else {
54 dropdown = $this;
55 target = S('[' + self.attr_name() + '="' + dropdown.attr('id') + '"]');
56 }
57
58 var settings = target.data(self.attr_name(true) + '-init') || self.settings;
59
60 if (S(e.currentTarget).data(self.data_attr()) && settings.is_hover) {
61 self.closeall.call(self);
62 }
63
64 if (settings.is_hover) {
65 self.open.apply(self, [dropdown, target]);
66 }
67 })
68 .on('mouseleave.fndtn.dropdown', '[' + this.attr_name() + '], [' + this.attr_name() + '-content]', function (e) {
69 var $this = S(this);
70 var settings;
71
72 if ($this.data(self.data_attr())) {
73 settings = $this.data(self.data_attr(true) + '-init') || self.settings;
74 } else {
75 var target = S('[' + self.attr_name() + '="' + S(this).attr('id') + '"]'),
76 settings = target.data(self.attr_name(true) + '-init') || self.settings;
77 }
78
79 self.timeout = setTimeout(function () {
80 if ($this.data(self.data_attr())) {
81 if (settings.is_hover) {
82 self.close.call(self, S('#' + $this.data(self.data_attr())));
83 }
84 } else {
85 if (settings.is_hover) {
86 self.close.call(self, $this);
87 }
88 }
89 }.bind(this), settings.hover_timeout);
90 })
91 .on('click.fndtn.dropdown', function (e) {
92 var parent = S(e.target).closest('[' + self.attr_name() + '-content]');
93 var links = parent.find('a');
94
95 if (links.length > 0 && parent.attr('aria-autoclose') !== 'false') {
96 self.close.call(self, S('[' + self.attr_name() + '-content]'));
97 }
98
99 if (e.target !== document && !$.contains(document.documentElement, e.target)) {
100 return;
101 }
102
103 if (S(e.target).closest('[' + self.attr_name() + ']').length > 0) {
104 return;
105 }
106
107 if (!(S(e.target).data('revealId')) &&
108 (parent.length > 0 && (S(e.target).is('[' + self.attr_name() + '-content]') ||
109 $.contains(parent.first()[0], e.target)))) {
110 e.stopPropagation();
111 return;
112 }
113
114 self.close.call(self, S('[' + self.attr_name() + '-content]'));
115 })
116 .on('opened.fndtn.dropdown', '[' + self.attr_name() + '-content]', function () {
117 self.settings.opened.call(this);
118 })
119 .on('closed.fndtn.dropdown', '[' + self.attr_name() + '-content]', function () {
120 self.settings.closed.call(this);
121 });
122
123 S(window)
124 .off('.dropdown')
125 .on('resize.fndtn.dropdown', self.throttle(function () {
126 self.resize.call(self);
127 }, 50));
128
129 this.resize();
130 },
131
132 close : function (dropdown) {
133 var self = this;
134 dropdown.each(function (idx) {
135 var original_target = $('[' + self.attr_name() + '=' + dropdown[idx].id + ']') || $('aria-controls=' + dropdown[idx].id + ']');
136 original_target.attr('aria-expanded', 'false');
137 if (self.S(this).hasClass(self.settings.active_class)) {
138 self.S(this)
139 .css(Foundation.rtl ? 'right' : 'left', '-99999px')
140 .attr('aria-hidden', 'true')
141 .removeClass(self.settings.active_class)
142 .prev('[' + self.attr_name() + ']')
143 .removeClass(self.settings.active_class)
144 .removeData('target');
145
146 self.S(this).trigger('closed.fndtn.dropdown', [dropdown]);
147 }
148 });
149 dropdown.removeClass('f-open-' + this.attr_name(true));
150 },
151
152 closeall : function () {
153 var self = this;
154 $.each(self.S('.f-open-' + this.attr_name(true)), function () {
155 self.close.call(self, self.S(this));
156 });
157 },
158
159 open : function (dropdown, target) {
160 this
161 .css(dropdown
162 .addClass(this.settings.active_class), target);
163 dropdown.prev('[' + this.attr_name() + ']').addClass(this.settings.active_class);
164 dropdown.data('target', target.get(0)).trigger('opened.fndtn.dropdown', [dropdown, target]);
165 dropdown.attr('aria-hidden', 'false');
166 target.attr('aria-expanded', 'true');
167 dropdown.focus();
168 dropdown.addClass('f-open-' + this.attr_name(true));
169 },
170
171 data_attr : function () {
172 if (this.namespace.length > 0) {
173 return this.namespace + '-' + this.name;
174 }
175
176 return this.name;
177 },
178
179 toggle : function (target) {
180 if (target.hasClass(this.settings.disabled_class)) {
181 return;
182 }
183 var dropdown = this.S('#' + target.data(this.data_attr()));
184 if (dropdown.length === 0) {
185 // No dropdown found, not continuing
186 return;
187 }
188
189 this.close.call(this, this.S('[' + this.attr_name() + '-content]').not(dropdown));
190
191 if (dropdown.hasClass(this.settings.active_class)) {
192 this.close.call(this, dropdown);
193 if (dropdown.data('target') !== target.get(0)) {
194 this.open.call(this, dropdown, target);
195 }
196 } else {
197 this.open.call(this, dropdown, target);
198 }
199 },
200
201 resize : function () {
202 var dropdown = this.S('[' + this.attr_name() + '-content].open');
203 var target = $(dropdown.data("target"));
204
205 if (dropdown.length && target.length) {
206 this.css(dropdown, target);
207 }
208 },
209
210 css : function (dropdown, target) {
211 var left_offset = Math.max((target.width() - dropdown.width()) / 2, 8),
212 settings = target.data(this.attr_name(true) + '-init') || this.settings,
213 parentOverflow = dropdown.parent().css('overflow-y') || dropdown.parent().css('overflow');
214
215 this.clear_idx();
216
217
218
219 if (this.small()) {
220 var p = this.dirs.bottom.call(dropdown, target, settings);
221
222 dropdown.attr('style', '').removeClass('drop-left drop-right drop-top').css({
223 position : 'absolute',
224 width : '95%',
225 'max-width' : 'none',
226 top : p.top
227 });
228
229 dropdown.css(Foundation.rtl ? 'right' : 'left', left_offset);
230 }
231 // detect if dropdown is in an overflow container
232 else if (parentOverflow !== 'visible') {
233 var offset = target[0].offsetTop + target[0].offsetHeight;
234
235 dropdown.attr('style', '').css({
236 position : 'absolute',
237 top : offset
238 });
239
240 dropdown.css(Foundation.rtl ? 'right' : 'left', left_offset);
241 }
242 else {
243
244 this.style(dropdown, target, settings);
245 }
246
247 return dropdown;
248 },
249
250 style : function (dropdown, target, settings) {
251 var css = $.extend({position : 'absolute'},
252 this.dirs[settings.align].call(dropdown, target, settings));
253
254 dropdown.attr('style', '').css(css);
255 },
256
257 // return CSS property object
258 // `this` is the dropdown
259 dirs : {
260 // Calculate target offset
261 _base : function (t, s) {
262 var o_p = this.offsetParent(),
263 o = o_p.offset(),
264 p = t.offset();
265
266 p.top -= o.top;
267 p.left -= o.left;
268
269 //set some flags on the p object to pass along
270 p.missRight = false;
271 p.missTop = false;
272 p.missLeft = false;
273 p.leftRightFlag = false;
274
275 //lets see if the panel will be off the screen
276 //get the actual width of the page and store it
277 var actualBodyWidth;
278 var windowWidth = window.innerWidth;
279
280 if (document.getElementsByClassName('row')[0]) {
281 actualBodyWidth = document.getElementsByClassName('row')[0].clientWidth;
282 } else {
283 actualBodyWidth = windowWidth;
284 }
285
286 var actualMarginWidth = (windowWidth - actualBodyWidth) / 2;
287 var actualBoundary = actualBodyWidth;
288
289 if (!this.hasClass('mega') && !s.ignore_repositioning) {
290 var outerWidth = this.outerWidth();
291 var o_left = t.offset().left;
292
293 //miss top
294 if (t.offset().top <= this.outerHeight()) {
295 p.missTop = true;
296 actualBoundary = windowWidth - actualMarginWidth;
297 p.leftRightFlag = true;
298 }
299
300 //miss right
301 if (o_left + outerWidth > o_left + actualMarginWidth && o_left - actualMarginWidth > outerWidth) {
302 p.missRight = true;
303 p.missLeft = false;
304 }
305
306 //miss left
307 if (o_left - outerWidth <= 0) {
308 p.missLeft = true;
309 p.missRight = false;
310 }
311 }
312
313 return p;
314 },
315
316 top : function (t, s) {
317 var self = Foundation.libs.dropdown,
318 p = self.dirs._base.call(this, t, s);
319
320 this.addClass('drop-top');
321
322 if (p.missTop == true) {
323 p.top = p.top + t.outerHeight() + this.outerHeight();
324 this.removeClass('drop-top');
325 }
326
327 if (p.missRight == true) {
328 p.left = p.left - this.outerWidth() + t.outerWidth();
329 }
330
331 if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
332 self.adjust_pip(this, t, s, p);
333 }
334
335 if (Foundation.rtl) {
336 return {left : p.left - this.outerWidth() + t.outerWidth(),
337 top : p.top - this.outerHeight()};
338 }
339
340 return {left : p.left, top : p.top - this.outerHeight()};
341 },
342
343 bottom : function (t, s) {
344 var self = Foundation.libs.dropdown,
345 p = self.dirs._base.call(this, t, s);
346
347 if (p.missRight == true) {
348 p.left = p.left - this.outerWidth() + t.outerWidth();
349 }
350
351 if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
352 self.adjust_pip(this, t, s, p);
353 }
354
355 if (self.rtl) {
356 return {left : p.left - this.outerWidth() + t.outerWidth(), top : p.top + t.outerHeight()};
357 }
358
359 return {left : p.left, top : p.top + t.outerHeight()};
360 },
361
362 left : function (t, s) {
363 var p = Foundation.libs.dropdown.dirs._base.call(this, t, s);
364
365 this.addClass('drop-left');
366
367 if (p.missLeft == true) {
368 p.left = p.left + this.outerWidth();
369 p.top = p.top + t.outerHeight();
370 this.removeClass('drop-left');
371 }
372
373 return {left : p.left - this.outerWidth(), top : p.top};
374 },
375
376 right : function (t, s) {
377 var p = Foundation.libs.dropdown.dirs._base.call(this, t, s);
378
379 this.addClass('drop-right');
380
381 if (p.missRight == true) {
382 p.left = p.left - this.outerWidth();
383 p.top = p.top + t.outerHeight();
384 this.removeClass('drop-right');
385 } else {
386 p.triggeredRight = true;
387 }
388
389 var self = Foundation.libs.dropdown;
390
391 if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
392 self.adjust_pip(this, t, s, p);
393 }
394
395 return {left : p.left + t.outerWidth(), top : p.top};
396 }
397 },
398
399 // Insert rule to style psuedo elements
400 adjust_pip : function (dropdown, target, settings, position) {
401 var sheet = Foundation.stylesheet,
402 pip_offset_base = 8;
403
404 if (dropdown.hasClass(settings.mega_class)) {
405 pip_offset_base = position.left + (target.outerWidth() / 2) - 8;
406 } else if (this.small()) {
407 pip_offset_base += position.left - 8;
408 }
409
410 this.rule_idx = sheet.cssRules.length;
411
412 //default
413 var sel_before = '.f-dropdown.open:before',
414 sel_after = '.f-dropdown.open:after',
415 css_before = 'left: ' + pip_offset_base + 'px;',
416 css_after = 'left: ' + (pip_offset_base - 1) + 'px;';
417
418 if (position.missRight == true) {
419 pip_offset_base = dropdown.outerWidth() - 23;
420 sel_before = '.f-dropdown.open:before',
421 sel_after = '.f-dropdown.open:after',
422 css_before = 'left: ' + pip_offset_base + 'px;',
423 css_after = 'left: ' + (pip_offset_base - 1) + 'px;';
424 }
425
426 //just a case where right is fired, but its not missing right
427 if (position.triggeredRight == true) {
428 sel_before = '.f-dropdown.open:before',
429 sel_after = '.f-dropdown.open:after',
430 css_before = 'left:-12px;',
431 css_after = 'left:-14px;';
432 }
433
434 if (sheet.insertRule) {
435 sheet.insertRule([sel_before, '{', css_before, '}'].join(' '), this.rule_idx);
436 sheet.insertRule([sel_after, '{', css_after, '}'].join(' '), this.rule_idx + 1);
437 } else {
438 sheet.addRule(sel_before, css_before, this.rule_idx);
439 sheet.addRule(sel_after, css_after, this.rule_idx + 1);
440 }
441 },
442
443 // Remove old dropdown rule index
444 clear_idx : function () {
445 var sheet = Foundation.stylesheet;
446
447 if (typeof this.rule_idx !== 'undefined') {
448 sheet.deleteRule(this.rule_idx);
449 sheet.deleteRule(this.rule_idx);
450 delete this.rule_idx;
451 }
452 },
453
454 small : function () {
455 return matchMedia(Foundation.media_queries.small).matches &&
456 !matchMedia(Foundation.media_queries.medium).matches;
457 },
458
459 off : function () {
460 this.S(this.scope).off('.fndtn.dropdown');
461 this.S('html, body').off('.fndtn.dropdown');
462 this.S(window).off('.fndtn.dropdown');
463 this.S('[data-dropdown-content]').off('.fndtn.dropdown');
464 },
465
466 reflow : function () {}
467 };
468 }(jQuery, window, window.document));