src/collection/ModelProperty.js
import ObjectProperty from "../ObjectProperty";
import PrimitiveProperty from "../PrimitiveProperty";
import shadow from "../decorators/shadow";
import shadowBound from "../decorators/shadowBound";
import StateType from "../StateType";
import ModelAccess from "./ModelAccess";
/*
@ignore
*/
export default class ModelProperty extends ObjectProperty {
constructor(stateType=ModelProperty.type) {
super(stateType);
}
/*
Used by CollectionProperty to obtain the a model definition suitable for creating a
ModelProperty instance.
*/
static modelDefinitionFor(state, collection) {
const id = collection.extractId(state);
const cid = id || collection.makeId();
return {
id: id || cid,
cid: cid,
data: state,
dirty: false,
lastReqId: -1,
waiting: false,
}
}
get cid() {
return this._() && this._().cid;
}
get collection() {
return this.parent().parent();
}
get data() {
return this._() && this._().data;
}
/*
Gets the ID by which the collection is tracking the model. This will be the persistent storage
ID or an ID created by the collection
*/
get id() {
return this._() && this._().id;
}
@shadow
changeId(id) {
if (this._() && this._().id !== id) {
this._().id = id;
}
}
clearCheckpoint() {
const { collection, data } = this;
const dataProp = data && data.$$();
if (collection.isAutocheckpoint() && dataProp) {
dataProp.clearCheckpoint();
}
}
clearDirty() {
if (this._()) {
this._().dirty = false;
}
this.clearCheckpoint();
}
resetDataToCheckpoint() {
const dataProp = this.data && this.data.$$();
dataProp && dataProp.resetToCheckpoint();
if (this._()) {
this._().dirty = false;
}
}
@shadowBound
defaults(data) {
const id = this.collection.extractId(data);
const state = this._();
const currDirty = state.$().nextState().dirty;
// update the ID if it has changed
if (this._().id !== id) {
this._().id = id;
}
this._().data.__().defaults(data);
// reset to not dirty if was not dirty before the merge since assuming data being merged
// is coming from a source of truth, such as data returned from a save
if (!currDirty) {
this.clearDirty();
}
}
@shadowBound
destroy() {
return this.collection.destroy(this.cid);
}
@shadowBound
isWaiting() {
return this._() && this._().waiting;
}
isDirty() {
return this._() && this._().dirty;
}
/*
Gets if this model has not yet been saved to persistent storage. Assignment of an ID property
is used to determine if an object has been saved.
*/
@shadowBound
isNew() {
const id = this.collection.extractId(this.data);
return !id;
}
@shadowBound
merge(data) {
const id = this.collection.extractId(data);
const state = this._();
const currDirty = state.$().nextState().dirty;
// update the ID if it has changed
if (state.id !== id) {
state.id = id;
}
state.data.__().merge(data);
// reset to not dirty if was not dirty before the merge since assuming data being merged
// is coming from a source of truth, such as data returned from a save
if (!currDirty) {
this.clearDirty();
}
}
@shadowBound
save() {
return this.collection.save(this.cid);
}
setBusy(busy) {
if (busy === this[_busy]) { return }
this[_busy] = busy;
this.touch();
}
@shadowBound
setData(data) {
const id = this.collection.extractId(state);
const state = this._();
// set state first since we trap the invalidate() call and set the dirty flag
state.data = data;
// now we can explicitly set the dirty flag
this.clearDirty();
// update the id if it has changed
if (state.id !== id) {
state.id = id;
}
}
//------------------------------------------------------------------------------------------------------
// Property subclasses may want to override success methods
//------------------------------------------------------------------------------------------------------
/*
Creates a ModelAccess for this property's implementations. ModelAccess will generate a ShadowModelAccess
for its 'data' child which will in turn generate a ShadowModelAccess for each of its children.
*/
create$(impl) {
return new ModelAccess(impl, this);
}
propertyChildInvalidated(childProperty) {
if (childProperty.__().name() == "data" && !this._().dirty) {
this._().dirty = true;
if (this.collection.isAutocheckpoint && !childProperty.hasCheckpoint()) {
childProperty.checkpoint();
}
}
}
}
StateType.defineType(ModelProperty, spec => {
spec.initialState({})
.readonlyOff
.typeName("ModelProperty")
});