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"
13 // done is true if there are no more pages to load.
15 // loading is true if a network request is in progress.
17 // items is a stream that resolves to an array of all items retrieved so far.
19 // loadMore() loads the next page, if any.
20 window.MultipageLoader = function(config) {
22 Object.assign(loader, config, {
27 loadMore: function() {
28 if (loader.done || loader.loading)
30 var filters = loader.thresholdItem ? [
31 ["modified_at", "<=", loader.thresholdItem.modified_at],
32 ["uuid", "!=", loader.thresholdItem.uuid],
35 loader.loadFunc(filters).then(function(resp) {
36 var items = loader.items() || []
37 Array.prototype.push.apply(items, resp.items)
38 if (resp.items.length == 0)
41 loader.thresholdItem = resp.items[resp.items.length-1]
42 loader.loading = false
44 }).catch(function(err) {
46 loader.loading = false
53 // MergingLoader merges results from multiple loaders (given in the
54 // config.children array) into a single result set.
56 // new MergingLoader({children: [loader, loader, ...]})
58 // The children must retrieve results in "modified_at desc" order.
59 window.MergingLoader = function(config) {
61 Object.assign(loader, config, {
62 // Sorted items ready to display, merged from all children.
66 loadable: function() {
67 // Return an array of children that we could call
68 // loadMore() on. Update loader.done and loader.loading.
70 loader.loading = false
71 return loader.children.filter(function(child) {
81 loadMore: function() {
82 // Call loadMore() on children that have reached
84 loader.loadable().map(function(child) {
85 if (child.items().length - child.itemsDisplayed < loader.lowWaterMark) {
91 mergeItems: function() {
92 // cutoff is the topmost (recent) of {bottom (oldest) entry of
93 // any child that still has more pages left to fetch}
95 loader.children.forEach(function(child) {
96 var items = child.items()
97 if (items.length == 0 || child.done)
99 var last = items[items.length-1].modified_at
100 if (!cutoff || cutoff < last)
104 loader.children.forEach(function(child) {
105 child.itemsDisplayed = 0
106 child.items().every(function(item) {
107 if (cutoff && item.modified_at < cutoff)
108 // Some other children haven't caught up to this
109 // point, so don't display this item or anything
113 child.itemsDisplayed++
114 return true // continue
117 loader.items(combined.sort(function(a, b) {
118 return a.modified_at < b.modified_at ? 1 : -1
121 // Number of undisplayed items to keep on hand for each result
122 // set. When hitting "load more", if a result set already has
123 // this many additional results available, we don't bother
124 // fetching a new page. This is the _minimum_ number of rows
125 // that will be added to loader.items in each "load more"
126 // event (except for the case where all items are displayed).
129 var childrenReady = m.stream.merge(loader.children.map(function(child) {
132 childrenReady.map(loader.loadable)
133 childrenReady.map(loader.mergeItems)