Add deprecation notice
[web.git] / js / foundation / foundation.tooltip.js
1 ;
2 (function ($, window, document, undefined) {
3 'use strict';
4
5 Foundation.libs.tooltip = {
6 name: 'tooltip',
7
8 version: '5.5.0',
9
10 settings: {
11 additional_inheritable_classes: [],
12 tooltip_class: '.tooltip',
13 append_to: 'body',
14 touch_close_text: 'Tap To Close',
15 disable_for_touch: false,
16 hover_delay: 200,
17 show_on: 'all',
18 tip_template: function (selector, content) {
19 return '<span data-selector="' + selector + '" id="' + selector + '" class="'
20 + Foundation.libs.tooltip.settings.tooltip_class.substring(1)
21 + '" role="tooltip">' + content + '<span class="nub"></span></span>';
22 }
23 },
24
25 cache: {},
26
27 init: function (scope, method, options) {
28 Foundation.inherit(this, 'random_str');
29 this.bindings(method, options);
30 },
31
32 should_show: function (target, tip) {
33 var settings = $.extend({}, this.settings, this.data_options(target));
34
35 if (settings.show_on === 'all') {
36 return true;
37 } else if (this.small() && settings.show_on === 'small') {
38 return true;
39 } else if (this.medium() && settings.show_on === 'medium') {
40 return true;
41 } else if (this.large() && settings.show_on === 'large') {
42 return true;
43 }
44 return false;
45 },
46
47 medium: function () {
48 return matchMedia(Foundation.media_queries['medium']).matches;
49 },
50
51 large: function () {
52 return matchMedia(Foundation.media_queries['large']).matches;
53 },
54
55 events: function (instance) {
56 var self = this,
57 S = self.S;
58
59 self.create(this.S(instance));
60
61 $(this.scope)
62 .off('.tooltip')
63 .on('mouseenter.fndtn.tooltip mouseleave.fndtn.tooltip touchstart.fndtn.tooltip MSPointerDown.fndtn.tooltip',
64 '[' + this.attr_name() + ']', function (e) {
65 var $this = S(this),
66 settings = $.extend({}, self.settings, self.data_options($this)),
67 is_touch = false;
68
69 if (Modernizr.touch && /touchstart|MSPointerDown/i.test(e.type) && S(e.target).is('a')) {
70 return false;
71 }
72
73 if (/mouse/i.test(e.type) && self.ie_touch(e)) return false;
74
75 if ($this.hasClass('open')) {
76 if (Modernizr.touch && /touchstart|MSPointerDown/i.test(e.type)) e.preventDefault();
77 self.hide($this);
78 } else {
79 if (settings.disable_for_touch && Modernizr.touch && /touchstart|MSPointerDown/i.test(e.type)) {
80 return;
81 } else if (!settings.disable_for_touch && Modernizr.touch && /touchstart|MSPointerDown/i.test(e.type)) {
82 e.preventDefault();
83 S(settings.tooltip_class + '.open').hide();
84 is_touch = true;
85 }
86
87 if (/enter|over/i.test(e.type)) {
88 this.timer = setTimeout(function () {
89 var tip = self.showTip($this);
90 }.bind(this), self.settings.hover_delay);
91 } else if (e.type === 'mouseout' || e.type === 'mouseleave') {
92 clearTimeout(this.timer);
93 self.hide($this);
94 } else {
95 self.showTip($this);
96 }
97 }
98 })
99 .on('mouseleave.fndtn.tooltip touchstart.fndtn.tooltip MSPointerDown.fndtn.tooltip', '[' + this.attr_name() + '].open', function (e) {
100 if (/mouse/i.test(e.type) && self.ie_touch(e)) return false;
101
102 if ($(this).data('tooltip-open-event-type') == 'touch' && e.type == 'mouseleave') {
103 return;
104 }
105 else if ($(this).data('tooltip-open-event-type') == 'mouse' && /MSPointerDown|touchstart/i.test(e.type)) {
106 self.convert_to_touch($(this));
107 } else {
108 self.hide($(this));
109 }
110 })
111 .on('DOMNodeRemoved DOMAttrModified', '[' + this.attr_name() + ']:not(a)', function (e) {
112 self.hide(S(this));
113 });
114 },
115
116 ie_touch: function (e) {
117 // How do I distinguish between IE11 and Windows Phone 8?????
118 return false;
119 },
120
121 showTip: function ($target) {
122 var $tip = this.getTip($target);
123 if (this.should_show($target, $tip)) {
124 return this.show($target);
125 }
126 return;
127 },
128
129 getTip: function ($target) {
130 var selector = this.selector($target),
131 settings = $.extend({}, this.settings, this.data_options($target)),
132 tip = null;
133
134 if (selector) {
135 tip = this.S('span[data-selector="' + selector + '"]' + settings.tooltip_class);
136 }
137
138 return (typeof tip === 'object') ? tip : false;
139 },
140
141 selector: function ($target) {
142 var id = $target.attr('id'),
143 dataSelector = $target.attr(this.attr_name()) || $target.attr('data-selector');
144
145 if ((id && id.length < 1 || !id) && typeof dataSelector != 'string') {
146 dataSelector = this.random_str(6);
147 $target
148 .attr('data-selector', dataSelector)
149 .attr('aria-describedby', dataSelector);
150 }
151
152 return (id && id.length > 0) ? id : dataSelector;
153 },
154
155 create: function ($target) {
156 var self = this,
157 settings = $.extend({}, this.settings, this.data_options($target)),
158 tip_template = this.settings.tip_template;
159
160 if (typeof settings.tip_template === 'string' && window.hasOwnProperty(settings.tip_template)) {
161 tip_template = window[settings.tip_template];
162 }
163
164 var $tip = $(tip_template(this.selector($target), $('<div></div>').html($target.attr('title')).html())),
165 classes = this.inheritable_classes($target);
166
167 $tip.addClass(classes).appendTo(settings.append_to);
168
169 if (Modernizr.touch) {
170 $tip.append('<span class="tap-to-close">' + settings.touch_close_text + '</span>');
171 $tip.on('touchstart.fndtn.tooltip MSPointerDown.fndtn.tooltip', function (e) {
172 self.hide($target);
173 });
174 }
175
176 $target.removeAttr('title').attr('title', '');
177 },
178
179 reposition: function (target, tip, classes) {
180 var width, nub, nubHeight, nubWidth, column, objPos;
181
182 tip.css('visibility', 'hidden').show();
183
184 width = target.data('width');
185 nub = tip.children('.nub');
186 nubHeight = nub.outerHeight();
187 nubWidth = nub.outerHeight();
188
189 if (this.small()) {
190 tip.css({'width': '100%'});
191 } else {
192 tip.css({'width': (width) ? width : 'auto'});
193 }
194
195 objPos = function (obj, top, right, bottom, left, width) {
196 return obj.css({
197 'top': (top) ? top : 'auto',
198 'bottom': (bottom) ? bottom : 'auto',
199 'left': (left) ? left : 'auto',
200 'right': (right) ? right : 'auto'
201 }).end();
202 };
203
204 objPos(tip, (target.offset().top + target.outerHeight() + 10), 'auto', 'auto', target.offset().left);
205
206 if (this.small()) {
207 objPos(tip, (target.offset().top + target.outerHeight() + 10), 'auto', 'auto', 12.5, $(this.scope).width());
208 tip.addClass('tip-override');
209 objPos(nub, -nubHeight, 'auto', 'auto', target.offset().left);
210 } else {
211 var left = target.offset().left;
212 if (Foundation.rtl) {
213 nub.addClass('rtl');
214 left = target.offset().left + target.outerWidth() - tip.outerWidth();
215 }
216 objPos(tip, (target.offset().top + target.outerHeight() + 10), 'auto', 'auto', left);
217 tip.removeClass('tip-override');
218 if (classes && classes.indexOf('tip-top') > -1) {
219 if (Foundation.rtl) nub.addClass('rtl');
220 objPos(tip, (target.offset().top - tip.outerHeight()), 'auto', 'auto', left)
221 .removeClass('tip-override');
222 } else if (classes && classes.indexOf('tip-left') > -1) {
223 objPos(tip, (target.offset().top + (target.outerHeight() / 2) - (tip.outerHeight() / 2)), 'auto', 'auto', (target.offset().left - tip.outerWidth() - nubHeight))
224 .removeClass('tip-override');
225 nub.removeClass('rtl');
226 } else if (classes && classes.indexOf('tip-right') > -1) {
227 objPos(tip, (target.offset().top + (target.outerHeight() / 2) - (tip.outerHeight() / 2)), 'auto', 'auto', (target.offset().left + target.outerWidth() + nubHeight))
228 .removeClass('tip-override');
229 nub.removeClass('rtl');
230 }
231 }
232
233 tip.css('visibility', 'visible').hide();
234 },
235
236 small: function () {
237 return matchMedia(Foundation.media_queries.small).matches && !matchMedia(Foundation.media_queries.medium).matches;
238 },
239
240 inheritable_classes: function ($target) {
241 var settings = $.extend({}, this.settings, this.data_options($target)),
242 inheritables = ['tip-top', 'tip-left', 'tip-bottom', 'tip-right', 'radius', 'round'].concat(settings.additional_inheritable_classes),
243 classes = $target.attr('class'),
244 filtered = classes ? $.map(classes.split(' '), function (el, i) {
245 if ($.inArray(el, inheritables) !== -1) {
246 return el;
247 }
248 }).join(' ') : '';
249
250 return $.trim(filtered);
251 },
252
253 convert_to_touch: function ($target) {
254 var self = this,
255 $tip = self.getTip($target),
256 settings = $.extend({}, self.settings, self.data_options($target));
257
258 if ($tip.find('.tap-to-close').length === 0) {
259 $tip.append('<span class="tap-to-close">' + settings.touch_close_text + '</span>');
260 $tip.on('click.fndtn.tooltip.tapclose touchstart.fndtn.tooltip.tapclose MSPointerDown.fndtn.tooltip.tapclose', function (e) {
261 self.hide($target);
262 });
263 }
264
265 $target.data('tooltip-open-event-type', 'touch');
266 },
267
268 show: function ($target) {
269 var $tip = this.getTip($target);
270
271 if ($target.data('tooltip-open-event-type') == 'touch') {
272 this.convert_to_touch($target);
273 }
274
275 this.reposition($target, $tip, $target.attr('class'));
276 $target.addClass('open');
277 $tip.fadeIn(150);
278 },
279
280 hide: function ($target) {
281 var $tip = this.getTip($target);
282
283 $tip.fadeOut(150, function () {
284 $tip.find('.tap-to-close').remove();
285 $tip.off('click.fndtn.tooltip.tapclose MSPointerDown.fndtn.tapclose');
286 $target.removeClass('open');
287 });
288 },
289
290 off: function () {
291 var self = this;
292 this.S(this.scope).off('.fndtn.tooltip');
293 this.S(this.settings.tooltip_class).each(function (i) {
294 $('[' + self.attr_name() + ']').eq(i).attr('title', $(this).text());
295 }).remove();
296 },
297
298 reflow: function () {
299 }
300 };
301 }(jQuery, window, window.document));