PageRenderTime 23ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/htdocs/wp-content/themes/vtte-revistas/js/foundation.tooltip.js

https://gitlab.com/VTTE/sitios-vtte
JavaScript | 429 lines | 232 code | 37 blank | 160 comment | 46 complexity | 1e82d2c0f033344d73110c3d6d2671f3 MD5 | raw file
  1. /**
  2. * Tooltip module.
  3. * @module foundation.tooltip
  4. * @requires foundation.util.box
  5. * @requires foundation.util.triggers
  6. */
  7. !function($, document, Foundation){
  8. 'use strict';
  9. /**
  10. * Creates a new instance of a Tooltip.
  11. * @class
  12. * @fires Tooltip#init
  13. * @param {jQuery} element - jQuery object to attach a tooltip to.
  14. * @param {Object} options - object to extend the default configuration.
  15. */
  16. function Tooltip(element, options){
  17. this.$element = element;
  18. this.options = $.extend({}, Tooltip.defaults, this.$element.data(), options);
  19. this.isActive = false;
  20. this.isClick = false;
  21. this._init();
  22. Foundation.registerPlugin(this, 'Tooltip');
  23. }
  24. Tooltip.defaults = {
  25. disableForTouch: false,
  26. /**
  27. * Time, in ms, before a tooltip should open on hover.
  28. * @option
  29. * @example 200
  30. */
  31. hoverDelay: 200,
  32. /**
  33. * Time, in ms, a tooltip should take to fade into view.
  34. * @option
  35. * @example 150
  36. */
  37. fadeInDuration: 150,
  38. /**
  39. * Time, in ms, a tooltip should take to fade out of view.
  40. * @option
  41. * @example 150
  42. */
  43. fadeOutDuration: 150,
  44. /**
  45. * Disables hover events from opening the tooltip if set to true
  46. * @option
  47. * @example false
  48. */
  49. disableHover: false,
  50. /**
  51. * Optional addtional classes to apply to the tooltip template on init.
  52. * @option
  53. * @example 'my-cool-tip-class'
  54. */
  55. templateClasses: '',
  56. /**
  57. * Non-optional class added to tooltip templates. Foundation default is 'tooltip'.
  58. * @option
  59. * @example 'tooltip'
  60. */
  61. tooltipClass: 'tooltip',
  62. /**
  63. * Class applied to the tooltip anchor element.
  64. * @option
  65. * @example 'has-tip'
  66. */
  67. triggerClass: 'has-tip',
  68. /**
  69. * Minimum breakpoint size at which to open the tooltip.
  70. * @option
  71. * @example 'small'
  72. */
  73. showOn: 'small',
  74. /**
  75. * Custom template to be used to generate markup for tooltip.
  76. * @option
  77. * @example '<div class="tooltip"></div>'
  78. */
  79. template: '',
  80. /**
  81. * Text displayed in the tooltip template on open.
  82. * @option
  83. * @example 'Some cool space fact here.'
  84. */
  85. tipText: '',
  86. touchCloseText: 'Tap to close.',
  87. /**
  88. * Allows the tooltip to remain open if triggered with a click or touch event.
  89. * @option
  90. * @example true
  91. */
  92. clickOpen: true,
  93. /**
  94. * Additional positioning classes, set by the JS
  95. * @option
  96. * @example 'top'
  97. */
  98. positionClass: '',
  99. /**
  100. * Distance, in pixels, the template should push away from the anchor on the Y axis.
  101. * @option
  102. * @example 10
  103. */
  104. vOffset: 10,
  105. /**
  106. * Distance, in pixels, the template should push away from the anchor on the X axis, if aligned to a side.
  107. * @option
  108. * @example 12
  109. */
  110. hOffset: 12
  111. };
  112. /**
  113. * Initializes the tooltip by setting the creating the tip element, adding it's text, setting private variables and setting attributes on the anchor.
  114. * @private
  115. */
  116. Tooltip.prototype._init = function(){
  117. var elemId = this.$element.attr('aria-describedby') || Foundation.GetYoDigits(6, 'tooltip');
  118. this.options.positionClass = this._getPositionClass(this.$element);
  119. this.options.tipText = this.options.tipText || this.$element.attr('title');
  120. this.template = this.options.template ? $(this.options.template) : this._buildTemplate(elemId);
  121. this.template.appendTo(document.body)
  122. .text(this.options.tipText)
  123. .hide();
  124. this.$element.attr({
  125. 'title': '',
  126. 'aria-describedby': elemId,
  127. 'data-yeti-box': elemId,
  128. 'data-toggle': elemId,
  129. 'data-resize': elemId
  130. }).addClass(this.triggerClass);
  131. //helper variables to track movement on collisions
  132. this.usedPositions = [];
  133. this.counter = 4;
  134. this.classChanged = false;
  135. this._events();
  136. };
  137. /**
  138. * Grabs the current positioning class, if present, and returns the value or an empty string.
  139. * @private
  140. */
  141. Tooltip.prototype._getPositionClass = function(element){
  142. if(!element){ return ''; }
  143. // var position = element.attr('class').match(/top|left|right/g);
  144. var position = element[0].className.match(/\b(top|left|right)\b/g);
  145. position = position ? position[0] : '';
  146. return position;
  147. };
  148. /**
  149. * builds the tooltip element, adds attributes, and returns the template.
  150. * @private
  151. */
  152. Tooltip.prototype._buildTemplate = function(id){
  153. var templateClasses = (this.options.tooltipClass + ' ' + this.options.positionClass + ' ' + this.options.templateClasses).trim();
  154. var $template = $('<div></div>').addClass(templateClasses).attr({
  155. 'role': 'tooltip',
  156. 'aria-hidden': true,
  157. 'data-is-active': false,
  158. 'data-is-focus': false,
  159. 'id': id
  160. });
  161. return $template;
  162. };
  163. /**
  164. * Function that gets called if a collision event is detected.
  165. * @param {String} position - positioning class to try
  166. * @private
  167. */
  168. Tooltip.prototype._reposition = function(position){
  169. this.usedPositions.push(position ? position : 'bottom');
  170. //default, try switching to opposite side
  171. if(!position && (this.usedPositions.indexOf('top') < 0)){
  172. this.template.addClass('top');
  173. }else if(position === 'top' && (this.usedPositions.indexOf('bottom') < 0)){
  174. this.template.removeClass(position);
  175. }else if(position === 'left' && (this.usedPositions.indexOf('right') < 0)){
  176. this.template.removeClass(position)
  177. .addClass('right');
  178. }else if(position === 'right' && (this.usedPositions.indexOf('left') < 0)){
  179. this.template.removeClass(position)
  180. .addClass('left');
  181. }
  182. //if default change didn't work, try bottom or left first
  183. else if(!position && (this.usedPositions.indexOf('top') > -1) && (this.usedPositions.indexOf('left') < 0)){
  184. this.template.addClass('left');
  185. }else if(position === 'top' && (this.usedPositions.indexOf('bottom') > -1) && (this.usedPositions.indexOf('left') < 0)){
  186. this.template.removeClass(position)
  187. .addClass('left');
  188. }else if(position === 'left' && (this.usedPositions.indexOf('right') > -1) && (this.usedPositions.indexOf('bottom') < 0)){
  189. this.template.removeClass(position);
  190. }else if(position === 'right' && (this.usedPositions.indexOf('left') > -1) && (this.usedPositions.indexOf('bottom') < 0)){
  191. this.template.removeClass(position);
  192. }
  193. //if nothing cleared, set to bottom
  194. else{
  195. this.template.removeClass(position);
  196. }
  197. this.classChanged = true;
  198. this.counter--;
  199. };
  200. /**
  201. * sets the position class of an element and recursively calls itself until there are no more possible positions to attempt, or the tooltip element is no longer colliding.
  202. * if the tooltip is larger than the screen width, default to full width - any user selected margin
  203. * @private
  204. */
  205. Tooltip.prototype._setPosition = function(){
  206. var position = this._getPositionClass(this.template),
  207. $tipDims = Foundation.Box.GetDimensions(this.template),
  208. $anchorDims = Foundation.Box.GetDimensions(this.$element),
  209. direction = (position === 'left' ? 'left' : ((position === 'right') ? 'left' : 'top')),
  210. param = (direction === 'top') ? 'height' : 'width',
  211. offset = (param === 'height') ? this.options.vOffset : this.options.hOffset,
  212. _this = this;
  213. if(($tipDims.width >= $tipDims.windowDims.width) || (!this.counter && !Foundation.Box.ImNotTouchingYou(this.template))){
  214. this.template.offset(Foundation.Box.GetOffsets(this.template, this.$element, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
  215. // this.$element.offset(Foundation.GetOffsets(this.template, this.$element, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
  216. 'width': $anchorDims.windowDims.width - (this.options.hOffset * 2),
  217. 'height': 'auto'
  218. });
  219. return false;
  220. }
  221. this.template.offset(Foundation.Box.GetOffsets(this.template, this.$element,'center ' + (position || 'bottom'), this.options.vOffset, this.options.hOffset));
  222. while(!Foundation.Box.ImNotTouchingYou(this.template) && this.counter){
  223. this._reposition(position);
  224. this._setPosition();
  225. }
  226. };
  227. /**
  228. * reveals the tooltip, and fires an event to close any other open tooltips on the page
  229. * @fires Tooltip#closeme
  230. * @fires Tooltip#show
  231. * @function
  232. */
  233. Tooltip.prototype.show = function(){
  234. if(this.options.showOn !== 'all' && !Foundation.MediaQuery.atLeast(this.options.showOn)){
  235. // console.error('The screen is too small to display this tooltip');
  236. return false;
  237. }
  238. var _this = this;
  239. this.template.css('visibility', 'hidden').show();
  240. this._setPosition();
  241. /**
  242. * Fires to close all other open tooltips on the page
  243. * @event Closeme#tooltip
  244. */
  245. this.$element.trigger('closeme.zf.tooltip', this.template.attr('id'));
  246. this.template.attr({
  247. 'data-is-active': true,
  248. 'aria-hidden': false
  249. });
  250. _this.isActive = true;
  251. // console.log(this.template);
  252. this.template.stop().hide().css('visibility', '').fadeIn(this.options.fadeInDuration, function(){
  253. //maybe do stuff?
  254. });
  255. /**
  256. * Fires when the tooltip is shown
  257. * @event Tooltip#show
  258. */
  259. this.$element.trigger('show.zf.tooltip');
  260. };
  261. /**
  262. * Hides the current tooltip, and resets the positioning class if it was changed due to collision
  263. * @fires Tooltip#hide
  264. * @function
  265. */
  266. Tooltip.prototype.hide = function(){
  267. // console.log('hiding', this.$element.data('yeti-box'));
  268. var _this = this;
  269. this.template.stop().attr({
  270. 'aria-hidden': true,
  271. 'data-is-active': false
  272. }).fadeOut(this.options.fadeOutDuration, function(){
  273. _this.isActive = false;
  274. _this.isClick = false;
  275. if(_this.classChanged){
  276. _this.template
  277. .removeClass(_this._getPositionClass(_this.template))
  278. .addClass(_this.options.positionClass);
  279. _this.usedPositions = [];
  280. _this.counter = 4;
  281. _this.classChanged = false;
  282. }
  283. });
  284. /**
  285. * fires when the tooltip is hidden
  286. * @event Tooltip#hide
  287. */
  288. this.$element.trigger('hide.zf.tooltip');
  289. };
  290. /**
  291. * adds event listeners for the tooltip and its anchor
  292. * TODO combine some of the listeners like focus and mouseenter, etc.
  293. * @private
  294. */
  295. Tooltip.prototype._events = function(){
  296. var _this = this;
  297. var $template = this.template;
  298. var isFocus = false;
  299. if(!this.options.disableHover){
  300. this.$element
  301. .on('mouseenter.zf.tooltip', function(e){
  302. if(!_this.isActive){
  303. _this.timeout = setTimeout(function(){
  304. _this.show();
  305. }, _this.options.hoverDelay);
  306. }
  307. })
  308. .on('mouseleave.zf.tooltip', function(e){
  309. clearTimeout(_this.timeout);
  310. if(!isFocus || (!_this.isClick && _this.options.clickOpen)){
  311. _this.hide();
  312. }
  313. });
  314. }
  315. if(this.options.clickOpen){
  316. this.$element.on('mousedown.zf.tooltip', function(e){
  317. e.stopImmediatePropagation();
  318. if(_this.isClick){
  319. _this.hide();
  320. // _this.isClick = false;
  321. }else{
  322. _this.isClick = true;
  323. if((_this.options.disableHover || !_this.$element.attr('tabindex')) && !_this.isActive){
  324. _this.show();
  325. }
  326. }
  327. });
  328. }
  329. if(!this.options.disableForTouch){
  330. this.$element
  331. .on('tap.zf.tooltip touchend.zf.tooltip', function(e){
  332. _this.isActive ? _this.hide() : _this.show();
  333. });
  334. }
  335. this.$element.on({
  336. // 'toggle.zf.trigger': this.toggle.bind(this),
  337. // 'close.zf.trigger': this.hide.bind(this)
  338. 'close.zf.trigger': this.hide.bind(this)
  339. });
  340. this.$element
  341. .on('focus.zf.tooltip', function(e){
  342. isFocus = true;
  343. // console.log(_this.isClick);
  344. if(_this.isClick){
  345. return false;
  346. }else{
  347. // $(window)
  348. _this.show();
  349. }
  350. })
  351. .on('focusout.zf.tooltip', function(e){
  352. isFocus = false;
  353. _this.isClick = false;
  354. _this.hide();
  355. })
  356. .on('resizeme.zf.trigger', function(){
  357. if(_this.isActive){
  358. _this._setPosition();
  359. }
  360. });
  361. };
  362. /**
  363. * adds a toggle method, in addition to the static show() & hide() functions
  364. * @function
  365. */
  366. Tooltip.prototype.toggle = function(){
  367. if(this.isActive){
  368. this.hide();
  369. }else{
  370. this.show();
  371. }
  372. };
  373. /**
  374. * Destroys an instance of tooltip, removes template element from the view.
  375. * @function
  376. */
  377. Tooltip.prototype.destroy = function(){
  378. this.$element.attr('title', this.template.text())
  379. .off('.zf.trigger .zf.tootip')
  380. // .removeClass('has-tip')
  381. .removeAttr('aria-describedby')
  382. .removeAttr('data-yeti-box')
  383. .removeAttr('data-toggle')
  384. .removeAttr('data-resize');
  385. this.template.remove();
  386. Foundation.unregisterPlugin(this);
  387. };
  388. /**
  389. * TODO utilize resize event trigger
  390. */
  391. Foundation.plugin(Tooltip, 'Tooltip');
  392. }(jQuery, window.document, window.Foundation);