1 | import Vue from 'vue';
|
2 | const ctx = '@@InfiniteScroll';
|
3 |
|
4 | var throttle = function(fn, delay) {
|
5 | var now, lastExec, timer, context, args;
|
6 |
|
7 | var execute = function() {
|
8 | fn.apply(context, args);
|
9 | lastExec = now;
|
10 | };
|
11 |
|
12 | return function() {
|
13 | context = this;
|
14 | args = arguments;
|
15 |
|
16 | now = Date.now();
|
17 |
|
18 | if (timer) {
|
19 | clearTimeout(timer);
|
20 | timer = null;
|
21 | }
|
22 |
|
23 | if (lastExec) {
|
24 | var diff = delay - (now - lastExec);
|
25 | if (diff < 0) {
|
26 | execute();
|
27 | } else {
|
28 | timer = setTimeout(() => {
|
29 | execute();
|
30 | }, diff);
|
31 | }
|
32 | } else {
|
33 | execute();
|
34 | }
|
35 | };
|
36 | };
|
37 |
|
38 | var getScrollTop = function(element) {
|
39 | if (element === window) {
|
40 | return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop);
|
41 | }
|
42 |
|
43 | return element.scrollTop;
|
44 | };
|
45 |
|
46 | var getComputedStyle = Vue.prototype.$isServer ? {} : document.defaultView.getComputedStyle;
|
47 |
|
48 | var getScrollEventTarget = function(element) {
|
49 | var currentNode = element;
|
50 |
|
51 | while (currentNode && currentNode.tagName !== 'HTML' && currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
|
52 | var overflowY = getComputedStyle(currentNode).overflowY;
|
53 | if (overflowY === 'scroll' || overflowY === 'auto') {
|
54 | return currentNode;
|
55 | }
|
56 | currentNode = currentNode.parentNode;
|
57 | }
|
58 | return window;
|
59 | };
|
60 |
|
61 | var getVisibleHeight = function(element) {
|
62 | if (element === window) {
|
63 | return document.documentElement.clientHeight;
|
64 | }
|
65 |
|
66 | return element.clientHeight;
|
67 | };
|
68 |
|
69 | var getElementTop = function(element) {
|
70 | if (element === window) {
|
71 | return getScrollTop(window);
|
72 | }
|
73 | return element.getBoundingClientRect().top + getScrollTop(window);
|
74 | };
|
75 |
|
76 | var isAttached = function(element) {
|
77 | var currentNode = element.parentNode;
|
78 | while (currentNode) {
|
79 | if (currentNode.tagName === 'HTML') {
|
80 | return true;
|
81 | }
|
82 | if (currentNode.nodeType === 11) {
|
83 | return false;
|
84 | }
|
85 | currentNode = currentNode.parentNode;
|
86 | }
|
87 | return false;
|
88 | };
|
89 |
|
90 | var doBind = function() {
|
91 | if (this.binded) return;
|
92 | this.binded = true;
|
93 |
|
94 | var directive = this;
|
95 | var element = directive.el;
|
96 |
|
97 | directive.scrollEventTarget = getScrollEventTarget(element);
|
98 | directive.scrollListener = throttle(doCheck.bind(directive), 200);
|
99 | directive.scrollEventTarget.addEventListener('scroll', directive.scrollListener);
|
100 |
|
101 | var disabledExpr = element.getAttribute('infinite-scroll-disabled');
|
102 | var disabled = false;
|
103 |
|
104 | if (disabledExpr) {
|
105 | this.vm.$watch(disabledExpr, function(value) {
|
106 | directive.disabled = value;
|
107 | if (!value && directive.immediateCheck) {
|
108 | doCheck.call(directive);
|
109 | }
|
110 | });
|
111 | disabled = Boolean(directive.vm[disabledExpr]);
|
112 | }
|
113 | directive.disabled = disabled;
|
114 |
|
115 | var distanceExpr = element.getAttribute('infinite-scroll-distance');
|
116 | var distance = 0;
|
117 | if (distanceExpr) {
|
118 | distance = Number(directive.vm[distanceExpr] || distanceExpr);
|
119 | if (isNaN(distance)) {
|
120 | distance = 0;
|
121 | }
|
122 | }
|
123 | directive.distance = distance;
|
124 |
|
125 | var immediateCheckExpr = element.getAttribute('infinite-scroll-immediate-check');
|
126 | var immediateCheck = true;
|
127 | if (immediateCheckExpr) {
|
128 | immediateCheck = Boolean(directive.vm[immediateCheckExpr]);
|
129 | }
|
130 | directive.immediateCheck = immediateCheck;
|
131 |
|
132 | if (immediateCheck) {
|
133 | doCheck.call(directive);
|
134 | }
|
135 |
|
136 | var eventName = element.getAttribute('infinite-scroll-listen-for-event');
|
137 | if (eventName) {
|
138 | directive.vm.$on(eventName, function() {
|
139 | doCheck.call(directive);
|
140 | });
|
141 | }
|
142 | };
|
143 |
|
144 | var doCheck = function(force) {
|
145 | var scrollEventTarget = this.scrollEventTarget;
|
146 | var element = this.el;
|
147 | var distance = this.distance;
|
148 |
|
149 | if (force !== true && this.disabled) return;
|
150 | var viewportScrollTop = getScrollTop(scrollEventTarget);
|
151 | var viewportBottom = viewportScrollTop + getVisibleHeight(scrollEventTarget);
|
152 |
|
153 | var shouldTrigger = false;
|
154 |
|
155 | if (scrollEventTarget === element) {
|
156 | shouldTrigger = scrollEventTarget.scrollHeight - viewportBottom <= distance;
|
157 | } else {
|
158 | var elementBottom = getElementTop(element) - getElementTop(scrollEventTarget) + element.offsetHeight + viewportScrollTop;
|
159 |
|
160 | shouldTrigger = viewportBottom + distance >= elementBottom;
|
161 | }
|
162 |
|
163 | if (shouldTrigger && this.expression) {
|
164 | this.expression();
|
165 | }
|
166 | };
|
167 |
|
168 | export default {
|
169 | bind(el, binding, vnode) {
|
170 | el[ctx] = {
|
171 | el,
|
172 | vm: vnode.context,
|
173 | expression: binding.value
|
174 | };
|
175 | const args = arguments;
|
176 | var cb = function() {
|
177 | el[ctx].vm.$nextTick(function() {
|
178 | if (isAttached(el)) {
|
179 | doBind.call(el[ctx], args);
|
180 | }
|
181 |
|
182 | el[ctx].bindTryCount = 0;
|
183 |
|
184 | var tryBind = function() {
|
185 | if (el[ctx].bindTryCount > 10) return;
|
186 | el[ctx].bindTryCount++;
|
187 | if (isAttached(el)) {
|
188 | doBind.call(el[ctx], args);
|
189 | } else {
|
190 | setTimeout(tryBind, 50);
|
191 | }
|
192 | };
|
193 |
|
194 | tryBind();
|
195 | });
|
196 | };
|
197 | if (el[ctx].vm._isMounted) {
|
198 | cb();
|
199 | return;
|
200 | }
|
201 | el[ctx].vm.$on('hook:mounted', cb);
|
202 | },
|
203 |
|
204 | unbind(el) {
|
205 | if (el[ctx] && el[ctx].scrollEventTarget) {
|
206 | el[ctx].scrollEventTarget.removeEventListener('scroll', el[ctx].scrollListener);
|
207 | }
|
208 | }
|
209 | };
|