1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 window.models = window.models || {}
6 window.models.Pager = function(loadFunc) {
7 // loadFunc(filters) must return a promise for a page of results.
13 loadMore: function() {
14 // Get the next page, if there are any more items to get.
17 var filters = pager.thresholdItem ? [
18 ["modified_at", "<=", pager.thresholdItem.modified_at],
19 ["uuid", "!=", pager.thresholdItem.uuid],
21 loadFunc(filters).then(function(resp) {
22 var items = pager.items() || []
23 Array.prototype.push.apply(items, resp.items)
24 if (resp.items.length == 0)
27 pager.thresholdItem = resp.items[resp.items.length-1]
34 // MultisiteLoader loads pages of results from multiple API sessions
35 // and merges them into a single result set.
37 // The constructor implicitly starts an initial page load for each
40 // new MultisiteLoader({loadFunc: function(session, filters){...},
41 // sessionDB: new window.models.SessionDB()}
43 // At any given time, ml.loadMore will be either false (meaning a page
44 // load is in progress or there are no more results to fetch) or a
45 // function that starts loading more results.
47 // loadFunc() must retrieve results in "modified_at desc" order.
48 window.models = window.models || {}
49 window.models.MultisiteLoader = function(config) {
51 if (!(config.loadFunc && config.sessionDB))
52 throw new Error("MultisiteLoader constructor requires loadFunc and sessionDB")
53 Object.assign(loader, config, {
54 // Sorted items ready to display, merged from all pagers.
59 // Number of undisplayed items to keep on hand for each result
60 // set. When hitting "load more", if a result set already has
61 // this many additional results available, we don't bother
62 // fetching a new page. This is the _minimum_ number of rows
63 // that will be added to loader.displayable in each "load
64 // more" event (except for the case where all items are
68 var sessions = loader.sessionDB.loadActive()
69 m.stream.merge(Object.keys(sessions).map(function(key) {
70 var pager = new window.models.Pager(loader.loadFunc.bind(null, sessions[key]))
71 loader.pagers[key] = pager
73 // Resolve the stream with the session key when the results
75 return pager.items.map(function() { return key })
76 })).map(function(keys) {
77 // Top (most recent) of {bottom (oldest) entry of any pager
78 // that still has more pages left to fetch}
80 keys.forEach(function(key) {
81 var pager = loader.pagers[key]
82 var items = pager.items()
83 if (items.length == 0 || pager.done)
85 var last = items[items.length-1].modified_at
86 if (!cutoff || cutoff < last)
90 keys.forEach(function(key) {
91 var pager = loader.pagers[key]
92 pager.itemsDisplayed = 0
93 pager.items().every(function(item) {
94 if (cutoff && item.modified_at < cutoff)
95 // Some other pagers haven't caught up to this
96 // point, so don't display this item or anything
99 item.session = sessions[key]
101 pager.itemsDisplayed++
102 return true // continue
105 loader.displayable = combined.sort(function(a, b) {
106 return a.modified_at < b.modified_at ? 1 : -1
108 // Make a new loadMore function that hits the pagers (if
109 // necessary according to lowWaterMark)... or set
110 // loader.loadMore to false if there is nothing left to fetch.
112 Object.keys(loader.pagers).map(function(key) {
113 if (!loader.pagers[key].done)
114 loadable.push(loader.pagers[key])
116 if (loadable.length == 0) {
118 loader.loadMore = false
120 loader.loadMore = function() {
121 loader.loadMore = false
122 loadable.map(function(pager) {
123 if (pager.items().length - pager.itemsDisplayed < loader.lowWaterMark)