Razique Mahroua
2019-11-28 1759c24ad2d2b35ec5c756e3dd3a60185fe944b7
commit | author | age
1759c2 1 /**
RM 2  * angular-strap
3  * @version v2.0.3 - 2014-05-30
4  * @link http://mgcrea.github.io/angular-strap
5  * @author Olivier Louvignes (olivier@mg-crea.com)
6  * @license MIT License, http://www.opensource.org/licenses/MIT
7  */
8 'use strict';
9 angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions']).provider('$tooltip', function () {
10   var defaults = this.defaults = {
11       animation: 'am-fade',
12       prefixClass: 'tooltip',
13       prefixEvent: 'tooltip',
14       container: false,
15       target: false,
16       placement: 'top',
17       template: 'tooltip/tooltip.tpl.html',
18       contentTemplate: false,
19       trigger: 'hover focus',
20       keyboard: false,
21       html: false,
22       show: false,
23       title: '',
24       type: '',
25       delay: 0
26     };
27   this.$get = [
28     '$window',
29     '$rootScope',
30     '$compile',
31     '$q',
32     '$templateCache',
33     '$http',
34     '$animate',
35     'dimensions',
36     '$$rAF',
37     function ($window, $rootScope, $compile, $q, $templateCache, $http, $animate, dimensions, $$rAF) {
38       var trim = String.prototype.trim;
39       var isTouch = 'createTouch' in $window.document;
40       var htmlReplaceRegExp = /ng-bind="/gi;
41       function TooltipFactory(element, config) {
42         var $tooltip = {};
43         // Common vars
44         var nodeName = element[0].nodeName.toLowerCase();
45         var options = $tooltip.$options = angular.extend({}, defaults, config);
46         $tooltip.$promise = fetchTemplate(options.template);
47         var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new();
48         if (options.delay && angular.isString(options.delay)) {
49           options.delay = parseFloat(options.delay);
50         }
51         // Support scope as string options
52         if (options.title) {
53           $tooltip.$scope.title = options.title;
54         }
55         // Provide scope helpers
56         scope.$hide = function () {
57           scope.$$postDigest(function () {
58             $tooltip.hide();
59           });
60         };
61         scope.$show = function () {
62           scope.$$postDigest(function () {
63             $tooltip.show();
64           });
65         };
66         scope.$toggle = function () {
67           scope.$$postDigest(function () {
68             $tooltip.toggle();
69           });
70         };
71         $tooltip.$isShown = scope.$isShown = false;
72         // Private vars
73         var timeout, hoverState;
74         // Support contentTemplate option
75         if (options.contentTemplate) {
76           $tooltip.$promise = $tooltip.$promise.then(function (template) {
77             var templateEl = angular.element(template);
78             return fetchTemplate(options.contentTemplate).then(function (contentTemplate) {
79               var contentEl = findElement('[ng-bind="content"]', templateEl[0]);
80               if (!contentEl.length)
81                 contentEl = findElement('[ng-bind="title"]', templateEl[0]);
82               contentEl.removeAttr('ng-bind').html(contentTemplate);
83               return templateEl[0].outerHTML;
84             });
85           });
86         }
87         // Fetch, compile then initialize tooltip
88         var tipLinker, tipElement, tipTemplate, tipContainer;
89         $tooltip.$promise.then(function (template) {
90           if (angular.isObject(template))
91             template = template.data;
92           if (options.html)
93             template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
94           template = trim.apply(template);
95           tipTemplate = template;
96           tipLinker = $compile(template);
97           $tooltip.init();
98         });
99         $tooltip.init = function () {
100           // Options: delay
101           if (options.delay && angular.isNumber(options.delay)) {
102             options.delay = {
103               show: options.delay,
104               hide: options.delay
105             };
106           }
107           // Replace trigger on touch devices ?
108           // if(isTouch && options.trigger === defaults.trigger) {
109           //   options.trigger.replace(/hover/g, 'click');
110           // }
111           // Options : container
112           if (options.container === 'self') {
113             tipContainer = element;
114           } else if (options.container) {
115             tipContainer = findElement(options.container);
116           }
117           // Options: trigger
118           var triggers = options.trigger.split(' ');
119           angular.forEach(triggers, function (trigger) {
120             if (trigger === 'click') {
121               element.on('click', $tooltip.toggle);
122             } else if (trigger !== 'manual') {
123               element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
124               element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
125               nodeName === 'button' && trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
126             }
127           });
128           // Options: target
129           if (options.target) {
130             options.target = angular.isElement(options.target) ? options.target : findElement(options.target)[0];
131           }
132           // Options: show
133           if (options.show) {
134             scope.$$postDigest(function () {
135               options.trigger === 'focus' ? element[0].focus() : $tooltip.show();
136             });
137           }
138         };
139         $tooltip.destroy = function () {
140           // Unbind events
141           var triggers = options.trigger.split(' ');
142           for (var i = triggers.length; i--;) {
143             var trigger = triggers[i];
144             if (trigger === 'click') {
145               element.off('click', $tooltip.toggle);
146             } else if (trigger !== 'manual') {
147               element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
148               element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
149               nodeName === 'button' && trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
150             }
151           }
152           // Remove element
153           if (tipElement) {
154             tipElement.remove();
155             tipElement = null;
156           }
157           // Cancel pending callbacks
158           clearTimeout(timeout);
159           // Destroy scope
160           scope.$destroy();
161         };
162         $tooltip.enter = function () {
163           clearTimeout(timeout);
164           hoverState = 'in';
165           if (!options.delay || !options.delay.show) {
166             return $tooltip.show();
167           }
168           timeout = setTimeout(function () {
169             if (hoverState === 'in')
170               $tooltip.show();
171           }, options.delay.show);
172         };
173         $tooltip.show = function () {
174           scope.$emit(options.prefixEvent + '.show.before', $tooltip);
175           var parent = options.container ? tipContainer : null;
176           var after = options.container ? null : element;
177           // Hide any existing tipElement
178           if (tipElement)
179             tipElement.remove();
180           // Fetch a cloned element linked from template
181           tipElement = $tooltip.$element = tipLinker(scope, function (clonedElement, scope) {
182           });
183           // Set the initial positioning.
184           tipElement.css({
185             top: '-9999px',
186             left: '-9999px',
187             display: 'block'
188           }).addClass(options.placement);
189           // Options: animation
190           if (options.animation)
191             tipElement.addClass(options.animation);
192           // Options: type
193           if (options.type)
194             tipElement.addClass(options.prefixClass + '-' + options.type);
195           $animate.enter(tipElement, parent, after, function () {
196             scope.$emit(options.prefixEvent + '.show', $tooltip);
197           });
198           $tooltip.$isShown = scope.$isShown = true;
199           scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
200           $$rAF($tooltip.$applyPlacement);
201           // var a = bodyEl.offsetWidth + 1; ?
202           // Bind events
203           if (options.keyboard) {
204             if (options.trigger !== 'focus') {
205               $tooltip.focus();
206               tipElement.on('keyup', $tooltip.$onKeyUp);
207             } else {
208               element.on('keyup', $tooltip.$onFocusKeyUp);
209             }
210           }
211         };
212         $tooltip.leave = function () {
213           clearTimeout(timeout);
214           hoverState = 'out';
215           if (!options.delay || !options.delay.hide) {
216             return $tooltip.hide();
217           }
218           timeout = setTimeout(function () {
219             if (hoverState === 'out') {
220               $tooltip.hide();
221             }
222           }, options.delay.hide);
223         };
224         $tooltip.hide = function (blur) {
225           if (!$tooltip.$isShown)
226             return;
227           scope.$emit(options.prefixEvent + '.hide.before', $tooltip);
228           $animate.leave(tipElement, function () {
229             scope.$emit(options.prefixEvent + '.hide', $tooltip);
230           });
231           $tooltip.$isShown = scope.$isShown = false;
232           scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
233           // Unbind events
234           if (options.keyboard && tipElement !== null) {
235             tipElement.off('keyup', $tooltip.$onKeyUp);
236           }
237           // Allow to blur the input when hidden, like when pressing enter key
238           if (blur && options.trigger === 'focus') {
239             return element[0].blur();
240           }
241         };
242         $tooltip.toggle = function () {
243           $tooltip.$isShown ? $tooltip.leave() : $tooltip.enter();
244         };
245         $tooltip.focus = function () {
246           tipElement[0].focus();
247         };
248         // Protected methods
249         $tooltip.$applyPlacement = function () {
250           if (!tipElement)
251             return;
252           // Get the position of the tooltip element.
253           var elementPosition = getPosition();
254           // Get the height and width of the tooltip so we can center it.
255           var tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight');
256           // Get the tooltip's top and left coordinates to center it with this directive.
257           var tipPosition = getCalculatedOffset(options.placement, elementPosition, tipWidth, tipHeight);
258           // Now set the calculated positioning.
259           tipPosition.top += 'px';
260           tipPosition.left += 'px';
261           tipElement.css(tipPosition);
262         };
263         $tooltip.$onKeyUp = function (evt) {
264           evt.which === 27 && $tooltip.hide();
265         };
266         $tooltip.$onFocusKeyUp = function (evt) {
267           evt.which === 27 && element[0].blur();
268         };
269         $tooltip.$onFocusElementMouseDown = function (evt) {
270           evt.preventDefault();
271           evt.stopPropagation();
272           // Some browsers do not auto-focus buttons (eg. Safari)
273           $tooltip.$isShown ? element[0].blur() : element[0].focus();
274         };
275         // Private methods
276         function getPosition() {
277           if (options.container === 'body') {
278             return dimensions.offset(options.target || element[0]);
279           } else {
280             return dimensions.position(options.target || element[0]);
281           }
282         }
283         function getCalculatedOffset(placement, position, actualWidth, actualHeight) {
284           var offset;
285           var split = placement.split('-');
286           switch (split[0]) {
287           case 'right':
288             offset = {
289               top: position.top + position.height / 2 - actualHeight / 2,
290               left: position.left + position.width
291             };
292             break;
293           case 'bottom':
294             offset = {
295               top: position.top + position.height,
296               left: position.left + position.width / 2 - actualWidth / 2
297             };
298             break;
299           case 'left':
300             offset = {
301               top: position.top + position.height / 2 - actualHeight / 2,
302               left: position.left - actualWidth
303             };
304             break;
305           default:
306             offset = {
307               top: position.top - actualHeight,
308               left: position.left + position.width / 2 - actualWidth / 2
309             };
310             break;
311           }
312           if (!split[1]) {
313             return offset;
314           }
315           // Add support for corners @todo css
316           if (split[0] === 'top' || split[0] === 'bottom') {
317             switch (split[1]) {
318             case 'left':
319               offset.left = position.left;
320               break;
321             case 'right':
322               offset.left = position.left + position.width - actualWidth;
323             }
324           } else if (split[0] === 'left' || split[0] === 'right') {
325             switch (split[1]) {
326             case 'top':
327               offset.top = position.top - actualHeight;
328               break;
329             case 'bottom':
330               offset.top = position.top + position.height;
331             }
332           }
333           return offset;
334         }
335         return $tooltip;
336       }
337       // Helper functions
338       function findElement(query, element) {
339         return angular.element((element || document).querySelectorAll(query));
340       }
341       function fetchTemplate(template) {
342         return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) {
343           if (angular.isObject(res)) {
344             $templateCache.put(template, res.data);
345             return res.data;
346           }
347           return res;
348         });
349       }
350       return TooltipFactory;
351     }
352   ];
353 }).directive('bsTooltip', [
354   '$window',
355   '$location',
356   '$sce',
357   '$tooltip',
358   '$$rAF',
359   function ($window, $location, $sce, $tooltip, $$rAF) {
360     return {
361       restrict: 'EAC',
362       scope: true,
363       link: function postLink(scope, element, attr, transclusion) {
364         // Directive options
365         var options = { scope: scope };
366         angular.forEach([
367           'template',
368           'contentTemplate',
369           'placement',
370           'container',
371           'target',
372           'delay',
373           'trigger',
374           'keyboard',
375           'html',
376           'animation',
377           'type'
378         ], function (key) {
379           if (angular.isDefined(attr[key]))
380             options[key] = attr[key];
381         });
382         // Observe scope attributes for change
383         angular.forEach(['title'], function (key) {
384           attr[key] && attr.$observe(key, function (newValue, oldValue) {
385             scope[key] = $sce.trustAsHtml(newValue);
386             angular.isDefined(oldValue) && $$rAF(function () {
387               tooltip && tooltip.$applyPlacement();
388             });
389           });
390         });
391         // Support scope as an object
392         attr.bsTooltip && scope.$watch(attr.bsTooltip, function (newValue, oldValue) {
393           if (angular.isObject(newValue)) {
394             angular.extend(scope, newValue);
395           } else {
396             scope.title = newValue;
397           }
398           angular.isDefined(oldValue) && $$rAF(function () {
399             tooltip && tooltip.$applyPlacement();
400           });
401         }, true);
402         // Initialize popover
403         var tooltip = $tooltip(element, options);
404         // Garbage collection
405         scope.$on('$destroy', function () {
406           tooltip.destroy();
407           options = null;
408           tooltip = null;
409         });
410       }
411     };
412   }
413 ]);