|
11 | 11 | * or submit itself to any jurisdiction. |
12 | 12 | */ |
13 | 13 |
|
| 14 | +import { Observable, RemoteData } from '/js/src/index.js'; |
| 15 | +import { ObservableData } from '../utilities/ObservableData.js'; |
| 16 | +import { PaginatedRemoteDataSource } from '../utilities/fetch/PaginatedRemoteDataSource.js'; |
| 17 | +import { PaginationModel } from '../components/Pagination/PaginationModel.js'; |
| 18 | +import { buildUrl } from '../utilities/fetch/buildUrl.js'; |
| 19 | + |
14 | 20 | /** |
15 | 21 | * Interface of a model representing an overview page state |
16 | 22 | * |
|
20 | 26 | /** |
21 | 27 | * @property {PaginationModel} OverviewModel#pagination pagination model of the overview |
22 | 28 | */ |
| 29 | + |
| 30 | +/** |
| 31 | + * Base model for an overview page |
| 32 | + * |
| 33 | + * @template T the type of data displayed in the overview page |
| 34 | + */ |
| 35 | +export class OverviewPageModel extends Observable { |
| 36 | + /** |
| 37 | + * Constructor |
| 38 | + */ |
| 39 | + constructor() { |
| 40 | + super(); |
| 41 | + |
| 42 | + this._pagination = new PaginationModel(); |
| 43 | + this._pagination.observe(() => this.load()); |
| 44 | + this._pagination.itemsPerPageSelector$.observe(() => this.notify()); |
| 45 | + |
| 46 | + const dataSourceObservable = ObservableData.builder().initialValue(RemoteData.loading()).build(); |
| 47 | + dataSourceObservable.observe(() => this.parseApiRemoteData(dataSourceObservable.getCurrent())); |
| 48 | + |
| 49 | + this._dataSource = new PaginatedRemoteDataSource(); |
| 50 | + this._dataSource.pipe(dataSourceObservable); |
| 51 | + |
| 52 | + this._observableItems = ObservableData.builder().initialValue(RemoteData.loading()).build(); |
| 53 | + this._observableItems.bubbleTo(this); |
| 54 | + } |
| 55 | + |
| 56 | + /** |
| 57 | + * Return the root endpoint for the model to use to fetch data |
| 58 | + * |
| 59 | + * @return {string} the endpoint |
| 60 | + * @abstract |
| 61 | + */ |
| 62 | + getRootEndpoint() { |
| 63 | + throw new Error('Abstract function call'); |
| 64 | + } |
| 65 | + |
| 66 | + /** |
| 67 | + * Reset this model to its default |
| 68 | + * |
| 69 | + * @returns {void} |
| 70 | + */ |
| 71 | + reset() { |
| 72 | + this._observableItems.setCurrent(RemoteData.notAsked()); |
| 73 | + this._pagination.reset(); |
| 74 | + } |
| 75 | + |
| 76 | + /** |
| 77 | + * Parse the API remote data to extract the list of items to display and update the pagination |
| 78 | + * |
| 79 | + * @param {RemoteData<{items: T[], totalCount: number}, *>} remoteData the API remote data |
| 80 | + * @return {void} |
| 81 | + */ |
| 82 | + parseApiRemoteData(remoteData) { |
| 83 | + /* |
| 84 | + * When fetching data, to avoid concurrency issues, save a flag stating if the fetched data should be concatenated with the current one |
| 85 | + * (infinite scroll) or if they should replace them |
| 86 | + */ |
| 87 | + const keepExisting = this._pagination.currentPage > 1 && this._pagination.isInfiniteScrollEnabled; |
| 88 | + |
| 89 | + remoteData.match({ |
| 90 | + NotAsked: () => this._observableItems.setCurrent(RemoteData.notAsked()), |
| 91 | + Loading: () => this._pagination.isInfiniteScrollEnabled ? null : this._observableItems.setCurrent(RemoteData.loading()), |
| 92 | + Success: ({ items, totalCount }) => { |
| 93 | + const concatenateWith = keepExisting ? this.items.match({ |
| 94 | + Success: (payload) => payload, |
| 95 | + Other: () => [], |
| 96 | + }) : []; |
| 97 | + this._pagination.itemsCount = totalCount; |
| 98 | + this._observableItems.setCurrent(RemoteData.success([...concatenateWith, ...this.processItems(items)])); |
| 99 | + }, |
| 100 | + Failure: (error) => this._observableItems.setCurrent(RemoteData.failure(error)), |
| 101 | + }); |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * Apply a processing on each provided items and return the result |
| 106 | + * |
| 107 | + * @param {T[]} items the items to process |
| 108 | + * @return {T[]} The list of processed items |
| 109 | + */ |
| 110 | + processItems(items) { |
| 111 | + return items; |
| 112 | + } |
| 113 | + |
| 114 | + /** |
| 115 | + * Fetch all the relevant items from the API |
| 116 | + * |
| 117 | + * @return {Promise<void>} void |
| 118 | + */ |
| 119 | + async load() { |
| 120 | + const params = { |
| 121 | + 'page[offset]': this._pagination.firstItemOffset, |
| 122 | + 'page[limit]': this._pagination.itemsPerPage, |
| 123 | + }; |
| 124 | + |
| 125 | + await this._dataSource.fetch(buildUrl(this.getRootEndpoint(), params)); |
| 126 | + } |
| 127 | + |
| 128 | + /** |
| 129 | + * Return the current items remote data |
| 130 | + * |
| 131 | + * @return {RemoteData<T[]>} the items |
| 132 | + */ |
| 133 | + get items() { |
| 134 | + return this._observableItems.getCurrent(); |
| 135 | + } |
| 136 | + |
| 137 | + /** |
| 138 | + * Returns the overview pagination |
| 139 | + * |
| 140 | + * @return {PaginationModel} the pagination |
| 141 | + */ |
| 142 | + get pagination() { |
| 143 | + return this._pagination; |
| 144 | + } |
| 145 | +} |
0 commit comments