q42-cms-components
Version:
Front-end package that provides a UI on top of the QMS back-end
194 lines (174 loc) • 6.41 kB
JavaScript
import moment from 'moment';
import { crudStores } from '../tools/crud-stores.js';
export function initialValue(schema, isProp) {
if (schema.default) {
return schema.default;
}
if (schema.enum) {
return schema.enum[0];
}
switch (schema.type) {
case 'string':
if (!schema.required)
return null;
switch (schema.format) {
case 'datetime-picker':
return new Date();
default:
return '';
}
case 'integer':
case 'number':
if (!schema.required) {
return null;
}
return 0;
case 'boolean':
return false;
case 'null':
return null;
case 'array':
return schema.minLength && !schema.items.oneOf && !schema.items.$ref
? Array(schema.minLength).fill(0).map(() => initialValue(schema.items))
: [];
case 'object':
default: {
if (!schema.required && isProp) {
return null;
}
if (schema.oneOf && isProp) {
return null;
}
if (schema.$ref && isProp) {
return null;
}
if (!schema.properties) {
console.error(`Missing initializer for type ${schema.type}`, schema);
throw `Missing initialiser for type ${schema.type}`;
}
const value = {$type: null}; // $type needs to be the first prop in the object, to be compatible with the backend parser
for (const p in schema.properties) {
value[p] = initialValue(schema.properties[p], true);
}
// 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.
value.isNew = true;
return value;
}
}
}
export function initialWithQuery(schema, query) {
const initial = initialValue(schema);
if (query instanceof Object) {
for (const propKey in query) {
// We support propName.nestedProp syntax in query string, so we need to get a reference to the containing object.
const result = getContainingObj(schema, initial, propKey);
if (result) {
const { containingObj, propName, propSchema } = result;
// alters some property initial
containingObj[propName] = convertQueryValue(propSchema, query[propKey]);
}
}
}
return initial;
}
function convertQueryValue(propSchema, queryValue) {
switch (propSchema.type) {
case 'string':
if (propSchema.format === 'datetime-picker') {
return moment(queryValue).toDate();
}
return queryValue;
case 'boolean':
return queryValue.toLowerCase() === 'true';
case 'number':
return Number.parseFloat(queryValue);
case 'array':
console.warn('Ignoring query string value for array', queryValue);
return [];
default:
console.warn(`Ignoring query string value for ${propSchema.type}`, queryValue);
return null;
}
}
function getContainingObj(schema, obj, propKey) {
if (schema.type !== 'object' || !schema.properties) {
return null;
}
const propPath = propKey.split('.');
let containingObj = obj;
let propName = propPath.shift();
if (!(propName in schema.properties)) {
return null;
}
let propSchema = schema.properties[propName];
// check if we can find the propKey in the schema
// if not, we'll skip the property
while (propPath.length) {
const nextPropName = propPath.shift();
switch (propSchema.type) {
case 'object':
if (!(nextPropName in propSchema.properties)) {
return null;
}
if (containingObj[propName] === null || containingObj[propName] === undefined) {
containingObj[propName] = containingObj[propName] || initialValue(propSchema);
} else if (!(containingObj[propName] instanceof Object)) {
return null;
}
containingObj = containingObj[propName];
propSchema = propSchema.properties[nextPropName];
break;
default:
console.warn(`unsupported schema type ${schema.type} at ${propName} in ${propKey}`);
return null;
}
propName = nextPropName;
}
return {
containingObj,
propName,
propSchema,
};
}
export function isSimpleProp(schema) {
// null check for schema.items is for the case List<Guid> with [MappedTo(typeof([CmsItem]))] which has schema.type - array, but doesn't have items.
return !(schema.type === 'object' || (schema.type === 'array' && schema.items && schema.items.type === 'object'));
}
export function getNameForRef($ref, guid) {
const store = crudStores.getStoreBySchemaRef($ref);
const item = store.indexedItems[guid];
return item ? item[store.displayField] : guid;
}
// gets the first usable string to display as the title of the component
export function getComponentTitle(component, schemaProperties) {
for (const propName in schemaProperties) {
const propSchema = schemaProperties[propName];
if (!propSchema.isHidden && propSchema.type === 'string') {
const titleValue = component[propName]; // the value of the current string property
// if cms item, where titleValue is a single Guid. We like to show a friendly name
if (propSchema.$ref) {
return getNameForRef(propSchema.$ref, titleValue);
}
// if the property has options it's an enum, we don't want to show the raw enum value. We'll look for displayName if it's there.
else if (propSchema.options) {
// look for the currently selected option
const activeOption = propSchema.options.find(option => option.key === titleValue);
// if the option is found use it's displayName, or just the raw value if displayName isn't used.
return (activeOption && activeOption.displayName) || titleValue;
} else {
return titleValue;
}
}
}
}
export function getCmsPageDisplayTitle(version) {
return version.page && version.page.url
|| version.metaTitle;
}
export function getCmsItemDisplayTitle(item) {
return item.title
|| item.name;
}