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.scrollspy', [
10   'mgcrea.ngStrap.helpers.debounce',
11   'mgcrea.ngStrap.helpers.dimensions'
12 ]).provider('$scrollspy', function () {
13   // Pool of registered spies
14   var spies = this.$$spies = {};
15   var defaults = this.defaults = {
16       debounce: 150,
17       throttle: 100,
18       offset: 100
19     };
20   this.$get = [
21     '$window',
22     '$document',
23     '$rootScope',
24     'dimensions',
25     'debounce',
26     'throttle',
27     function ($window, $document, $rootScope, dimensions, debounce, throttle) {
28       var windowEl = angular.element($window);
29       var docEl = angular.element($document.prop('documentElement'));
30       var bodyEl = angular.element($window.document.body);
31       // Helper functions
32       function nodeName(element, name) {
33         return element[0].nodeName && element[0].nodeName.toLowerCase() === name.toLowerCase();
34       }
35       function ScrollSpyFactory(config) {
36         // Common vars
37         var options = angular.extend({}, defaults, config);
38         if (!options.element)
39           options.element = bodyEl;
40         var isWindowSpy = nodeName(options.element, 'body');
41         var scrollEl = isWindowSpy ? windowEl : options.element;
42         var scrollId = isWindowSpy ? 'window' : options.id;
43         // Use existing spy
44         if (spies[scrollId]) {
45           spies[scrollId].$$count++;
46           return spies[scrollId];
47         }
48         var $scrollspy = {};
49         // Private vars
50         var unbindViewContentLoaded, unbindIncludeContentLoaded;
51         var trackedElements = $scrollspy.$trackedElements = [];
52         var sortedElements = [];
53         var activeTarget;
54         var debouncedCheckPosition;
55         var throttledCheckPosition;
56         var debouncedCheckOffsets;
57         var viewportHeight;
58         var scrollTop;
59         $scrollspy.init = function () {
60           // Setup internal ref counter
61           this.$$count = 1;
62           // Bind events
63           debouncedCheckPosition = debounce(this.checkPosition, options.debounce);
64           throttledCheckPosition = throttle(this.checkPosition, options.throttle);
65           scrollEl.on('click', this.checkPositionWithEventLoop);
66           windowEl.on('resize', debouncedCheckPosition);
67           scrollEl.on('scroll', throttledCheckPosition);
68           debouncedCheckOffsets = debounce(this.checkOffsets, options.debounce);
69           unbindViewContentLoaded = $rootScope.$on('$viewContentLoaded', debouncedCheckOffsets);
70           unbindIncludeContentLoaded = $rootScope.$on('$includeContentLoaded', debouncedCheckOffsets);
71           debouncedCheckOffsets();
72           // Register spy for reuse
73           if (scrollId) {
74             spies[scrollId] = $scrollspy;
75           }
76         };
77         $scrollspy.destroy = function () {
78           // Check internal ref counter
79           this.$$count--;
80           if (this.$$count > 0) {
81             return;
82           }
83           // Unbind events
84           scrollEl.off('click', this.checkPositionWithEventLoop);
85           windowEl.off('resize', debouncedCheckPosition);
86           scrollEl.off('scroll', debouncedCheckPosition);
87           unbindViewContentLoaded();
88           unbindIncludeContentLoaded();
89           if (scrollId) {
90             delete spies[scrollId];
91           }
92         };
93         $scrollspy.checkPosition = function () {
94           // Not ready yet
95           if (!sortedElements.length)
96             return;
97           // Calculate the scroll position
98           scrollTop = (isWindowSpy ? $window.pageYOffset : scrollEl.prop('scrollTop')) || 0;
99           // Calculate the viewport height for use by the components
100           viewportHeight = Math.max($window.innerHeight, docEl.prop('clientHeight'));
101           // Activate first element if scroll is smaller
102           if (scrollTop < sortedElements[0].offsetTop && activeTarget !== sortedElements[0].target) {
103             return $scrollspy.$activateElement(sortedElements[0]);
104           }
105           // Activate proper element
106           for (var i = sortedElements.length; i--;) {
107             if (angular.isUndefined(sortedElements[i].offsetTop) || sortedElements[i].offsetTop === null)
108               continue;
109             if (activeTarget === sortedElements[i].target)
110               continue;
111             if (scrollTop < sortedElements[i].offsetTop)
112               continue;
113             if (sortedElements[i + 1] && scrollTop > sortedElements[i + 1].offsetTop)
114               continue;
115             return $scrollspy.$activateElement(sortedElements[i]);
116           }
117         };
118         $scrollspy.checkPositionWithEventLoop = function () {
119           setTimeout(this.checkPosition, 1);
120         };
121         // Protected methods
122         $scrollspy.$activateElement = function (element) {
123           if (activeTarget) {
124             var activeElement = $scrollspy.$getTrackedElement(activeTarget);
125             if (activeElement) {
126               activeElement.source.removeClass('active');
127               if (nodeName(activeElement.source, 'li') && nodeName(activeElement.source.parent().parent(), 'li')) {
128                 activeElement.source.parent().parent().removeClass('active');
129               }
130             }
131           }
132           activeTarget = element.target;
133           element.source.addClass('active');
134           if (nodeName(element.source, 'li') && nodeName(element.source.parent().parent(), 'li')) {
135             element.source.parent().parent().addClass('active');
136           }
137         };
138         $scrollspy.$getTrackedElement = function (target) {
139           return trackedElements.filter(function (obj) {
140             return obj.target === target;
141           })[0];
142         };
143         // Track offsets behavior
144         $scrollspy.checkOffsets = function () {
145           angular.forEach(trackedElements, function (trackedElement) {
146             var targetElement = document.querySelector(trackedElement.target);
147             trackedElement.offsetTop = targetElement ? dimensions.offset(targetElement).top : null;
148             if (options.offset && trackedElement.offsetTop !== null)
149               trackedElement.offsetTop -= options.offset * 1;
150           });
151           sortedElements = trackedElements.filter(function (el) {
152             return el.offsetTop !== null;
153           }).sort(function (a, b) {
154             return a.offsetTop - b.offsetTop;
155           });
156           debouncedCheckPosition();
157         };
158         $scrollspy.trackElement = function (target, source) {
159           trackedElements.push({
160             target: target,
161             source: source
162           });
163         };
164         $scrollspy.untrackElement = function (target, source) {
165           var toDelete;
166           for (var i = trackedElements.length; i--;) {
167             if (trackedElements[i].target === target && trackedElements[i].source === source) {
168               toDelete = i;
169               break;
170             }
171           }
172           trackedElements = trackedElements.splice(toDelete, 1);
173         };
174         $scrollspy.activate = function (i) {
175           trackedElements[i].addClass('active');
176         };
177         // Initialize plugin
178         $scrollspy.init();
179         return $scrollspy;
180       }
181       return ScrollSpyFactory;
182     }
183   ];
184 }).directive('bsScrollspy', [
185   '$rootScope',
186   'debounce',
187   'dimensions',
188   '$scrollspy',
189   function ($rootScope, debounce, dimensions, $scrollspy) {
190     return {
191       restrict: 'EAC',
192       link: function postLink(scope, element, attr) {
193         var options = { scope: scope };
194         angular.forEach([
195           'offset',
196           'target'
197         ], function (key) {
198           if (angular.isDefined(attr[key]))
199             options[key] = attr[key];
200         });
201         var scrollspy = $scrollspy(options);
202         scrollspy.trackElement(options.target, element);
203         scope.$on('$destroy', function () {
204           scrollspy.untrackElement(options.target, element);
205           scrollspy.destroy();
206           options = null;
207           scrollspy = null;
208         });
209       }
210     };
211   }
212 ]).directive('bsScrollspyList', [
213   '$rootScope',
214   'debounce',
215   'dimensions',
216   '$scrollspy',
217   function ($rootScope, debounce, dimensions, $scrollspy) {
218     return {
219       restrict: 'A',
220       compile: function postLink(element, attr) {
221         var children = element[0].querySelectorAll('li > a[href]');
222         angular.forEach(children, function (child) {
223           var childEl = angular.element(child);
224           childEl.parent().attr('bs-scrollspy', '').attr('data-target', childEl.attr('href'));
225         });
226       }
227     };
228   }
229 ]);