@eclipse-scout/core
Version:
Eclipse Scout runtime
162 lines (148 loc) • 6.29 kB
text/typescript
/*
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {App, objects, strings, TypeDescriptor} from '../index';
import $ from 'jquery';
export interface DefaultValuesBootstrapOptions {
url?: string;
}
export const defaultValues = {
/**
* map of "objectType" -> { defaultValuesObject }
* @internal
*/
_defaults: {},
/**
* map of "objectType" -> [ "objectType", "parentObjectType", ..., "topLevelObjectType" ]
* @internal
*/
_objectTypeHierarchyFlat: {},
bootstrap(options?: DefaultValuesBootstrapOptions): JQuery.Promise<any> {
options = options || {};
let defaultOptions = {
url: 'defaultValues'
};
options = $.extend({}, defaultOptions, options);
// Load default value configuration from server (and cache it)
return $.ajaxJson(options.url)
.then(response => App.handleJsonError(options.url, response))
.then(defaultValues.init.bind(this));
},
init(data: any) {
// Store defaults
defaultValues._objectTypeHierarchyFlat = {};
defaultValues._defaults = data.defaults || {};
// Generate object type hierarchy
let objectTypeHierarchy = data.objectTypeHierarchy || {};
defaultValues._generateObjectTypeHierarchyRec(objectTypeHierarchy, undefined, defaultValues._objectTypeHierarchyFlat);
// For all object types in the defaults that don't have a hierarchy yet, add a dummy hierarchy with one element
Object.keys(defaultValues._defaults).forEach(objectType => {
if (!defaultValues._objectTypeHierarchyFlat[objectType]) {
defaultValues._objectTypeHierarchyFlat[objectType] = [objectType];
}
});
},
/** @internal */
_generateObjectTypeHierarchyRec(json: any, currentParentObjectTypes: any, targetMap: any) {
if (!json) {
return;
}
if (!targetMap) {
throw new Error('Argument \'targetMap\' must not be null');
}
Object.keys(json).forEach(objectType => {
let newCurrentParentObjectTypes = [objectType];
if (currentParentObjectTypes) {
newCurrentParentObjectTypes = newCurrentParentObjectTypes.concat(currentParentObjectTypes);
}
if (typeof json[objectType] === 'object') {
defaultValues._generateObjectTypeHierarchyRec(json[objectType], newCurrentParentObjectTypes, targetMap);
}
// Store current result
if (targetMap[objectType]) {
throw new Error('Object type \'' + objectType + '\' has ambiguous parent object types.');
}
targetMap[objectType] = newCurrentParentObjectTypes;
}, this);
},
/**
* Applies the defaults for the given object type to the given object. Properties
* are only set if they don't exist yet. The argument 'objectType' is optional
* if the object has a property of the same name. If the object is an array,
* the defaults are applied to each of the elements.
*/
applyTo(object: Record<string, any> | Record<string, any>[], objectType?: string) {
if (Array.isArray(object)) {
for (let i = 0; i < object.length; i++) {
defaultValues.applyTo(object[i], objectType);
}
} else if (typeof object === 'object') {
objectType = objectType || object.objectType;
if (objectType) {
if (typeof objectType !== 'string') {
let objectTypeShort = (objectType + '').substring(0, 80);
throw new Error('objectType has to be a string but is a ' + typeof objectType + ' ObjectType: ' + objectTypeShort);
}
defaultValues._applyToInternal(object, objectType);
}
}
},
/** @internal */
_applyToInternal(object: Record<string, any>, objectType: string) {
let objectTypeHierarchy = defaultValues._objectTypeHierarchyFlat[objectType];
if (!objectTypeHierarchy) {
// Remove model variant and try again
let objectInfo = TypeDescriptor.parse(objectType);
objectType = objectInfo.objectType.toString();
objectTypeHierarchy = defaultValues._objectTypeHierarchyFlat[objectType];
}
if (!objectTypeHierarchy) {
// Unknown type, nothing to apply
return;
}
for (let i = 0; i < objectTypeHierarchy.length; i++) {
let t = objectTypeHierarchy[i];
let defaults = defaultValues._defaults[t];
defaultValues._extendWithDefaults(object, defaults);
}
},
/** @internal */
_extendWithDefaults(object: Record<string, any>, defaults: Record<string, any>) {
if (object === undefined || defaults === undefined) {
return;
}
Object.keys(defaults).forEach(prop => {
// Support for "pseudo" default values: If a property name in the default values definition
// starts with a "~" character, the defined object will _not_ be applied as a default value
// for a non-existing property, but inner properties of that object will be applied to an
// existing object.
let realProp = prop;
if (strings.startsWith(prop, '~')) {
realProp = prop.substring(1);
}
// If property does not exist, set the default value and return.
if (object[realProp] === undefined) {
object[realProp] = objects.valueCopy(defaults[realProp]);
} else if (objects.isObject(object[realProp]) && objects.isObject(defaults[prop])) {
// Special case: "default objects". If the property value is an object and default
// value is also an object, extend the property value instead of replacing it.
defaultValues._extendWithDefaults(object[realProp], defaults[prop]);
} else if (Array.isArray(object[realProp]) && objects.isObject(defaults[prop])) {
// Special case: "array of default objects": If the property value is an array of objects and
// the default value is an object, extend each object in the array with the default value.
let objectArray = object[realProp];
for (let i = 0; i < objectArray.length; i++) {
if (objects.isObject(objectArray[i])) {
defaultValues._extendWithDefaults(objectArray[i], defaults[prop]);
}
}
}
});
}
};