UNPKG

angular2-json-schema-form

Version:
781 lines (756 loc) 30.4 kB
import * as _ from 'lodash'; import { buildFormGroupTemplate, checkInlineType, copy, forEach, getControl, getFromSchema, getInputType, hasOwn, inArray, isArray, isEmpty, isInputRequired, isNumber, isObject, isDefined, isString, JsonPointer, toTitleCase, updateInputOptions } from './index'; /** * Layout function library: * * buildLayout: Builds a complete layout from an input layout and schema * * buildLayoutFromSchema: Builds a complete layout entirely from an input schema * * mapLayout: * * buildTitleMap: */ /** * 'buildLayout' function * * @param {any} jsf * @return {any[]} */ export function buildLayout(jsf: any, widgetLibrary: any): any[] { let hasSubmitButton = !JsonPointer.get(jsf, '/globalOptions/addSubmit'); let formLayout = mapLayout(jsf.layout, (layoutItem, index, layoutPointer) => { let currentIndex: number = index; let newNode: any = {}; if (isObject(layoutItem)) { newNode = layoutItem; } else if (JsonPointer.isJsonPointer(layoutItem)) { newNode.dataPointer = layoutItem; } else if (isString(layoutItem)) { newNode.key = layoutItem; } else { console.error('buildLayout error: Form layout element not recognized:'); console.error(layoutItem); return null; } Object.assign(newNode, { _id: _.uniqueId(), layoutPointer: layoutPointer.replace(/\/\d+/g, '/-'), options: {}, }); let itemSchema: any = null; // If newNode does not have a dataPointer, try to find an equivalent if (!hasOwn(newNode, 'dataPointer')) { // If newNode has a key, change it to a dataPointer if (hasOwn(newNode, 'key')) { if (newNode.key === '*') { newNode.dataPointer = newNode.key; } else if (JsonPointer.isJsonPointer(newNode.key)) { newNode.dataPointer = JsonPointer.compile(newNode.key); } else { newNode.dataPointer = JsonPointer.compile(JsonPointer.parseObjectPath(newNode.key), '-'); } delete newNode.key; // If newNode is an array, search for dataPointer in child nodes } else if (hasOwn(newNode, 'type') && newNode.type.slice(-5) === 'array') { const findDataPointer = (items) => { if (items === null || typeof items !== 'object') { return; } if (hasOwn(items, 'dataPointer')) { return items.dataPointer; } if (isArray(items.items)) { for (let item of items.items) { if (hasOwn(item, 'dataPointer') && item.dataPointer.indexOf('/-') !== -1 ) { return item.dataPointer; } if (hasOwn(item, 'items')) { const searchItem = findDataPointer(item); if (searchItem) { return searchItem; } } } } }; const childDataPointer = findDataPointer(newNode); if (childDataPointer) { newNode.dataPointer = childDataPointer.slice(0, childDataPointer.lastIndexOf('/-')); } } } if (hasOwn(newNode, 'dataPointer')) { if (newNode.dataPointer === '*') { return buildLayoutFromSchema( jsf, widgetLibrary, newNode.layoutPointer.slice(0, -2) ); } newNode.dataPointer = JsonPointer.toGenericPointer(newNode.dataPointer, jsf.arrayMap); const LastKey: string = JsonPointer.toKey(newNode.dataPointer); if (isString(LastKey) && LastKey !== '-') { newNode.name = LastKey; } if (!jsf.dataMap.has(newNode.dataPointer)) { jsf.dataMap.set(newNode.dataPointer, new Map); } else if ( jsf.dataMap.get(newNode.dataPointer).has('schemaPointer') ) { itemSchema = JsonPointer.get( jsf.schema, jsf.dataMap.get(newNode.dataPointer).get('schemaPointer') ); } else { itemSchema = getFromSchema(jsf.schema, newNode.dataPointer); } if (itemSchema) { if (!hasOwn(newNode, 'type')) { newNode.type = getInputType(itemSchema, newNode); } else if (!widgetLibrary.hasWidget(newNode.type)) { const oldWidgetType = newNode.type; newNode.type = getInputType(itemSchema, newNode); console.error('error: widget type "' + oldWidgetType + '" not found in library. Replacing with "' + newNode.type + '".'); } else { newNode.type = checkInlineType(newNode.type, itemSchema, newNode); } newNode.dataType = itemSchema.type; updateInputOptions(newNode, itemSchema, jsf); // Present checkboxes as single control, rather than array if (newNode.type === 'checkboxes' && hasOwn(itemSchema, 'items')) { updateInputOptions(newNode, itemSchema.items, jsf); } else if (itemSchema.type === 'array' && hasOwn(itemSchema, 'items')) { if (isArray(itemSchema.items)) { newNode.tupleItems = itemSchema.items.length; if (hasOwn(itemSchema, 'additionalItems')) { newNode.listItems = hasOwn(itemSchema, 'maxItems') ? itemSchema.maxItems - itemSchema.items.length : true; } else { newNode.listItems = false; } } else { newNode.tupleItems = false; newNode.listItems = itemSchema.maxItems || true; } } if (!newNode.options.title && newNode.options.legend) { newNode.options.title = newNode.options.legend; } else if (!newNode.options.title && !/^\d+$/.test(newNode.name)) { newNode.options.title = toTitleCase(newNode.name.replace(/_/g, ' ')); } if (isInputRequired(jsf.schema, newNode.dataPointer)) { newNode.options.required = true; jsf.fieldsRequired = true; } } else { // TODO: create item in FormGroup model from layout key (?) updateInputOptions(newNode, {}, jsf); } if (hasOwn(newNode.options, 'copyValueTo')) { if (typeof newNode.options.copyValueTo === 'string') { newNode.options.copyValueTo = [newNode.options.copyValueTo]; } if (isArray(newNode.options.copyValueTo)) { newNode.options.copyValueTo = newNode.options.copyValueTo.map(item => JsonPointer.isJsonPointer(item) ? JsonPointer.compile(item) : JsonPointer.compile(JsonPointer.parseObjectPath(item), '-') ); } } newNode.widget = widgetLibrary.getWidget(newNode.type); jsf.dataMap.get(newNode.dataPointer).set('inputType', newNode.type); jsf.dataMap.get(newNode.dataPointer).set('widget', newNode.widget); if (newNode.dataType === 'array' && hasOwn(newNode, 'items')) { if (newNode.options.required && !newNode.minItems) { newNode.minItems = 1; } let arrayPointer: string = newNode.dataPointer + '/-'; if (!jsf.dataMap.has(arrayPointer)) { jsf.dataMap.set(arrayPointer, new Map); } jsf.dataMap.get(arrayPointer).set('inputType', 'section'); // Fix insufficiently nested array item groups if (newNode.items.length > 1) { let arrayItemGroup = []; let arrayItemGroupTemplate = []; let newIndex = 0; for (let i = newNode.items.length - 1, l = 0; i >= l; i--) { let subItem = newNode.items[i]; if (hasOwn(subItem, 'dataPointer') && subItem.dataPointer.slice(0, arrayPointer.length) === arrayPointer ) { let arrayItem = newNode.items.splice(i, 1)[0]; let arrayItemTemplate = mapLayout([arrayItem], templateItem => { templateItem.layoutPointer = templateItem.layoutPointer .replace(newNode.layoutPointer, newNode.layoutPointer + '/items/-'); return templateItem; })[0]; arrayItemGroupTemplate.unshift(arrayItemTemplate); arrayItem.dataPointer = newNode.dataPointer + '/-' + arrayItem.dataPointer.slice(arrayPointer.length); arrayItem.layoutPointer = newNode.layoutPointer + '/items/-/items/-'; arrayItemGroup.unshift(arrayItem); newIndex++; } else { subItem.arrayItem = true; // TODO: Check schema to get arrayItemType and removable subItem.arrayItemType = 'list'; subItem.removable = newNode.options.removable || !newNode.options.minItems; } } if (arrayItemGroup.length) { newNode.items.push({ arrayItem: true, items: arrayItemGroup, layoutPointer: newNode.layoutPointer + '/items/-', options: { arrayItemType: newNode.tupleItems > newNode.items.length ? 'tuple' : 'list', removable: newNode.options.removable !== false && (newNode.options.minItems || 0) <= newNode.items.length, }, dataPointer: newNode.dataPointer + '/-', type: 'fieldset', widget: widgetLibrary.getWidget('fieldset'), }); } } else { newNode.items[0].arrayItem = true; if (!newNode.items[0].dataPointer) { newNode.items[0].dataPointer = JsonPointer.toGenericPointer(arrayPointer, jsf.arrayMap); } if (newNode.options.minItems) { newNode.items[0].options.removable = false; } else if (!JsonPointer.has(newNode, '/items/0/options/removable')) { newNode.items[0].options.removable = true; } newNode.items[0].options.arrayItemType = newNode.tupleItems ? 'tuple' : 'list'; } // TODO: check maxItems to verify adding new items is OK, and check // additionalItems for whether there is a different schema for new items if (newNode.options.addable !== false) { jsf.layoutRefLibrary[arrayPointer] = _.cloneDeep(newNode.items[newNode.items.length - 1]); const initialNodeData = JsonPointer.get(jsf.initialValues, newNode.dataPointer); if (isArray(initialNodeData) && initialNodeData.length > newNode.items.length ) { for (let i = newNode.items.length, l = initialNodeData.length; i < l; i++) { newNode.items .push(_.cloneDeep(jsf.layoutRefLibrary[arrayPointer])); } } let buttonText: string = 'Add'; if (newNode.options.title) { buttonText += ' ' + newNode.options.title; } else if (newNode.name && !/^\d+$/.test(newNode.name)) { buttonText += ' ' + toTitleCase(newNode.name.replace(/_/g, ' ')); // If newNode doesn't have a title, look for title of parent array item } else { const parentSchema = getFromSchema(jsf.schema, newNode.dataPointer, true); if (hasOwn(parentSchema, 'title')) { buttonText += ' to ' + parentSchema.title; } } const dataPointer = JsonPointer.toGenericPointer(arrayPointer, jsf.arrayMap); let newNodeRef: any = { arrayItem: true, dataPointer: dataPointer, layoutPointer: newNode.layoutPointer + '/items/-', listItems: newNode.listItems, options: { arrayItemType: 'list', removable: !!newNode.options.removable, title: buttonText, }, tupleItems: newNode.tupleItems, type: '$ref', widget: widgetLibrary.getWidget('$ref'), $ref: dataPointer, }; if (isDefined(newNode.options.maxItems)) { newNodeRef.options.maxItems = newNode.options.maxItems; } if (isString(JsonPointer.get(newNode, '/style/add'))) { newNodeRef.options.fieldStyle = newNode.style.add; delete newNode.style.add; if (isEmpty(newNode.style)) { delete newNode.style; } } newNode.items.push(newNodeRef); } } else { newNode.arrayItem = false; } } else if (hasOwn(newNode, 'type') || hasOwn(newNode, 'items')) { const parentType: string = JsonPointer.get(jsf.layout, layoutPointer, 0, -2).type; if (!hasOwn(newNode, 'type')) { newNode.type = inArray(parentType, ['tabs', 'tabarray']) ? 'tab' : 'fieldset'; } newNode.arrayItem = parentType === 'array'; newNode.widget = widgetLibrary.getWidget(newNode.type); updateInputOptions(newNode, {}, jsf); } if (newNode.type === 'submit') { hasSubmitButton = true; } return newNode; }); if (!hasSubmitButton) { formLayout.push({ options: { title: 'Submit', }, type: 'submit', widget: widgetLibrary.getWidget('submit'), }); } return formLayout; } /** * 'buildLayoutFromSchema' function * * @param {any} jsf - * @param {number = 0} layoutIndex - * @param {string = ''} layoutPointer - * @param {string = ''} schemaPointer - * @param {string = ''} dataPointer - * @param {boolean = false} arrayItem - * @param {string = null} arrayItemType - * @param {boolean = null} removable - * @param {boolean = false} forRefLibrary - * @return {any} */ export function buildLayoutFromSchema( jsf: any, widgetLibrary: any, layoutPointer: string = '', schemaPointer: string = '', dataPointer: string = '', arrayItem: boolean = false, arrayItemType: string = null, removable: boolean = null, forRefLibrary: boolean = false ): any { const schema = JsonPointer.get(jsf.schema, schemaPointer); if (!hasOwn(schema, 'type') && !hasOwn(schema, 'x-schema-form') && !hasOwn(schema, '$ref')) { return null; } const newNodeType: string = getInputType(schema); let newNode: any = { _id: _.uniqueId(), arrayItem: arrayItem, dataPointer: JsonPointer.toGenericPointer(dataPointer, jsf.arrayMap), dataType: schema.type || (hasOwn(schema, '$ref') ? '$ref' : null), layoutPointer: layoutPointer.replace(/\/\d+/g, '/-') || '/-', options: {}, type: newNodeType, widget: widgetLibrary.getWidget(newNodeType), }; const lastDataKey = JsonPointer.toKey(newNode.dataPointer); if (lastDataKey !== '-') { newNode.name = lastDataKey; } if (newNode.arrayItem) { newNode.options.arrayItemType = arrayItemType; newNode.options.removable = removable; } if (dataPointer !== '') { if (!jsf.dataMap.has(newNode.dataPointer)) { jsf.dataMap.set(newNode.dataPointer, new Map); } jsf.dataMap.get(newNode.dataPointer).set('schemaPointer', schemaPointer); jsf.dataMap.get(newNode.dataPointer).set('inputType', newNode.type); jsf.dataMap.get(newNode.dataPointer).set('widget', newNode.widget); } updateInputOptions(newNode, schema, jsf); if (!newNode.options.title && newNode.options.legend) { newNode.options.title = newNode.options.legend; } else if (!newNode.options.title && newNode.name && !/^\d+$/.test(newNode.name)) { newNode.options.title = toTitleCase(newNode.name.replace(/_/g, ' ')); } if (newNode.dataType === 'object') { let newFieldset: any[] = []; let newKeys: string[] = []; if (isObject(schema.properties)) { newKeys = isArray(schema.properties['ui:order']) ? schema['properties']['ui:order'] : Object.keys(schema['properties']); } else if (hasOwn(schema, 'additionalProperties')) { return null; // TODO: Figure out what to do with additionalProperties // ... possibly provide a way to enter both key names and values? } for (let key of newKeys) { if (hasOwn(schema.properties, key)) { let newLayoutPointer: string; if (newNode.layoutPointer === '' && !forRefLibrary) { newLayoutPointer = '/-'; } else { newLayoutPointer = newNode.layoutPointer + '/items/-'; } let innerItem = buildLayoutFromSchema( jsf, widgetLibrary, newLayoutPointer, schemaPointer + '/properties/' + key, dataPointer + '/' + key, false, null, null, forRefLibrary ); if (innerItem) { if (isInputRequired(schema, '/' + key)) { innerItem.options.required = true; jsf.fieldsRequired = true; } newFieldset.push(innerItem); } } } // if (dataPointer === '' && !forRefLibrary) { // newNode = newFieldset; // } else { newNode.items = newFieldset; // } } else if (newNode.dataType === 'array') { newNode.items = []; let templateArray: any[] = []; if (!forRefLibrary) { const templateControl: any = getControl(jsf.formGroupTemplate, dataPointer); if (hasOwn(templateControl, 'controls')) { templateArray = templateControl['controls']; } } if (!newNode.minItems && isInputRequired(jsf.schema, schemaPointer)) { newNode.minItems = 1; } const minItems: number = newNode.minItems || 0; const maxItems: number = newNode.maxItems || 1000000; if (isDefined(newNode.options.removable)) { removable = newNode.options.removable; } else if (!isDefined(removable)) { removable = true; } let additionalItems: any = null; if (isArray(schema.items)) { // 'items' is an array = tuple items newNode.tupleItems = schema.items.length; if (hasOwn(schema, 'additionalItems')) { newNode.listItems = hasOwn(schema, 'maxItems') ? schema.maxItems - schema.items.length : true; } else { newNode.listItems = false; } newNode.items = _.filter(_.map(schema.items, (item: any, i) => { return buildLayoutFromSchema( jsf, widgetLibrary, newNode.layoutPointer + '/items/-', schemaPointer + '/items/' + i, dataPointer + '/' + i, true, 'tuple', removable && i >= minItems, forRefLibrary ); })); if (newNode.items.length < maxItems && hasOwn(schema, 'additionalItems') && isObject(schema.additionalItems) ) { // 'additionalItems' is an object = additional list items (after tuple items) if (newNode.items.length < templateArray.length) { for (let i = newNode.items.length, l = templateArray.length; i < l; i++) { newNode.items.push(buildLayoutFromSchema( jsf, widgetLibrary, newNode.layoutPointer + '/items/-', schemaPointer + '/additionalItems', dataPointer + '/' + i, true, 'list', removable && i >= minItems, forRefLibrary )); } } else if (newNode.items.length > templateArray.length) { for (let i = templateArray.length, l = newNode.items.length; i < l; i++) { templateArray.push(buildFormGroupTemplate( jsf, null, false, schemaPointer + '/additionalItems', dataPointer + '/' + i, JsonPointer.toControlPointer(jsf.formGroupTemplate, dataPointer + '/' + i) )); } } if (newNode.items.length < maxItems && newNode.options.addable !== false && JsonPointer.get(newNode.items[newNode.items.length - 1], '/type') !== '$ref' ) { additionalItems = buildLayoutFromSchema( jsf, widgetLibrary, newNode.layoutPointer + '/items/-', schemaPointer + '/additionalItems', dataPointer + '/-', true, 'list', removable, forRefLibrary ); } } } else { // 'items' is an object = list items only (no tuple items) newNode.tupleItems = false; newNode.listItems = schema.maxItems || true; for (let i = 0, l = Math.max(templateArray.length, minItems, 1); i < l; i++) { newNode.items.push(buildLayoutFromSchema( jsf, widgetLibrary, newNode.layoutPointer + '/items/-', schemaPointer + '/items', dataPointer + '/' + i, true, 'list', removable && i >= minItems, forRefLibrary )); } if (newNode.items.length < maxItems && newNode.options.addable !== false && JsonPointer.get(newNode.items[newNode.items.length - 1], '/type') !== '$ref' ) { additionalItems = buildLayoutFromSchema( jsf, widgetLibrary, newNode.layoutPointer + '/items/-', schemaPointer + '/items', dataPointer + '/-', true, 'list', removable, forRefLibrary ); } } // If addable items, save to layoutRefLibrary, and add $ref item to layout if (additionalItems) { jsf.layoutRefLibrary[dataPointer + '/-'] = additionalItems; delete jsf.layoutRefLibrary[dataPointer + '/-']['key']; delete jsf.layoutRefLibrary[dataPointer + '/-']['name']; let buttonText: string = 'Add '; if (additionalItems.options.title) { buttonText += additionalItems.options.title; } else if (schema.title) { buttonText += 'to ' + schema.title; } else { buttonText += 'to ' + toTitleCase(JsonPointer.toKey(dataPointer).replace(/_/g, ' ')); } let newNodeRef: any = { arrayItem: true, dataPointer: dataPointer + '/-', layoutPointer: newNode.layoutPointer + '/items/-', listItems: newNode.listItems, options: { arrayItemType: 'list', removable: false, title: buttonText, }, tupleItems: newNode.tupleItems, type: '$ref', widget: widgetLibrary.getWidget('$ref'), $ref: dataPointer + '/-', }; if (isDefined(newNode.options.maxItems)) { newNodeRef.options.maxItems = newNode.options.maxItems; } newNode.items.push(newNodeRef); } else if ( JsonPointer.get(newNode.items[newNode.items.length - 1], '/type') === '$ref' ) { Object.assign(newNode.items[newNode.items.length - 1], { listItems: newNode.listItems, tupleItems: newNode.tupleItems, }); if ( isNumber(JsonPointer.get(jsf.schema, schemaPointer, 0, -1).maxItems) ) { newNode.items[newNode.items.length - 1].options.maxItems = JsonPointer.get(jsf.schema, schemaPointer, 0, -1).maxItems; } } } else if (newNode.dataType === '$ref') { const schemaRef: string = JsonPointer.compile(schema.$ref); let buttonText: string = 'Add'; if (newNode.options.title) { buttonText += ' ' + newNode.options.title; } else if (newNode.name && !/^\d+$/.test(newNode.name)) { buttonText += ' ' + toTitleCase(newNode.name.replace(/_/g, ' ')); // If newNode doesn't have a title, look for title of parent array item } else if ( hasOwn(JsonPointer.get(jsf.schema, schemaPointer, 0, -1), 'title') ) { buttonText += ' to ' + JsonPointer.get(jsf.schema, schemaPointer, 0, -1).title; } Object.assign(newNode, { recursiveReference: true, widget: widgetLibrary.getWidget('$ref'), $ref: schemaRef, }); Object.assign(newNode.options, { removable: false, title: buttonText, }); if (isNumber(JsonPointer.get(jsf.schema, schemaPointer, 0, -1).maxItems)) { newNode.options.maxItems = JsonPointer.get(jsf.schema, schemaPointer, 0, -1).maxItems; } // Build dataRecursiveRefMap let genericDataPointer = JsonPointer.toGenericPointer(newNode.dataPointer, jsf.arrayMap); // TODO: Replace the following by checking to see if the parent element is // an array, and only removing the index if allowed genericDataPointer = genericDataPointer.replace(/\/\d+$/, '/-'); if (!forRefLibrary) { // Is schema $ref a subset of dataPointer? // If yes, map dataPointer and schema $ref as a recursive reference if (JsonPointer.isSubPointer(schemaRef, genericDataPointer)) { jsf.dataRecursiveRefMap.set(genericDataPointer, schemaRef); // If no, add a partial reference now, so a full reference can be added later } else { jsf.dataRecursiveRefMap.set(schemaRef, genericDataPointer); } // If partial reference already exists, // use current and previous dataPointers to create a full reference } else if (jsf.dataRecursiveRefMap.has(schemaRef) && !jsf.dataRecursiveRefMap.has(jsf.dataRecursiveRefMap.get(schemaRef)) ) { if (genericDataPointer === jsf.dataRecursiveRefMap.get(schemaRef).slice(-genericDataPointer.length) ) { jsf.dataRecursiveRefMap.set( jsf.dataRecursiveRefMap.get(schemaRef), jsf.dataRecursiveRefMap.get(schemaRef).slice(0, -genericDataPointer.length) ); } else { jsf.dataRecursiveRefMap.set( jsf.dataRecursiveRefMap.get(schemaRef) + genericDataPointer, jsf.dataRecursiveRefMap.get(schemaRef) ); } } // Add layout template to layoutRefLibrary if (!hasOwn(jsf.layoutRefLibrary, schemaRef)) { // Set to null first to prevent recursive reference from causing endless loop jsf.layoutRefLibrary[schemaRef] = null; const newLayout: any = buildLayoutFromSchema( jsf, widgetLibrary, '', schemaRef, '', newNode.arrayItem, newNode.arrayItemType, true, true ); if (newLayout) { jsf.layoutRefLibrary[schemaRef] = newLayout; } else { delete jsf.layoutRefLibrary[schemaRef]; } } } return newNode; } /** * 'mapLayout' function * * Creates a new layout by running each element in an existing layout through * an iteratee. Recursively maps within array elements 'items' and 'tabs'. * The iteratee is invoked with four arguments: (value, index, layout, path) * * THe returned layout may be longer (or shorter) then the source layout. * * If an item from the source layout returns multiple items (as '*' usually will), * this function will keep all returned items in-line with the surrounding items. * * If an item from the source layout causes an error and returns null, it is * simply skipped, and the function will still return all non-null items. * * @param {any[]} layout - the layout to map * @param {(v: any, i?: number, l?: any, p?: string) => any} * function - the funciton to invoke on each element * @param {any = ''} layoutPointer - the layoutPointer to layout, inside rootLayout * @param {any[] = layout} rootLayout - the root layout, which conatins layout * @return {[type]} */ export function mapLayout( layout: any[], fn: (v: any, i?: number, p?: string, l?: any) => any, layoutPointer: string = '', rootLayout: any[] = layout ): any[] { let indexPad: number = 0; let newLayout: any[] = []; forEach(layout, (item, index) => { let realIndex = +index + indexPad; let newLayoutPointer = layoutPointer + '/' + realIndex; let newNode: any = copy(item); let itemsArray: any[] = []; if (isObject(item)) { if (hasOwn(item, 'tabs')) { item.items = item.tabs; delete item.tabs; } if (hasOwn(item, 'items')) { itemsArray = isArray(item.items) ? item.items : [item.items]; } } if (itemsArray.length) { newNode.items = mapLayout(itemsArray, fn, newLayoutPointer + '/items', rootLayout); } newNode = fn(newNode, realIndex, newLayoutPointer, rootLayout); if (!isDefined(newNode)) { indexPad--; } else { if (isArray(newNode)) { indexPad += newNode.length - 1; } newLayout = newLayout.concat(newNode); } }); return newLayout; }; /** * 'buildTitleMap' function * * @param {any} titleMap - * @param {any} enumList - * @param {boolean = false} fieldRequired - * @return {{name: string, value: any}[]} */ export function buildTitleMap( titleMap: any, enumList: any, fieldRequired: boolean = true ): { name: string, value: any }[] { let newTitleMap: { name: string, value: any }[] = []; let hasEmptyValue: boolean = false; if (titleMap) { if (isArray(titleMap)) { if (enumList) { for (let i of Object.keys(titleMap)) { if (isObject(titleMap[i])) { // JSON Form / Angular Schema Form style const value: any = titleMap[i].value; if (enumList.indexOf(value) !== -1) { const name: string = titleMap[i].name; newTitleMap.push({ name, value }); if (!value) { hasEmptyValue = true; } } } else if (isString(titleMap[i])) { // React Jsonschema Form style if (i < enumList.length) { const name: string = titleMap[i]; const value: any = enumList[i]; newTitleMap.push({ name, value }); if (!value) { hasEmptyValue = true; } } } } } else { // If array titleMap and no enum list, just return the titleMap newTitleMap = titleMap; if (!fieldRequired) { hasEmptyValue = !!newTitleMap.filter(i => !i.value).length; } } } else if (enumList) { // Alternate JSON Form style, with enum list for (let i of Object.keys(enumList)) { let value: any = enumList[i]; if (hasOwn(titleMap, value)) { let name: string = titleMap[value]; newTitleMap.push({ name, value }); if (!value) { hasEmptyValue = true; } } } } else { // Alternate JSON Form style, without enum list for (let value of Object.keys(titleMap)) { let name: string = titleMap[value]; newTitleMap.push({ name, value }); if (!value) { hasEmptyValue = true; } } } } else if (enumList) { // Build map from enum list alone for (let i of Object.keys(enumList)) { let name: string = enumList[i]; let value: any = enumList[i]; newTitleMap.push({ name, value}); if (!value) { hasEmptyValue = true; } } } else { // If no titleMap and no enum list, return default map of boolean values newTitleMap = [{ name: 'True', value: true }, { name: 'False', value: false }]; } if (!fieldRequired && !hasEmptyValue) { newTitleMap.unshift({ name: '', value: '' }); } return newTitleMap; }