132 lines
4.3 KiB
JavaScript
132 lines
4.3 KiB
JavaScript
/*
|
|
This class was pulled from the official D&D 5e Foundry System
|
|
|
|
Original Authors:
|
|
Jeff Hitchcock (https://github.com/arbron)
|
|
Andrew (https://github.com/aaclayton)
|
|
|
|
Accessed on: 2024/01/06
|
|
|
|
Source Code: https://github.com/foundryvtt/dnd5e/blob/677c6ae127aa885bd4cb9a83f95180a655a8623b/module/data/fields.mjs#L264
|
|
*/
|
|
|
|
|
|
/**
|
|
* A subclass of ObjectField that represents a mapping of keys to the provided DataField type.
|
|
*
|
|
* @param {DataField} model The class of DataField which should be embedded in this field.
|
|
* @param {MappingFieldOptions} [options={}] Options which configure the behavior of the field.
|
|
* @property {string[]} [initialKeys] Keys that will be created if no data is provided.
|
|
* @property {MappingFieldInitialValueBuilder} [initialValue] Function to calculate the initial value for a key.
|
|
* @property {boolean} [initialKeysOnly=false] Should the keys in the initialized data be limited to the keys provided
|
|
* by `options.initialKeys`?
|
|
*/
|
|
export class MappingField extends foundry.data.fields.ObjectField {
|
|
constructor(model, options) {
|
|
if ( !(model instanceof foundry.data.fields.DataField) ) {
|
|
throw new Error("MappingField must have a DataField as its contained element");
|
|
}
|
|
super(options);
|
|
|
|
/**
|
|
* The embedded DataField definition which is contained in this field.
|
|
* @type {DataField}
|
|
*/
|
|
this.model = model;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
static get _defaults() {
|
|
return foundry.utils.mergeObject(super._defaults, {
|
|
initialKeys: null,
|
|
initialValue: null,
|
|
initialKeysOnly: false
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
_cleanType(value, options) {
|
|
Object.entries(value).forEach(([k, v]) => value[k] = this.model.clean(v, options));
|
|
return value;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
getInitialValue(data) {
|
|
let keys = this.initialKeys;
|
|
const initial = super.getInitialValue(data);
|
|
if ( !keys || !foundry.utils.isEmpty(initial) ) return initial;
|
|
if ( !(keys instanceof Array) ) keys = Object.keys(keys);
|
|
for ( const key of keys ) initial[key] = this._getInitialValueForKey(key);
|
|
return initial;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Get the initial value for the provided key.
|
|
* @param {string} key Key within the object being built.
|
|
* @param {object} [object] Any existing mapping data.
|
|
* @returns {*} Initial value based on provided field type.
|
|
*/
|
|
_getInitialValueForKey(key, object) {
|
|
const initial = this.model.getInitialValue();
|
|
return this.initialValue?.(key, initial, object) ?? initial;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
_validateType(value, options={}) {
|
|
if ( foundry.utils.getType(value) !== "Object" ) throw new Error("must be an Object");
|
|
const errors = this._validateValues(value, options);
|
|
if ( !foundry.utils.isEmpty(errors) ) throw new foundry.data.fields.ModelValidationError(errors);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Validate each value of the object.
|
|
* @param {object} value The object to validate.
|
|
* @param {object} options Validation options.
|
|
* @returns {Object<Error>} An object of value-specific errors by key.
|
|
*/
|
|
_validateValues(value, options) {
|
|
const errors = {};
|
|
for ( const [k, v] of Object.entries(value) ) {
|
|
const error = this.model.validate(v, options);
|
|
if ( error ) errors[k] = error;
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
initialize(value, model, options={}) {
|
|
if ( !value ) return value;
|
|
const obj = {};
|
|
const initialKeys = (this.initialKeys instanceof Array) ? this.initialKeys : Object.keys(this.initialKeys ?? {});
|
|
const keys = this.initialKeysOnly ? initialKeys : Object.keys(value);
|
|
for ( const key of keys ) {
|
|
const data = value[key] ?? this._getInitialValueForKey(key, value);
|
|
obj[key] = this.model.initialize(data, model, options);
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
_getField(path) {
|
|
if ( path.length === 0 ) return this;
|
|
else if ( path.length === 1 ) return this.model;
|
|
path.shift();
|
|
return this.model._getField(path);
|
|
}
|
|
};
|