tooltip.js 24 KB

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