daggerheart/module/data/fields/mappingField.mjs
WBHarry 2a4777f1a0
[Fix] Itemlink Redux Revengeance (#399)
* Small random fixes

* Added use of ItemLinkFields

* Multiclass levelup fixes

* Fixed our onCreate methods unintentionally being run on all clients

* Remade apps handling

* Added for all class items and subclass

* Restored foreignDocumentUuidField

* Improved

* PR fxies

* Fixed tooltip enrichment

* .

* Reverted silly change
2025-07-26 00:37:30 +02:00

128 lines
4.7 KiB
JavaScript

/**
* 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 default 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;
model.parent = this;
}
/* -------------------------------------------- */
/** @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]) => {
if (k.startsWith('-=')) return;
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)) {
const failure = new foundry.data.validation.DataModelValidationFailure();
failure.elements = Object.entries(errors).map(([id, failure]) => ({ id, failure }));
throw failure.asError();
}
}
/* -------------------------------------------- */
/**
* Validate each value of the object.
* @param {object} value The object to validate.
* @param {object} options Validation options.
* @returns {Record<string, Error>} An object of value-specific errors by key.
*/
_validateValues(value, options) {
const errors = {};
for (const [k, v] of Object.entries(value)) {
if (k.startsWith('-=')) continue;
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);
}
}