-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Additional improvements to Store and Record API #1082
Changes from 2 commits
aeefce7
f0347b9
00f3475
36f9a29
046b567
30742a7
56ca575
b6472ad
ba8d102
b28d628
8eb26a2
bb7b159
a267633
5c165c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,8 @@ | |
* | ||
* Copyright © 2019 Extremely Heavy Industries Inc. | ||
*/ | ||
import {isEqual} from 'lodash'; | ||
import {isEqual, isNil, isString} from 'lodash'; | ||
import {throwIf} from '@xh/hoist/utils/js'; | ||
|
||
/** | ||
* Wrapper object for each data element within a {@see BaseStore}. | ||
|
@@ -14,8 +15,6 @@ import {isEqual} from 'lodash'; | |
*/ | ||
export class Record { | ||
|
||
static RESERVED_FIELD_NAMES = ['parentId', 'store', 'xhTreePath'] | ||
|
||
/** @member {(string|number)} */ | ||
id; | ||
/** @member {BaseStore} */ | ||
|
@@ -43,20 +42,31 @@ export class Record { | |
* requiring children to also be recreated.) | ||
* | ||
* @param {Object} c - Record configuration | ||
* @param {Object} c.data - data for constructing the record. | ||
* @param {Object} c.raw - raw data for record. | ||
* @param {BaseStore} c.store - store containing this record. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Realizing that LocalStore defines We've discussed how we might want to unwind BaseStore, taking LocalStore -> Store and having it be the primary/parent store class. I dunno if we want to deal with that now. Alternatively we could move idSpec to BaseStore, or adjust the docs here to punt a bit and deal with LocalStore only. |
||
* @param {Record} [c.parent] - parent record, if any. | ||
*/ | ||
constructor({raw, store, parent}) { | ||
const id = raw.id; | ||
constructor({data, raw, store, parent}) { | ||
const {idSpec} = store, | ||
id = isString(idSpec) ? data[idSpec] : idSpec(data); | ||
|
||
throwIf(isNil(id), "Record has an undefined ID. Use 'LocalStore.idSpec' to resolve a unique ID for each record."); | ||
|
||
this.id = id; | ||
this.store = store; | ||
this.raw = raw; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add an |
||
this.parentId = parent ? parent.id : null; | ||
this.xhTreePath = parent ? [...parent.xhTreePath, id] : [id]; | ||
|
||
store.fields.forEach(f => { | ||
this[f.name] = f.parseVal(raw[f.name]); | ||
const {name} = f; | ||
if (name == 'id') return; | ||
throwIf( | ||
name in this, | ||
`Field name "${name}" cannot be used for data. It is reserved as a top-level property of the Record class.` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great that this check moved into this class. |
||
); | ||
this[name] = f.parseVal(data[name]); | ||
}); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
* Copyright © 2019 Extremely Heavy Industries Inc. | ||
*/ | ||
|
||
import {isString, isNil, partition} from 'lodash'; | ||
import {partition} from 'lodash'; | ||
import {throwIf} from '@xh/hoist/utils/js/'; | ||
import {Record} from '../Record'; | ||
|
||
|
@@ -164,28 +164,22 @@ export class RecordSet { | |
} | ||
|
||
createRecord(raw, records, parent) { | ||
const {store} = this, | ||
{idSpec} = store; | ||
const {store} = this; | ||
|
||
if (store.processRawData) store.processRawData(raw); | ||
|
||
raw.id = isString(idSpec) ? raw[idSpec] : idSpec(raw); | ||
|
||
throwIf( | ||
isNil(raw.id), | ||
"Record has a null/undefined ID. Use the 'LocalStore.idSpec' config to resolve a unique ID for each record." | ||
); | ||
let data = raw; | ||
if (store.processRawData) { | ||
data = store.processRawData(raw); | ||
throwIf(!data, 'processRawData should return an object. If writing/editing, be sure to return a clone!'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we enforce processRawData always returning a new object? Is there a likely/important use case where we have processRawData making decisions to not edit some objects (and it would be terribly inefficient to force it to clone anyway)? |
||
} | ||
|
||
const rec = new Record({data, raw, parent, store}); | ||
throwIf( | ||
records.has(raw.id), | ||
`ID ${raw.id} is not unique. Use the 'LocalStore.idSpec' config to resolve a unique ID for each record.` | ||
records.has(rec.id), | ||
`ID ${rec.id} is not unique. Use the 'LocalStore.idSpec' config to resolve a unique ID for each record.` | ||
); | ||
|
||
const rec = new Record({raw, parent, store}); | ||
records.set(rec.id, rec); | ||
|
||
if (raw.children) { | ||
raw.children.forEach(rawChild => this.createRecord(rawChild, records, rec)); | ||
if (data.children) { | ||
data.children.forEach(rawChild => this.createRecord(rawChild, records, rec)); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,6 @@ import {HoistModel, XH, managed} from '@xh/hoist/core'; | |
import {GridModel} from '@xh/hoist/cmp/grid'; | ||
import {computed} from '@xh/hoist/mobx'; | ||
import {convertIconToSvg, Icon} from '@xh/hoist/icon'; | ||
import {isNil} from 'lodash'; | ||
|
||
/** | ||
* A Model for managing the state of a LeftRightChooser. | ||
|
@@ -173,21 +172,21 @@ export class LeftRightChooserModel { | |
|
||
preprocessData(data) { | ||
return data | ||
.filter(rec => !rec.exclude) | ||
.map(raw => { | ||
raw.group = raw.group || this._ungroupedName; | ||
raw.side = raw.side || 'left'; | ||
raw.id = isNil(raw.id) ? XH.genId() : raw.id; | ||
return raw; | ||
.filter(r => !r.exclude) | ||
.map(r => { | ||
return { | ||
id: XH.genId(), | ||
group: this._ungroupedName, | ||
side: 'left', | ||
...r | ||
}; | ||
}); | ||
} | ||
|
||
moveRows(rows) { | ||
rows.forEach(rec => { | ||
if (rec.locked) return; | ||
|
||
const rawRec = this._data.find(raw => raw === rec.raw); | ||
rawRec.side = (rec.side === 'left' ? 'right' : 'left'); | ||
rec.raw.side = (rec.side === 'left' ? 'right' : 'left'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll be honest - I am not gonna dive into LRChooser code right now, but this seems very weird to be modifying the raw object. |
||
}); | ||
|
||
this.refreshStores(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that apps aren't expected to construct records, but this is still a bit confusing here. How about:
Would that be incrementally more clear?