1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 // MultipageLoader retrieves a multi-page result set from the
6 // server. The constructor initiates the first page load.
8 // config.loadFunc is a function that accepts an array of
9 // paging-related filters, and returns a promise for the API
10 // response. loadFunc() must retrieve results in "modified_at desc"
14 // * 'loading' if a network request is in progress;
15 // * 'done' if there are no more items to load;
16 // * 'ready' otherwise.
18 // items is a stream that resolves to an array of all items retrieved so far.
20 // loadMore() loads the next page, if any.
21 window.MultipageLoader = function(config) {
23 Object.assign(loader, config, {
31 loadMore: function() {
32 if (loader.state == loader.DONE || loader.state == loader.LOADING)
34 var filters = loader.thresholdItem ? [
35 ["modified_at", "<=", loader.thresholdItem.modified_at],
36 ["uuid", "!=", loader.thresholdItem.uuid],
38 loader.state = loader.LOADING
39 loader.loadFunc(filters).then(function(resp) {
40 var items = loader.items()
41 Array.prototype.push.apply(items, resp.items)
42 if (resp.items.length == 0) {
43 loader.state = loader.DONE
45 loader.thresholdItem = resp.items[resp.items.length-1]
46 loader.state = loader.READY
49 }).catch(function(err) {
51 loader.state = loader.READY
58 // MergingLoader merges results from multiple loaders (given in the
59 // config.children array) into a single result set.
61 // new MergingLoader({children: [loader, loader, ...]})
63 // The children must retrieve results in "modified_at desc" order.
64 window.MergingLoader = function(config) {
66 Object.assign(loader, config, {
67 // Sorted items ready to display, merged from all children.
73 loadable: function() {
74 // Return an array of children that we could call
75 // loadMore() on. Update loader.state.
76 loader.state = loader.DONE
77 return loader.children.filter(function(child) {
78 if (child.state == child.DONE)
80 if (child.state == child.LOADING) {
81 loader.state = loader.LOADING
84 if (loader.state == loader.DONE)
85 loader.state = loader.READY
89 loadMore: function() {
90 // Call loadMore() on children that have reached
92 loader.loadable().map(function(child) {
93 if (child.items().length - child.itemsDisplayed < loader.lowWaterMark) {
94 loader.state = loader.LOADING
99 mergeItems: function() {
100 // We want to avoid moving items around on the screen once
101 // they're displayed.
103 // To this end, here we find the last safely displayable
104 // item ("cutoff") by getting the last item from each
105 // unfinished child, and taking the topmost (most recent)
108 // (If we were to display an item below that cutoff, the
109 // next page of results from an unfinished child could
110 // include items that get inserted above the cutoff,
111 // causing the cutoff item to move down.)
113 var cutoffUnknown = false
114 loader.children.forEach(function(child) {
115 if (child.state == child.DONE)
117 var items = child.items()
118 if (items.length == 0) {
119 // No idea what's coming in the next page.
123 var last = items[items.length-1].modified_at
124 if (!cutoff || cutoff < last)
130 loader.children.forEach(function(child) {
131 child.itemsDisplayed = 0
132 child.items().every(function(item) {
133 if (cutoff && item.modified_at < cutoff)
134 // Don't display this item or anything after
135 // it (see "cutoff" comment above).
138 child.itemsDisplayed++
139 return true // continue
142 loader.items(combined.sort(function(a, b) {
143 return a.modified_at < b.modified_at ? 1 : -1
146 // Number of undisplayed items to keep on hand for each result
147 // set. When hitting "load more", if a result set already has
148 // this many additional results available, we don't bother
149 // fetching a new page. This is the _minimum_ number of rows
150 // that will be added to loader.items in each "load more"
151 // event (except for the case where all items are displayed).
154 var childrenReady = m.stream.merge(loader.children.map(function(child) {
157 childrenReady.map(loader.loadable)
158 childrenReady.map(loader.mergeItems)