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