foundation.tooltip.js 11 KB

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