-
Notifications
You must be signed in to change notification settings - Fork 8
/
StoreSelectionModel.ts
143 lines (124 loc) · 4.33 KB
/
StoreSelectionModel.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
* This file belongs to Hoist, an application development toolkit
* developed by Extremely Heavy Industries (www.xh.io | [email protected])
*
* Copyright © 2024 Extremely Heavy Industries Inc.
*/
import {HoistModel} from '@xh/hoist/core';
import {action, computed, observable, makeObservable} from '@xh/hoist/mobx';
import {castArray, compact, remove, isEqual, union, map} from 'lodash';
import {Store} from './Store';
import {StoreRecord, StoreRecordId, StoreRecordOrId} from './StoreRecord';
export interface StoreSelectionConfig {
store?: Store;
mode?: 'single' | 'multiple' | 'disabled';
xhImpl?: boolean;
}
/**
* Model for managing store selections.
* Typically accessed from a GridModel to observe/control Grid selection.
*/
export class StoreSelectionModel extends HoistModel {
readonly store: Store;
mode: 'single' | 'multiple' | 'disabled';
@observable.ref
private _ids = [];
get isEnabled(): boolean {
return this.mode !== 'disabled';
}
constructor({store, mode = 'single', xhImpl = false}: StoreSelectionConfig) {
super();
makeObservable(this);
this.xhImpl = xhImpl;
this.store = store;
this.mode = mode;
this.addReaction(this.cullSelectionReaction());
}
@computed.struct
get selectedRecords(): StoreRecord[] {
return compact(this._ids.map(it => this.store.getById(it, true)));
}
@computed.struct
get selectedIds(): StoreRecordId[] {
return map(this.selectedRecords, 'id');
}
/**
* Single selected record, or null if multiple/no records selected.
*
* Note that this getter will also change if just the data of selected record is changed
* due to store loading or editing. Applications only interested in the *identity*
* of the selection should use {@link selectedId} instead.
*/
get selectedRecord(): StoreRecord {
const {selectedRecords} = this;
return selectedRecords.length === 1 ? selectedRecords[0] : null;
}
/**
* ID of selected record, or null if multiple/no records selected.
*
* Note that this getter will *not* change if just the data of selected record is changed
* due to store loading or editing. Applications also interested in the *contents* of the
* selection should use the {@link selectedRecord} getter instead.
*/
get selectedId(): StoreRecordId {
const {selectedIds} = this;
return selectedIds.length === 1 ? selectedIds[0] : null;
}
/** @returns true if selection is empty. */
get isEmpty(): boolean {
return this.count === 0;
}
/** @returns count of currently selected records. */
@computed
get count(): number {
return this.selectedRecords.length;
}
/**
* Set the selection.
* @param records - single record/ID or array of records/IDs to select.
* @param clearSelection - true to clear previous selection (rather than add to it).
*/
@action
select(records: StoreRecordOrId | StoreRecordOrId[], clearSelection: boolean = true) {
records = castArray(records ?? []);
if (this.mode === 'disabled') return;
if (this.mode === 'single' && records.length > 1) {
records = [records[0]];
}
const ids = records
.map(it => {
return it instanceof StoreRecord ? it.id : it;
})
.filter(id => {
return this.store.getById(id, true);
});
if (isEqual(ids, this._ids)) {
return;
}
this._ids = clearSelection ? ids : union(this._ids, ids);
}
/** Select all filtered records. */
@action
selectAll() {
if (this.mode === 'multiple') {
this.select(this.store.records);
}
}
/** Clear the selection. */
@action
clear() {
this.select([]);
}
//------------------------
// Implementation
//------------------------
private cullSelectionReaction() {
// Remove recs from selection if they are no longer in store. Cleanup array in place without
// modifying observable -- the 'records' getter provides all observable state.
const {store} = this;
return {
track: () => store.records,
run: () => remove(this._ids, id => !store.getById(id, true))
};
}
}