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

/lantern-ui/app/bower_components/ui-bootstrap/src/tooltip/tooltip.js

https://gitlab.com/zanderwong/lantern
JavaScript | 361 lines | 230 code | 54 blank | 77 comment | 26 complexity | 0db07a463fcb4c6cff47439d526817f3 MD5 | raw file
  1. /**
  2. * The following features are still outstanding: animation as a
  3. * function, placement as a function, inside, support for more triggers than
  4. * just mouse enter/leave, html tooltips, and selector delegation.
  5. */
  6. angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )
  7. /**
  8. * The $tooltip service creates tooltip- and popover-like directives as well as
  9. * houses global options for them.
  10. */
  11. .provider( '$tooltip', function () {
  12. // The default options tooltip and popover.
  13. var defaultOptions = {
  14. placement: 'top',
  15. animation: true,
  16. popupDelay: 0
  17. };
  18. // Default hide triggers for each show trigger
  19. var triggerMap = {
  20. 'mouseenter': 'mouseleave',
  21. 'click': 'click',
  22. 'focus': 'blur'
  23. };
  24. // The options specified to the provider globally.
  25. var globalOptions = {};
  26. /**
  27. * `options({})` allows global configuration of all tooltips in the
  28. * application.
  29. *
  30. * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
  31. * // place tooltips left instead of top by default
  32. * $tooltipProvider.options( { placement: 'left' } );
  33. * });
  34. */
  35. this.options = function( value ) {
  36. angular.extend( globalOptions, value );
  37. };
  38. /**
  39. * This allows you to extend the set of trigger mappings available. E.g.:
  40. *
  41. * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
  42. */
  43. this.setTriggers = function setTriggers ( triggers ) {
  44. angular.extend( triggerMap, triggers );
  45. };
  46. /**
  47. * This is a helper function for translating camel-case to snake-case.
  48. */
  49. function snake_case(name){
  50. var regexp = /[A-Z]/g;
  51. var separator = '-';
  52. return name.replace(regexp, function(letter, pos) {
  53. return (pos ? separator : '') + letter.toLowerCase();
  54. });
  55. }
  56. /**
  57. * Returns the actual instance of the $tooltip service.
  58. * TODO support multiple triggers
  59. */
  60. this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) {
  61. return function $tooltip ( type, prefix, defaultTriggerShow ) {
  62. var options = angular.extend( {}, defaultOptions, globalOptions );
  63. /**
  64. * Returns an object of show and hide triggers.
  65. *
  66. * If a trigger is supplied,
  67. * it is used to show the tooltip; otherwise, it will use the `trigger`
  68. * option passed to the `$tooltipProvider.options` method; else it will
  69. * default to the trigger supplied to this directive factory.
  70. *
  71. * The hide trigger is based on the show trigger. If the `trigger` option
  72. * was passed to the `$tooltipProvider.options` method, it will use the
  73. * mapped trigger from `triggerMap` or the passed trigger if the map is
  74. * undefined; otherwise, it uses the `triggerMap` value of the show
  75. * trigger; else it will just use the show trigger.
  76. */
  77. function getTriggers ( trigger ) {
  78. var show = trigger || options.trigger || defaultTriggerShow;
  79. var hide = triggerMap[show] || show;
  80. return {
  81. show: show,
  82. hide: hide
  83. };
  84. }
  85. var directiveName = snake_case( type );
  86. var startSym = $interpolate.startSymbol();
  87. var endSym = $interpolate.endSymbol();
  88. var template =
  89. '<div '+ directiveName +'-popup '+
  90. 'title="'+startSym+'title'+endSym+'" '+
  91. 'content="'+startSym+'content'+endSym+'" '+
  92. 'placement="'+startSym+'placement'+endSym+'" '+
  93. 'animation="animation" '+
  94. 'is-open="isOpen"'+
  95. '>'+
  96. '</div>';
  97. return {
  98. restrict: 'EA',
  99. compile: function (tElem, tAttrs) {
  100. var tooltipLinker = $compile( template );
  101. return function link ( scope, element, attrs ) {
  102. var tooltip;
  103. var tooltipLinkedScope;
  104. var transitionTimeout;
  105. var popupTimeout;
  106. var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
  107. var triggers = getTriggers( undefined );
  108. var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
  109. var ttScope = scope.$new(true);
  110. var positionTooltip = function () {
  111. var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
  112. ttPosition.top += 'px';
  113. ttPosition.left += 'px';
  114. // Now set the calculated positioning.
  115. tooltip.css( ttPosition );
  116. };
  117. // By default, the tooltip is not open.
  118. // TODO add ability to start tooltip opened
  119. ttScope.isOpen = false;
  120. function toggleTooltipBind () {
  121. if ( ! ttScope.isOpen ) {
  122. showTooltipBind();
  123. } else {
  124. hideTooltipBind();
  125. }
  126. }
  127. // Show the tooltip with delay if specified, otherwise show it immediately
  128. function showTooltipBind() {
  129. if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
  130. return;
  131. }
  132. prepareTooltip();
  133. if ( ttScope.popupDelay ) {
  134. // Do nothing if the tooltip was already scheduled to pop-up.
  135. // This happens if show is triggered multiple times before any hide is triggered.
  136. if (!popupTimeout) {
  137. popupTimeout = $timeout( show, ttScope.popupDelay, false );
  138. popupTimeout.then(function(reposition){reposition();});
  139. }
  140. } else {
  141. show()();
  142. }
  143. }
  144. function hideTooltipBind () {
  145. scope.$apply(function () {
  146. hide();
  147. });
  148. }
  149. // Show the tooltip popup element.
  150. function show() {
  151. popupTimeout = null;
  152. // If there is a pending remove transition, we must cancel it, lest the
  153. // tooltip be mysteriously removed.
  154. if ( transitionTimeout ) {
  155. $timeout.cancel( transitionTimeout );
  156. transitionTimeout = null;
  157. }
  158. // Don't show empty tooltips.
  159. if ( ! ttScope.content ) {
  160. return angular.noop;
  161. }
  162. createTooltip();
  163. // Set the initial positioning.
  164. tooltip.css({ top: 0, left: 0, display: 'block' });
  165. // Now we add it to the DOM because need some info about it. But it's not
  166. // visible yet anyway.
  167. if ( appendToBody ) {
  168. $document.find( 'body' ).append( tooltip );
  169. } else {
  170. element.after( tooltip );
  171. }
  172. positionTooltip();
  173. // And show the tooltip.
  174. ttScope.isOpen = true;
  175. ttScope.$digest(); // digest required as $apply is not called
  176. // Return positioning function as promise callback for correct
  177. // positioning after draw.
  178. return positionTooltip;
  179. }
  180. // Hide the tooltip popup element.
  181. function hide() {
  182. // First things first: we don't show it anymore.
  183. ttScope.isOpen = false;
  184. //if tooltip is going to be shown after delay, we must cancel this
  185. $timeout.cancel( popupTimeout );
  186. popupTimeout = null;
  187. // And now we remove it from the DOM. However, if we have animation, we
  188. // need to wait for it to expire beforehand.
  189. // FIXME: this is a placeholder for a port of the transitions library.
  190. if ( ttScope.animation ) {
  191. if (!transitionTimeout) {
  192. transitionTimeout = $timeout(removeTooltip, 500);
  193. }
  194. } else {
  195. removeTooltip();
  196. }
  197. }
  198. function createTooltip() {
  199. // There can only be one tooltip element per directive shown at once.
  200. if (tooltip) {
  201. removeTooltip();
  202. }
  203. tooltipLinkedScope = ttScope.$new();
  204. tooltip = tooltipLinker(tooltipLinkedScope, angular.noop);
  205. }
  206. function removeTooltip() {
  207. transitionTimeout = null;
  208. if (tooltip) {
  209. tooltip.remove();
  210. tooltip = null;
  211. }
  212. if (tooltipLinkedScope) {
  213. tooltipLinkedScope.$destroy();
  214. tooltipLinkedScope = null;
  215. }
  216. }
  217. function prepareTooltip() {
  218. prepPlacement();
  219. prepPopupDelay();
  220. }
  221. /**
  222. * Observe the relevant attributes.
  223. */
  224. attrs.$observe( type, function ( val ) {
  225. ttScope.content = val;
  226. if (!val && ttScope.isOpen ) {
  227. hide();
  228. }
  229. });
  230. attrs.$observe( prefix+'Title', function ( val ) {
  231. ttScope.title = val;
  232. });
  233. function prepPlacement() {
  234. var val = attrs[ prefix + 'Placement' ];
  235. ttScope.placement = angular.isDefined( val ) ? val : options.placement;
  236. }
  237. function prepPopupDelay() {
  238. var val = attrs[ prefix + 'PopupDelay' ];
  239. var delay = parseInt( val, 10 );
  240. ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
  241. }
  242. var unregisterTriggers = function () {
  243. element.unbind(triggers.show, showTooltipBind);
  244. element.unbind(triggers.hide, hideTooltipBind);
  245. };
  246. function prepTriggers() {
  247. var val = attrs[ prefix + 'Trigger' ];
  248. unregisterTriggers();
  249. triggers = getTriggers( val );
  250. if ( triggers.show === triggers.hide ) {
  251. element.bind( triggers.show, toggleTooltipBind );
  252. } else {
  253. element.bind( triggers.show, showTooltipBind );
  254. element.bind( triggers.hide, hideTooltipBind );
  255. }
  256. }
  257. prepTriggers();
  258. var animation = scope.$eval(attrs[prefix + 'Animation']);
  259. ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
  260. var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']);
  261. appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
  262. // if a tooltip is attached to <body> we need to remove it on
  263. // location change as its parent scope will probably not be destroyed
  264. // by the change.
  265. if ( appendToBody ) {
  266. scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
  267. if ( ttScope.isOpen ) {
  268. hide();
  269. }
  270. });
  271. }
  272. // Make sure tooltip is destroyed and removed.
  273. scope.$on('$destroy', function onDestroyTooltip() {
  274. $timeout.cancel( transitionTimeout );
  275. $timeout.cancel( popupTimeout );
  276. unregisterTriggers();
  277. removeTooltip();
  278. ttScope = null;
  279. });
  280. };
  281. }
  282. };
  283. };
  284. }];
  285. })
  286. .directive( 'tooltipPopup', function () {
  287. return {
  288. restrict: 'EA',
  289. replace: true,
  290. scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
  291. templateUrl: 'template/tooltip/tooltip-popup.html'
  292. };
  293. })
  294. .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
  295. return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
  296. }])
  297. .directive( 'tooltipHtmlUnsafePopup', function () {
  298. return {
  299. restrict: 'EA',
  300. replace: true,
  301. scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
  302. templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
  303. };
  304. })
  305. .directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
  306. return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
  307. }]);