import { BaseSchemaModel, CrudStoreItemsModel, CrudStoreSchemaModel, CrudStoreSchemaPropertiesModel } from '../stores/types/CrudStoreModel'; import { remove } from 'lodash'; export default class CrudHelpers { public static initialValues(schema: CrudStoreSchemaModel, query?: { [k: string]: string }): CrudStoreItemsModel | undefined { let item = this.initialValueForObject(schema.properties); if (item && query) { item = this.populateQueryIntoCrudItem(schema.properties, item, query); } return item; } private static initialValueForObject(properties: CrudStoreSchemaPropertiesModel): CrudStoreItemsModel | undefined { if (!properties) { return; } const item: CrudStoreItemsModel = { $type: properties.$type.enum[0] }; for (const [property, value] of Object.entries(properties)) { const typedValue = value as BaseSchemaModel; switch (typedValue.type) { case 'string': item[property] = this.initialStringValues(typedValue); break; case 'integer': case 'number': item[property] = this.initialNumberValues(typedValue); break; case 'boolean': item[property] = typedValue.default ?? false; break; case 'array': item[property] = this.initialArrayValues(typedValue); break; case 'object': item[property] = this.initialObjectValues(typedValue); // Objects that are created need to be expanded by default. The validation only kicks in when their schema-page-component // is expanded. Objects with isNew will expand when they're just created. // This works for nested objects as well as array items that are automatically created. // The isNew property is not saved to the backend. if (item[property]) item[property].isNew = true; } } return item; } private static initialStringValues(property: BaseSchemaModel): string | Date | undefined { if (property.default && typeof property.default === 'string') { return property.default; } if (!property.required || property.isHidden) { return; } if (property.format === 'datetime-picker') { return new Date(); } return ''; } private static initialNumberValues(property: BaseSchemaModel): number | undefined { if (property.default && typeof property.default === 'number') { return property.default; } if (!property.required) { return; } return 0; } private static initialArrayValues(property: BaseSchemaModel): any[] { if (!property.minLength || !property.items) { return []; } if (property.items.oneOf || property.items.$ref) { return []; } return Array(property.minLength) .fill(0) .map(() => this.initialValueForObject(property.items!.properties!)); } private static initialObjectValues(property: BaseSchemaModel): CrudStoreItemsModel | undefined { if (!property.required) { return; } if (property.oneOf) { return; } if (property.$ref) { return; } if (!property.properties) { return; } return this.initialValueForObject(property.properties); } private static populateQueryIntoCrudItem( properties: CrudStoreSchemaPropertiesModel, item: CrudStoreItemsModel, query: { [k: string]: string } ): CrudStoreItemsModel { for (const [property, value] of Object.entries(query)) { if (!Object.keys(properties).includes(property)) { continue; } item[property] = this.convertQueryValueToType(properties[property] as BaseSchemaModel, value); } return item; } private static convertQueryValueToType(property: BaseSchemaModel, value: string) { switch (property.type) { case 'string': if (property.format === 'datetime-picker') { return new Date(value); } return value; case 'boolean': return value.toLowerCase() === 'true'; case 'number': return Number.parseFloat(value); case 'array': return []; default: return; } } public static getDisplayFieldForSchemaProperties(schemaProperties: Object | undefined): string | null { if (!schemaProperties) { return null; } // first look if a isDisplayTitle property has been set on this entity for (const entry of Object.entries(schemaProperties)) { const [key, value] = entry; if (value.isDisplayTitle === true) { return key; } } // otherwise look for the first string input on this entity. for (const entry of Object.entries(schemaProperties)) { const [key, value] = entry; if ( value.format === 'string-input' && !value.isHidden // hide fields like 'language', ) { return key; } } // fallback to Guid of a CmsItem return 'id'; } public static getStoreNameForReference(reference: string): string | null { const matches = reference.match(/\/items\/(.*)\/schema/); // In TypeInfoService, the reference URL is constructed with TypeName, which is in PascalCase. // Therefore, we need to convert the StoreName to camelCase before utilizing. // ex. '/items/Product/schema' --> 'product', '/items/CmsUser/schema' --> 'cmsUser' return matches && matches[1] ? matches[1].charAt(0).toLowerCase() + matches[1].slice(1) : null; } public static getDisplayFieldValueForReference(store: any, reference: string, guid: string) { const storeName = this.getStoreNameForReference(reference); if (!storeName) { return guid; } const getterName = `${storeName}/getItemByGuid`; const foundItem = typeof store.getters[getterName] === 'function' ? store.getters[getterName](guid) : null; const displayField = store.state[storeName] ? this.getDisplayFieldForSchemaProperties(store.state[storeName].schema.properties) : null; return foundItem && displayField ? foundItem[displayField] : guid; } public static addOrUpdateCrudItem(items: CrudStoreItemsModel[], item: CrudStoreItemsModel) { const existingItem = items.find((i) => i.id === item.id); // always update existing item with possible newer version if (existingItem) { this.removeCrudItem(items, item.id); } items.push(item); } public static removeCrudItem(items: CrudStoreItemsModel[], id: string) { remove(items, (i) => i.id === id); } }