tooltip.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. /**
  2. * angular-strap
  3. * @version v2.3.5 - 2015-10-29
  4. * @link http://mgcrea.github.io/angular-strap
  5. * @author Olivier Louvignes <olivier@mg-crea.com> (https://github.com/mgcrea)
  6. * @license MIT License, http://www.opensource.org/licenses/MIT
  7. */
  8. 'use strict';
  9. angular.module('mgcrea.ngStrap.tooltip', [ 'mgcrea.ngStrap.core', 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$tooltip', function() {
  10. var defaults = this.defaults = {
  11. animation: 'am-fade',
  12. customClass: '',
  13. prefixClass: 'tooltip',
  14. prefixEvent: 'tooltip',
  15. container: false,
  16. target: false,
  17. placement: 'top',
  18. templateUrl: 'tooltip/tooltip.tpl.html',
  19. template: '',
  20. contentTemplate: false,
  21. trigger: 'hover focus',
  22. keyboard: false,
  23. html: false,
  24. show: false,
  25. title: '',
  26. type: '',
  27. delay: 0,
  28. autoClose: false,
  29. bsEnabled: true,
  30. viewport: {
  31. selector: 'body',
  32. padding: 0
  33. }
  34. };
  35. this.$get = [ '$window', '$rootScope', '$bsCompiler', '$q', '$templateCache', '$http', '$animate', '$sce', 'dimensions', '$$rAF', '$timeout', function($window, $rootScope, $bsCompiler, $q, $templateCache, $http, $animate, $sce, dimensions, $$rAF, $timeout) {
  36. var trim = String.prototype.trim;
  37. var isTouch = 'createTouch' in $window.document;
  38. var htmlReplaceRegExp = /ng-bind="/gi;
  39. var $body = angular.element($window.document);
  40. function TooltipFactory(element, config) {
  41. var $tooltip = {};
  42. var options = $tooltip.$options = angular.extend({}, defaults, config);
  43. var promise = $tooltip.$promise = $bsCompiler.compile(options);
  44. var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new();
  45. var nodeName = element[0].nodeName.toLowerCase();
  46. if (options.delay && angular.isString(options.delay)) {
  47. var split = options.delay.split(',').map(parseFloat);
  48. options.delay = split.length > 1 ? {
  49. show: split[0],
  50. hide: split[1]
  51. } : split[0];
  52. }
  53. $tooltip.$id = options.id || element.attr('id') || '';
  54. if (options.title) {
  55. scope.title = $sce.trustAsHtml(options.title);
  56. }
  57. scope.$setEnabled = function(isEnabled) {
  58. scope.$$postDigest(function() {
  59. $tooltip.setEnabled(isEnabled);
  60. });
  61. };
  62. scope.$hide = function() {
  63. scope.$$postDigest(function() {
  64. $tooltip.hide();
  65. });
  66. };
  67. scope.$show = function() {
  68. scope.$$postDigest(function() {
  69. $tooltip.show();
  70. });
  71. };
  72. scope.$toggle = function() {
  73. scope.$$postDigest(function() {
  74. $tooltip.toggle();
  75. });
  76. };
  77. $tooltip.$isShown = scope.$isShown = false;
  78. var timeout, hoverState;
  79. var compileData, tipElement, tipContainer, tipScope;
  80. promise.then(function(data) {
  81. compileData = data;
  82. $tooltip.init();
  83. });
  84. $tooltip.init = function() {
  85. if (options.delay && angular.isNumber(options.delay)) {
  86. options.delay = {
  87. show: options.delay,
  88. hide: options.delay
  89. };
  90. }
  91. if (options.container === 'self') {
  92. tipContainer = element;
  93. } else if (angular.isElement(options.container)) {
  94. tipContainer = options.container;
  95. } else if (options.container) {
  96. tipContainer = findElement(options.container);
  97. }
  98. bindTriggerEvents();
  99. if (options.target) {
  100. options.target = angular.isElement(options.target) ? options.target : findElement(options.target);
  101. }
  102. if (options.show) {
  103. scope.$$postDigest(function() {
  104. options.trigger === 'focus' ? element[0].focus() : $tooltip.show();
  105. });
  106. }
  107. };
  108. $tooltip.destroy = function() {
  109. unbindTriggerEvents();
  110. destroyTipElement();
  111. scope.$destroy();
  112. };
  113. $tooltip.enter = function() {
  114. clearTimeout(timeout);
  115. hoverState = 'in';
  116. if (!options.delay || !options.delay.show) {
  117. return $tooltip.show();
  118. }
  119. timeout = setTimeout(function() {
  120. if (hoverState === 'in') $tooltip.show();
  121. }, options.delay.show);
  122. };
  123. $tooltip.show = function() {
  124. if (!options.bsEnabled || $tooltip.$isShown) return;
  125. scope.$emit(options.prefixEvent + '.show.before', $tooltip);
  126. var parent, after;
  127. if (options.container) {
  128. parent = tipContainer;
  129. if (tipContainer[0].lastChild) {
  130. after = angular.element(tipContainer[0].lastChild);
  131. } else {
  132. after = null;
  133. }
  134. } else {
  135. parent = null;
  136. after = element;
  137. }
  138. if (tipElement) destroyTipElement();
  139. tipScope = $tooltip.$scope.$new();
  140. tipElement = $tooltip.$element = compileData.link(tipScope, function(clonedElement, scope) {});
  141. tipElement.css({
  142. top: '-9999px',
  143. left: '-9999px',
  144. right: 'auto',
  145. display: 'block',
  146. visibility: 'hidden'
  147. });
  148. if (options.animation) tipElement.addClass(options.animation);
  149. if (options.type) tipElement.addClass(options.prefixClass + '-' + options.type);
  150. if (options.customClass) tipElement.addClass(options.customClass);
  151. after ? after.after(tipElement) : parent.prepend(tipElement);
  152. $tooltip.$isShown = scope.$isShown = true;
  153. safeDigest(scope);
  154. $tooltip.$applyPlacement();
  155. if (angular.version.minor <= 2) {
  156. $animate.enter(tipElement, parent, after, enterAnimateCallback);
  157. } else {
  158. $animate.enter(tipElement, parent, after).then(enterAnimateCallback);
  159. }
  160. safeDigest(scope);
  161. $$rAF(function() {
  162. if (tipElement) tipElement.css({
  163. visibility: 'visible'
  164. });
  165. if (options.keyboard) {
  166. if (options.trigger !== 'focus') {
  167. $tooltip.focus();
  168. }
  169. bindKeyboardEvents();
  170. }
  171. });
  172. if (options.autoClose) {
  173. bindAutoCloseEvents();
  174. }
  175. };
  176. function enterAnimateCallback() {
  177. scope.$emit(options.prefixEvent + '.show', $tooltip);
  178. }
  179. $tooltip.leave = function() {
  180. clearTimeout(timeout);
  181. hoverState = 'out';
  182. if (!options.delay || !options.delay.hide) {
  183. return $tooltip.hide();
  184. }
  185. timeout = setTimeout(function() {
  186. if (hoverState === 'out') {
  187. $tooltip.hide();
  188. }
  189. }, options.delay.hide);
  190. };
  191. var _blur;
  192. var _tipToHide;
  193. $tooltip.hide = function(blur) {
  194. if (!$tooltip.$isShown) return;
  195. scope.$emit(options.prefixEvent + '.hide.before', $tooltip);
  196. _blur = blur;
  197. _tipToHide = tipElement;
  198. if (angular.version.minor <= 2) {
  199. $animate.leave(tipElement, leaveAnimateCallback);
  200. } else {
  201. $animate.leave(tipElement).then(leaveAnimateCallback);
  202. }
  203. $tooltip.$isShown = scope.$isShown = false;
  204. safeDigest(scope);
  205. if (options.keyboard && tipElement !== null) {
  206. unbindKeyboardEvents();
  207. }
  208. if (options.autoClose && tipElement !== null) {
  209. unbindAutoCloseEvents();
  210. }
  211. };
  212. function leaveAnimateCallback() {
  213. scope.$emit(options.prefixEvent + '.hide', $tooltip);
  214. if (tipElement === _tipToHide) {
  215. if (_blur && options.trigger === 'focus') {
  216. return element[0].blur();
  217. }
  218. destroyTipElement();
  219. }
  220. }
  221. $tooltip.toggle = function() {
  222. $tooltip.$isShown ? $tooltip.leave() : $tooltip.enter();
  223. };
  224. $tooltip.focus = function() {
  225. tipElement[0].focus();
  226. };
  227. $tooltip.setEnabled = function(isEnabled) {
  228. options.bsEnabled = isEnabled;
  229. };
  230. $tooltip.setViewport = function(viewport) {
  231. options.viewport = viewport;
  232. };
  233. $tooltip.$applyPlacement = function() {
  234. if (!tipElement) return;
  235. var placement = options.placement, autoToken = /\s?auto?\s?/i, autoPlace = autoToken.test(placement);
  236. if (autoPlace) {
  237. placement = placement.replace(autoToken, '') || defaults.placement;
  238. }
  239. tipElement.addClass(options.placement);
  240. var elementPosition = getPosition(), tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight');
  241. $tooltip.$viewport = options.viewport && findElement(options.viewport.selector || options.viewport);
  242. if (autoPlace) {
  243. var originalPlacement = placement;
  244. var viewportPosition = getPosition($tooltip.$viewport);
  245. if (originalPlacement.indexOf('bottom') >= 0 && elementPosition.bottom + tipHeight > viewportPosition.bottom) {
  246. placement = originalPlacement.replace('bottom', 'top');
  247. } else if (originalPlacement.indexOf('top') >= 0 && elementPosition.top - tipHeight < viewportPosition.top) {
  248. placement = originalPlacement.replace('top', 'bottom');
  249. }
  250. if ((originalPlacement === 'right' || originalPlacement === 'bottom-left' || originalPlacement === 'top-left') && elementPosition.right + tipWidth > viewportPosition.width) {
  251. placement = originalPlacement === 'right' ? 'left' : placement.replace('left', 'right');
  252. } else if ((originalPlacement === 'left' || originalPlacement === 'bottom-right' || originalPlacement === 'top-right') && elementPosition.left - tipWidth < viewportPosition.left) {
  253. placement = originalPlacement === 'left' ? 'right' : placement.replace('right', 'left');
  254. }
  255. tipElement.removeClass(originalPlacement).addClass(placement);
  256. }
  257. var tipPosition = getCalculatedOffset(placement, elementPosition, tipWidth, tipHeight);
  258. applyPlacement(tipPosition, placement);
  259. };
  260. $tooltip.$onKeyUp = function(evt) {
  261. if (evt.which === 27 && $tooltip.$isShown) {
  262. $tooltip.hide();
  263. evt.stopPropagation();
  264. }
  265. };
  266. $tooltip.$onFocusKeyUp = function(evt) {
  267. if (evt.which === 27) {
  268. element[0].blur();
  269. evt.stopPropagation();
  270. }
  271. };
  272. $tooltip.$onFocusElementMouseDown = function(evt) {
  273. evt.preventDefault();
  274. evt.stopPropagation();
  275. $tooltip.$isShown ? element[0].blur() : element[0].focus();
  276. };
  277. function bindTriggerEvents() {
  278. var triggers = options.trigger.split(' ');
  279. angular.forEach(triggers, function(trigger) {
  280. if (trigger === 'click') {
  281. element.on('click', $tooltip.toggle);
  282. } else if (trigger !== 'manual') {
  283. element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
  284. element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
  285. nodeName === 'button' && trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
  286. }
  287. });
  288. }
  289. function unbindTriggerEvents() {
  290. var triggers = options.trigger.split(' ');
  291. for (var i = triggers.length; i--; ) {
  292. var trigger = triggers[i];
  293. if (trigger === 'click') {
  294. element.off('click', $tooltip.toggle);
  295. } else if (trigger !== 'manual') {
  296. element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
  297. element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
  298. nodeName === 'button' && trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
  299. }
  300. }
  301. }
  302. function bindKeyboardEvents() {
  303. if (options.trigger !== 'focus') {
  304. tipElement.on('keyup', $tooltip.$onKeyUp);
  305. } else {
  306. element.on('keyup', $tooltip.$onFocusKeyUp);
  307. }
  308. }
  309. function unbindKeyboardEvents() {
  310. if (options.trigger !== 'focus') {
  311. tipElement.off('keyup', $tooltip.$onKeyUp);
  312. } else {
  313. element.off('keyup', $tooltip.$onFocusKeyUp);
  314. }
  315. }
  316. var _autoCloseEventsBinded = false;
  317. function bindAutoCloseEvents() {
  318. $timeout(function() {
  319. tipElement.on('click', stopEventPropagation);
  320. $body.on('click', $tooltip.hide);
  321. _autoCloseEventsBinded = true;
  322. }, 0, false);
  323. }
  324. function unbindAutoCloseEvents() {
  325. if (_autoCloseEventsBinded) {
  326. tipElement.off('click', stopEventPropagation);
  327. $body.off('click', $tooltip.hide);
  328. _autoCloseEventsBinded = false;
  329. }
  330. }
  331. function stopEventPropagation(event) {
  332. event.stopPropagation();
  333. }
  334. function getPosition($element) {
  335. $element = $element || (options.target || element);
  336. var el = $element[0], isBody = el.tagName === 'BODY';
  337. var elRect = el.getBoundingClientRect();
  338. var rect = {};
  339. for (var p in elRect) {
  340. rect[p] = elRect[p];
  341. }
  342. if (rect.width === null) {
  343. rect = angular.extend({}, rect, {
  344. width: elRect.right - elRect.left,
  345. height: elRect.bottom - elRect.top
  346. });
  347. }
  348. var elOffset = isBody ? {
  349. top: 0,
  350. left: 0
  351. } : dimensions.offset(el), scroll = {
  352. scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.prop('scrollTop') || 0
  353. }, outerDims = isBody ? {
  354. width: document.documentElement.clientWidth,
  355. height: $window.innerHeight
  356. } : null;
  357. return angular.extend({}, rect, scroll, outerDims, elOffset);
  358. }
  359. function getCalculatedOffset(placement, position, actualWidth, actualHeight) {
  360. var offset;
  361. var split = placement.split('-');
  362. switch (split[0]) {
  363. case 'right':
  364. offset = {
  365. top: position.top + position.height / 2 - actualHeight / 2,
  366. left: position.left + position.width
  367. };
  368. break;
  369. case 'bottom':
  370. offset = {
  371. top: position.top + position.height,
  372. left: position.left + position.width / 2 - actualWidth / 2
  373. };
  374. break;
  375. case 'left':
  376. offset = {
  377. top: position.top + position.height / 2 - actualHeight / 2,
  378. left: position.left - actualWidth
  379. };
  380. break;
  381. default:
  382. offset = {
  383. top: position.top - actualHeight,
  384. left: position.left + position.width / 2 - actualWidth / 2
  385. };
  386. break;
  387. }
  388. if (!split[1]) {
  389. return offset;
  390. }
  391. if (split[0] === 'top' || split[0] === 'bottom') {
  392. switch (split[1]) {
  393. case 'left':
  394. offset.left = position.left;
  395. break;
  396. case 'right':
  397. offset.left = position.left + position.width - actualWidth;
  398. }
  399. } else if (split[0] === 'left' || split[0] === 'right') {
  400. switch (split[1]) {
  401. case 'top':
  402. offset.top = position.top - actualHeight;
  403. break;
  404. case 'bottom':
  405. offset.top = position.top + position.height;
  406. }
  407. }
  408. return offset;
  409. }
  410. function applyPlacement(offset, placement) {
  411. var tip = tipElement[0], width = tip.offsetWidth, height = tip.offsetHeight;
  412. var marginTop = parseInt(dimensions.css(tip, 'margin-top'), 10), marginLeft = parseInt(dimensions.css(tip, 'margin-left'), 10);
  413. if (isNaN(marginTop)) marginTop = 0;
  414. if (isNaN(marginLeft)) marginLeft = 0;
  415. offset.top = offset.top + marginTop;
  416. offset.left = offset.left + marginLeft;
  417. dimensions.setOffset(tip, angular.extend({
  418. using: function(props) {
  419. tipElement.css({
  420. top: Math.round(props.top) + 'px',
  421. left: Math.round(props.left) + 'px',
  422. right: ''
  423. });
  424. }
  425. }, offset), 0);
  426. var actualWidth = tip.offsetWidth, actualHeight = tip.offsetHeight;
  427. if (placement === 'top' && actualHeight !== height) {
  428. offset.top = offset.top + height - actualHeight;
  429. }
  430. if (/top-left|top-right|bottom-left|bottom-right/.test(placement)) return;
  431. var delta = getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight);
  432. if (delta.left) {
  433. offset.left += delta.left;
  434. } else {
  435. offset.top += delta.top;
  436. }
  437. dimensions.setOffset(tip, offset);
  438. if (/top|right|bottom|left/.test(placement)) {
  439. var isVertical = /top|bottom/.test(placement), arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight, arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight';
  440. replaceArrow(arrowDelta, tip[arrowOffsetPosition], isVertical);
  441. }
  442. }
  443. function getViewportAdjustedDelta(placement, position, actualWidth, actualHeight) {
  444. var delta = {
  445. top: 0,
  446. left: 0
  447. };
  448. if (!$tooltip.$viewport) return delta;
  449. var viewportPadding = options.viewport && options.viewport.padding || 0;
  450. var viewportDimensions = getPosition($tooltip.$viewport);
  451. if (/right|left/.test(placement)) {
  452. var topEdgeOffset = position.top - viewportPadding - viewportDimensions.scroll;
  453. var bottomEdgeOffset = position.top + viewportPadding - viewportDimensions.scroll + actualHeight;
  454. if (topEdgeOffset < viewportDimensions.top) {
  455. delta.top = viewportDimensions.top - topEdgeOffset;
  456. } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) {
  457. delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset;
  458. }
  459. } else {
  460. var leftEdgeOffset = position.left - viewportPadding;
  461. var rightEdgeOffset = position.left + viewportPadding + actualWidth;
  462. if (leftEdgeOffset < viewportDimensions.left) {
  463. delta.left = viewportDimensions.left - leftEdgeOffset;
  464. } else if (rightEdgeOffset > viewportDimensions.right) {
  465. delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset;
  466. }
  467. }
  468. return delta;
  469. }
  470. function replaceArrow(delta, dimension, isHorizontal) {
  471. var $arrow = findElement('.tooltip-arrow, .arrow', tipElement[0]);
  472. $arrow.css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%').css(isHorizontal ? 'top' : 'left', '');
  473. }
  474. function destroyTipElement() {
  475. clearTimeout(timeout);
  476. if ($tooltip.$isShown && tipElement !== null) {
  477. if (options.autoClose) {
  478. unbindAutoCloseEvents();
  479. }
  480. if (options.keyboard) {
  481. unbindKeyboardEvents();
  482. }
  483. }
  484. if (tipScope) {
  485. tipScope.$destroy();
  486. tipScope = null;
  487. }
  488. if (tipElement) {
  489. tipElement.remove();
  490. tipElement = $tooltip.$element = null;
  491. }
  492. }
  493. return $tooltip;
  494. }
  495. function safeDigest(scope) {
  496. scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
  497. }
  498. function findElement(query, element) {
  499. return angular.element((element || document).querySelectorAll(query));
  500. }
  501. var fetchPromises = {};
  502. function fetchTemplate(template) {
  503. if (fetchPromises[template]) return fetchPromises[template];
  504. return fetchPromises[template] = $http.get(template, {
  505. cache: $templateCache
  506. }).then(function(res) {
  507. return res.data;
  508. });
  509. }
  510. return TooltipFactory;
  511. } ];
  512. }).directive('bsTooltip', [ '$window', '$location', '$sce', '$tooltip', '$$rAF', function($window, $location, $sce, $tooltip, $$rAF) {
  513. return {
  514. restrict: 'EAC',
  515. scope: true,
  516. link: function postLink(scope, element, attr, transclusion) {
  517. var options = {
  518. scope: scope
  519. };
  520. angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'backdropAnimation', 'type', 'customClass', 'id' ], function(key) {
  521. if (angular.isDefined(attr[key])) options[key] = attr[key];
  522. });
  523. var falseValueRegExp = /^(false|0|)$/i;
  524. angular.forEach([ 'html', 'container' ], function(key) {
  525. if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
  526. });
  527. var dataTarget = element.attr('data-target');
  528. if (angular.isDefined(dataTarget)) {
  529. if (falseValueRegExp.test(dataTarget)) options.target = false; else options.target = dataTarget;
  530. }
  531. if (!scope.hasOwnProperty('title')) {
  532. scope.title = '';
  533. }
  534. attr.$observe('title', function(newValue) {
  535. if (angular.isDefined(newValue) || !scope.hasOwnProperty('title')) {
  536. var oldValue = scope.title;
  537. scope.title = $sce.trustAsHtml(newValue);
  538. angular.isDefined(oldValue) && $$rAF(function() {
  539. tooltip && tooltip.$applyPlacement();
  540. });
  541. }
  542. });
  543. attr.bsTooltip && scope.$watch(attr.bsTooltip, function(newValue, oldValue) {
  544. if (angular.isObject(newValue)) {
  545. angular.extend(scope, newValue);
  546. } else {
  547. scope.title = newValue;
  548. }
  549. angular.isDefined(oldValue) && $$rAF(function() {
  550. tooltip && tooltip.$applyPlacement();
  551. });
  552. }, true);
  553. attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
  554. if (!tooltip || !angular.isDefined(newValue)) return;
  555. if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(tooltip),?/i);
  556. newValue === true ? tooltip.show() : tooltip.hide();
  557. });
  558. attr.bsEnabled && scope.$watch(attr.bsEnabled, function(newValue, oldValue) {
  559. if (!tooltip || !angular.isDefined(newValue)) return;
  560. if (angular.isString(newValue)) newValue = !!newValue.match(/true|1|,?(tooltip),?/i);
  561. newValue === false ? tooltip.setEnabled(false) : tooltip.setEnabled(true);
  562. });
  563. attr.viewport && scope.$watch(attr.viewport, function(newValue) {
  564. if (!tooltip || !angular.isDefined(newValue)) return;
  565. tooltip.setViewport(newValue);
  566. });
  567. var tooltip = $tooltip(element, options);
  568. scope.$on('$destroy', function() {
  569. if (tooltip) tooltip.destroy();
  570. options = null;
  571. tooltip = null;
  572. });
  573. }
  574. };
  575. } ]);