232 lines
9.5 KiB
TypeScript
232 lines
9.5 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
|
|
import { BaseModel } from 'app/shared/models/base/base-model';
|
|
import { BaseViewModel, ViewModelConstructor } from 'app/site/base/base-view-model';
|
|
import { ModelDescriptor, NestedModelDescriptors } from '../repositories/base-repository';
|
|
import { CacheChangeIds, RelationCacheService } from './relation-cache.service';
|
|
import {
|
|
isCustomRelationDefinition,
|
|
isGenericRelationDefinition,
|
|
isNormalRelationDefinition,
|
|
isReverseRelationDefinition,
|
|
RelationDefinition
|
|
} from '../definitions/relations';
|
|
import { ViewModelStoreService } from './view-model-store.service';
|
|
|
|
/**
|
|
* Manages relations between view models. This service is and should only used by the
|
|
* base repository to offload managing relations between view models.
|
|
*/
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class RelationManagerService {
|
|
public constructor(
|
|
private viewModelStoreService: ViewModelStoreService,
|
|
private relationCacheService: RelationCacheService
|
|
) {}
|
|
|
|
public handleRelation<M extends BaseModel, V extends BaseViewModel>(
|
|
model: M,
|
|
viewModel: V,
|
|
relation: RelationDefinition
|
|
): any {
|
|
if (isNormalRelationDefinition(relation)) {
|
|
if (
|
|
(relation.type === 'M2M' || relation.type === 'O2M') &&
|
|
model[relation.ownIdKey] &&
|
|
model[relation.ownIdKey].constructor === Array
|
|
) {
|
|
const foreignViewModels = this.viewModelStoreService.getMany(
|
|
relation.foreignViewModel,
|
|
model[relation.ownIdKey]
|
|
);
|
|
this.sortViewModels(foreignViewModels, relation.order);
|
|
return foreignViewModels;
|
|
} else if (relation.type === 'M2O') {
|
|
const foreignViewModel = this.viewModelStoreService.get(
|
|
relation.foreignViewModel,
|
|
model[relation.ownIdKey]
|
|
);
|
|
return foreignViewModel;
|
|
}
|
|
} else if (isReverseRelationDefinition(relation)) {
|
|
if (relation.type === 'M2M') {
|
|
const foreignViewModels = this.viewModelStoreService.filter(
|
|
relation.foreignViewModel,
|
|
foreignViewModel =>
|
|
foreignViewModel[relation.foreignIdKey] &&
|
|
foreignViewModel[relation.foreignIdKey].constructor === Array &&
|
|
foreignViewModel[relation.foreignIdKey].includes(model.id)
|
|
);
|
|
this.sortViewModels(foreignViewModels, relation.order);
|
|
return foreignViewModels;
|
|
} else if (relation.type === 'O2M') {
|
|
const foreignViewModels = this.viewModelStoreService.filter(
|
|
relation.foreignViewModel,
|
|
foreignViewModel =>
|
|
foreignViewModel[relation.foreignIdKey] && foreignViewModel[relation.foreignIdKey] === model.id
|
|
);
|
|
this.sortViewModels(foreignViewModels, relation.order);
|
|
return foreignViewModels;
|
|
} else if (relation.type === 'M2O') {
|
|
const foreignViewModel = this.viewModelStoreService.find(
|
|
relation.foreignViewModel,
|
|
_foreignViewModel =>
|
|
_foreignViewModel[relation.foreignIdKey] &&
|
|
_foreignViewModel[relation.foreignIdKey] === model.id
|
|
);
|
|
return foreignViewModel;
|
|
}
|
|
} else if (isGenericRelationDefinition(relation)) {
|
|
const contentObject = this.viewModelStoreService.get<BaseViewModel>(
|
|
model[relation.ownContentObjectDataKey].collection,
|
|
model[relation.ownContentObjectDataKey].id
|
|
);
|
|
if (contentObject && relation.isVForeign(contentObject)) {
|
|
return contentObject;
|
|
}
|
|
} else if (isCustomRelationDefinition(relation)) {
|
|
return relation.get(model, viewModel);
|
|
}
|
|
}
|
|
|
|
public handleCachedRelation<V extends BaseViewModel>(
|
|
property: string,
|
|
target: V,
|
|
model: BaseModel,
|
|
viewModel: BaseViewModel,
|
|
relation: RelationDefinition
|
|
): any {
|
|
let result: any;
|
|
|
|
const cacheProperty = '__' + property;
|
|
const cachePropertyChangeIds = cacheProperty + '_cids';
|
|
let cached: boolean = cacheProperty in target;
|
|
let changeIds: CacheChangeIds | null = null;
|
|
if (cached) {
|
|
result = target[cacheProperty];
|
|
changeIds = target[cachePropertyChangeIds];
|
|
}
|
|
|
|
if (!isCustomRelationDefinition(relation)) {
|
|
if (cached) {
|
|
cached = this.relationCacheService.checkCacheValidity(changeIds);
|
|
}
|
|
|
|
if (!cached) {
|
|
result = this.handleRelation(model, viewModel, relation) as BaseViewModel | BaseViewModel[];
|
|
|
|
if (result) {
|
|
// Cache it:
|
|
target[cacheProperty] = result;
|
|
const newChangeIds = {};
|
|
if (Array.isArray(result)) {
|
|
result.forEach(
|
|
(_vm: BaseViewModel) =>
|
|
(newChangeIds[_vm.elementId] = this.relationCacheService.query(_vm.elementId))
|
|
);
|
|
} else {
|
|
newChangeIds[result.elementId] = this.relationCacheService.query(result.elementId);
|
|
}
|
|
target[cachePropertyChangeIds] = newChangeIds;
|
|
} else {
|
|
delete target[cacheProperty];
|
|
}
|
|
}
|
|
} else {
|
|
// Custom relations
|
|
const obj = relation.getCacheObjectToCheck(viewModel);
|
|
if (cached) {
|
|
if (obj && changeIds && changeIds[obj.elementId]) {
|
|
cached = this.relationCacheService.query(obj.elementId) === changeIds[obj.elementId];
|
|
} else {
|
|
cached = false;
|
|
}
|
|
}
|
|
|
|
if (!cached) {
|
|
result = this.handleRelation(model, viewModel, relation);
|
|
|
|
if (result && obj) {
|
|
target[cacheProperty] = result;
|
|
target[cachePropertyChangeIds] = {};
|
|
target[cachePropertyChangeIds][obj.elementId] = this.relationCacheService.query(obj.elementId);
|
|
} else {
|
|
delete target[cachePropertyChangeIds];
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sorts the array of foreign view models in the given view models for the given relation.
|
|
*/
|
|
public sortViewModels(viewModels: BaseViewModel[], order?: string): void {
|
|
viewModels.sort((a: BaseViewModel, b: BaseViewModel) => {
|
|
if (!order || a[order] === b[order]) {
|
|
return a.id - b.id;
|
|
} else {
|
|
return a[order] - b[order];
|
|
}
|
|
});
|
|
}
|
|
|
|
public createViewModel<M extends BaseModel, V extends BaseViewModel>(
|
|
model: M,
|
|
viewModelCtor: ViewModelConstructor<V>,
|
|
relationsByKey: { [key: string]: RelationDefinition },
|
|
nestedModelDescriptors: NestedModelDescriptors
|
|
): V {
|
|
let viewModel = new viewModelCtor(model);
|
|
viewModel = new Proxy(viewModel, {
|
|
get: (target: V, property) => {
|
|
let result: any;
|
|
const _model: M = target.getModel();
|
|
const relation = typeof property === 'string' ? relationsByKey[property] : null;
|
|
|
|
if (property in target) {
|
|
const descriptor = Object.getOwnPropertyDescriptor(viewModelCtor.prototype, property);
|
|
if (descriptor && descriptor.get) {
|
|
result = descriptor.get.bind(viewModel)();
|
|
} else {
|
|
result = target[property];
|
|
}
|
|
} else if (property in _model) {
|
|
result = _model[property];
|
|
} else if (relation) {
|
|
result = this.handleCachedRelation(<any>property, target, _model, viewModel, relation);
|
|
}
|
|
return result;
|
|
}
|
|
});
|
|
|
|
// set nested models
|
|
(nestedModelDescriptors[model.collectionString] || []).forEach(
|
|
(modelDescriptor: ModelDescriptor<BaseModel, BaseViewModel>) => {
|
|
const nestedModels = (model[modelDescriptor.ownKey] || []).map((nestedModel: object) => {
|
|
return new modelDescriptor.foreignModel(nestedModel);
|
|
});
|
|
const nestedViewModels = nestedModels.map(nestedModel => {
|
|
const nestedViewModel = this.createViewModel(
|
|
nestedModel,
|
|
modelDescriptor.foreignViewModel,
|
|
modelDescriptor.relationDefinitionsByKey,
|
|
nestedModelDescriptors
|
|
);
|
|
Object.keys(modelDescriptor.titles || {}).forEach(name => {
|
|
nestedViewModel[name] = () => modelDescriptor.titles[name](nestedViewModel);
|
|
});
|
|
return nestedViewModel;
|
|
});
|
|
this.sortViewModels(nestedViewModels, modelDescriptor.order);
|
|
|
|
viewModel[modelDescriptor.ownKey] = nestedViewModels;
|
|
}
|
|
);
|
|
return viewModel;
|
|
}
|
|
}
|