|
26 | 26 | <div class="cube-recycle-list-pool"> |
27 | 27 | <div |
28 | 28 | class="cube-recycle-list-item cube-recycle-list-invisible" |
29 | | - v-if="!item.isTombstone && !item.height" |
| 29 | + v-if="item && !item.isTombstone && !item.height" |
30 | 30 | :ref="'preloads'+index" |
31 | 31 | v-for="(item, index) in items" |
32 | 32 | > |
|
38 | 38 | </div> |
39 | 39 | </div> |
40 | 40 | <div |
41 | | - v-if="!infinite" |
| 41 | + v-if="!infinite && !noMore" |
42 | 42 | class="cube-recycle-list-loading" |
43 | 43 | :style="{visibility: loading ? 'visible' : 'hidden'}" |
44 | 44 | > |
45 | 45 | <slot name="spinner"> |
46 | 46 | <div class="cube-recycle-list-loading-content"> |
47 | | - <cube-loading class="spinner"></cube-loading> |
| 47 | + <cube-loading class="cube-recycle-list-spinner"></cube-loading> |
48 | 48 | </div> |
49 | 49 | </slot> |
50 | 50 | </div> |
|
72 | 72 | data() { |
73 | 73 | return { |
74 | 74 | items: [], |
75 | | - list: [], |
76 | 75 | heights: 0, |
77 | 76 | startIndex: 0, |
78 | 77 | loadings: [], |
79 | | - startOffset: 0, |
80 | 78 | noMore: false |
81 | 79 | } |
82 | 80 | }, |
|
109 | 107 | return this.loadings.length |
110 | 108 | } |
111 | 109 | }, |
112 | | - watch: { |
113 | | - list (newV) { |
114 | | - if (newV.length) { |
115 | | - this.loadings.pop() |
116 | | - if (!this.loading) { |
117 | | - this.loadItems() |
118 | | - } |
119 | | - } |
120 | | - }, |
121 | | - items (newV) { |
122 | | - if (newV.length > this.list.length) { |
123 | | - this.getItems() |
124 | | - } |
125 | | - } |
| 110 | + created() { |
| 111 | + this.list = [] |
| 112 | + this.promiseStack = [] |
126 | 113 | }, |
127 | 114 | mounted() { |
128 | 115 | this.checkPromiseCompatibility() |
129 | 116 | this.$el.addEventListener(EVENT_SCROLL, this._onScroll) |
130 | 117 | window.addEventListener(EVENT_RESIZE, this._onResize) |
131 | | - this.init() |
| 118 | + this.load() |
132 | 119 | }, |
133 | | - beforeDestroy () { |
| 120 | + beforeDestroy() { |
134 | 121 | this.$el.removeEventListener(EVENT_SCROLL, this._onScroll) |
135 | 122 | window.removeEventListener(EVENT_RESIZE, this._onResize) |
136 | 123 | }, |
137 | 124 | methods: { |
138 | | - checkPromiseCompatibility () { |
| 125 | + checkPromiseCompatibility() { |
139 | 126 | /* istanbul ignore if */ |
140 | 127 | if (isUndef(window.Promise)) { |
141 | 128 | warn(PROMISE_ERROR) |
142 | 129 | } |
143 | 130 | }, |
144 | | - init() { |
145 | | - this.load() |
146 | | - }, |
147 | 131 | load() { |
148 | 132 | if (this.infinite) { |
| 133 | + const items = this.items |
| 134 | + const start = items.length |
149 | 135 | // increase capacity of items to display tombstone |
150 | | - this.items.length += this.size |
151 | | - this.loadItems() |
| 136 | + items.length += this.size |
| 137 | + const end = items.length |
| 138 | + this.loadItems(start, end) |
| 139 | + this.getItems() |
152 | 140 | } else if (!this.loading) { |
153 | 141 | this.getItems() |
154 | 142 | } |
155 | 143 | }, |
156 | 144 | getItems() { |
| 145 | + const index = this.promiseStack.length |
| 146 | + const promiseFetch = this.onFetch() |
157 | 147 | this.loadings.push('pending') |
158 | | - this.onFetch().then((res) => { |
| 148 | + this.promiseStack.push(promiseFetch) |
| 149 | + promiseFetch.then((res) => { |
| 150 | + this.loadings.pop() |
159 | 151 | /* istanbul ignore if */ |
160 | 152 | if (!res) { |
161 | | - this.noMore = true |
162 | | - this.loadings.pop() |
| 153 | + this.stopScroll(index) |
163 | 154 | } else { |
164 | | - this.list = this.list.concat(res) |
| 155 | + this.setList(index, res) |
| 156 | + this.loadItemsByIndex(index) |
| 157 | + if (res.length < this.size) { |
| 158 | + this.stopScroll(index) |
| 159 | + } |
165 | 160 | } |
166 | 161 | }) |
167 | 162 | }, |
168 | | - loadItems(isResize) { |
| 163 | + removeUnusedTombs(copy, index) { |
| 164 | + let cursor |
| 165 | + let size = this.size |
| 166 | + let start = index * size |
| 167 | + let end = (index + 1) * size |
| 168 | + for (cursor = start; cursor < end; cursor++) { |
| 169 | + if (copy[cursor] && copy[cursor].isTombstone) break |
| 170 | + } |
| 171 | + this.items = copy.slice(0, cursor) |
| 172 | + }, |
| 173 | + stopScroll(index) { |
| 174 | + this.noMore = true |
| 175 | + this.removeUnusedTombs(this.items.slice(0), index) |
| 176 | + this.updateItemTop() |
| 177 | + this.updateStartIndex() |
| 178 | + }, |
| 179 | + setList(index, res) { |
| 180 | + const list = this.list |
| 181 | + const baseIndex = index * this.size |
| 182 | + for (let i = 0; i < res.length; i++) { |
| 183 | + list[baseIndex + i] = res[i] |
| 184 | + } |
| 185 | + }, |
| 186 | + loadItemsByIndex(index) { |
| 187 | + const size = this.size |
| 188 | + const start = index * size |
| 189 | + const end = (index + 1) * size |
| 190 | + this.loadItems(start, end) |
| 191 | + }, |
| 192 | + loadItems(start, end) { |
| 193 | + const items = this.items |
169 | 194 | let promiseTasks = [] |
170 | | - let start = 0 |
171 | | - let end = this.infinite ? this.items.length : this.list.length |
172 | 195 | let item |
173 | 196 | for (let i = start; i < end; i++) { |
174 | | - item = this.items[i] |
| 197 | + item = items[i] |
175 | 198 | /* istanbul ignore if */ |
176 | 199 | if (item && item.loaded) { |
177 | 200 | continue |
|
185 | 208 | // update items top and full list height |
186 | 209 | window.Promise.all(promiseTasks).then(() => { |
187 | 210 | this.updateItemTop() |
| 211 | + this.updateStartIndex() |
188 | 212 | }) |
189 | 213 | }, |
190 | 214 | setItem(index, data) { |
|
202 | 226 | let dom = this.$refs['preloads' + index] |
203 | 227 | if (dom && dom[0]) { |
204 | 228 | cur.height = dom[0].offsetHeight |
205 | | - } else { |
206 | | - // tombstone |
| 229 | + } else if (cur) { |
207 | 230 | cur.height = this.tombHeight |
208 | 231 | } |
209 | 232 | }, |
210 | 233 | updateItemTop() { |
| 234 | + let heights = 0 |
| 235 | + const items = this.items |
| 236 | + let pre |
| 237 | + let current |
211 | 238 | // loop all items to update item top and list height |
212 | | - this.heights = 0 |
213 | | - for (let i = 0; i < this.items.length; i++) { |
214 | | - let pre = this.items[i - 1] |
215 | | - this.items[i].top = pre ? pre.top + pre.height : 0 |
216 | | - this.heights += this.items[i].height |
217 | | - } |
218 | | - // update scroll top when needed |
219 | | - if (this.startOffset) { |
220 | | - this.setScrollTop() |
| 239 | + for (let i = 0; i < items.length; i++) { |
| 240 | + pre = items[i - 1] |
| 241 | + current = items[i] |
| 242 | + // it is empty in array |
| 243 | + /* istanbul ignore if */ |
| 244 | + if (!items[i]) { |
| 245 | + heights += 0 |
| 246 | + } else { |
| 247 | + current.top = pre ? pre.top + pre.height : 0 |
| 248 | + heights += current.height |
| 249 | + } |
221 | 250 | } |
222 | | - this.updateIndex() |
| 251 | + this.heights = heights |
223 | 252 | }, |
224 | | - updateIndex() { |
| 253 | + updateStartIndex() { |
225 | 254 | // update visible items start index |
226 | 255 | let top = this.$el.scrollTop |
227 | | - for (let i = 0; i < this.items.length; i++) { |
228 | | - if (this.items[i].top > top) { |
| 256 | + let item |
| 257 | + const items = this.items |
| 258 | + for (let i = 0; i < items.length; i++) { |
| 259 | + item = items[i] |
| 260 | + if (!item || item.top > top) { |
229 | 261 | this.startIndex = Math.max(0, i - 1) |
230 | 262 | break |
231 | 263 | } |
232 | 264 | } |
233 | 265 | }, |
234 | | - getStartItemOffset() { |
235 | | - if (this.items[this.startIndex]) { |
236 | | - this.startOffset = this.items[this.startIndex].top - this.$el.scrollTop |
237 | | - } |
238 | | - }, |
239 | | - setScrollTop() { |
240 | | - if (this.items[this.startIndex]) { |
241 | | - this.$el.scrollTop = this.items[this.startIndex].top - this.startOffset |
242 | | - // reset start item offset |
243 | | - this.startOffset = 0 |
244 | | - } |
245 | | - }, |
246 | 266 | _onScroll() { |
247 | 267 | // trigger load |
248 | | - if (this.$el.scrollTop + this.$el.offsetHeight > this.heights - this.offset) { |
| 268 | + if (!this.noMore && this.$el.scrollTop + this.$el.offsetHeight > this.heights - this.offset) { |
249 | 269 | this.load() |
250 | 270 | } |
251 | | - this.updateIndex() |
| 271 | + this.updateStartIndex() |
252 | 272 | }, |
253 | 273 | _onResize() { |
254 | | - this.getStartItemOffset() |
255 | | - this.items.forEach((item) => { |
| 274 | + const items = this.items |
| 275 | + items.forEach((item) => { |
256 | 276 | item.loaded = false |
257 | 277 | }) |
258 | | - this.loadItems(true) |
| 278 | + this.loadItems(0, items.length) |
259 | 279 | } |
260 | 280 | }, |
261 | 281 | components: { |
|
298 | 318 |
|
299 | 319 | .cube-recycle-list-loading-content |
300 | 320 | text-align: center |
301 | | - .spinner |
302 | | - margin: 10px auto |
303 | | - display: flex |
304 | | - justify-content: center |
305 | | -
|
306 | | - .cube-recycle-list-noMore |
307 | | - overflow: hidden |
| 321 | + .cube-recycle-list-spinner |
308 | 322 | margin: 10px auto |
309 | | - height: 20px |
310 | | - text-align: center |
| 323 | + display: flex |
| 324 | + justify-content: center |
311 | 325 | </style> |
312 | | - |
|
0 commit comments