From 20f79c570b0415c67c40c9277ac51c3cc9c8f1b9 Mon Sep 17 00:00:00 2001 From: James Scharett Date: Thu, 6 Jun 2019 06:56:50 +0200 Subject: [PATCH] Roughed in some basic layout logic --- .../example-layouts/jsf-layout-basic.json | 4 + .../src/lib/json-schema-form.component.ts | 3 +- .../src/lib/layout-item.data.ts | 14 ++ .../src/lib/layout.service.spec.ts | 22 +++- .../src/lib/layout.service.ts | 121 +++++++++++++++++- 5 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 projects/ngx-json-schema-form/src/assests/example-layouts/jsf-layout-basic.json create mode 100644 projects/ngx-json-schema-form/src/lib/layout-item.data.ts diff --git a/projects/ngx-json-schema-form/src/assests/example-layouts/jsf-layout-basic.json b/projects/ngx-json-schema-form/src/assests/example-layouts/jsf-layout-basic.json new file mode 100644 index 0000000..7ac531a --- /dev/null +++ b/projects/ngx-json-schema-form/src/assests/example-layouts/jsf-layout-basic.json @@ -0,0 +1,4 @@ +[ { + "key": "comment", + "type": "textarea" +} ] diff --git a/projects/ngx-json-schema-form/src/lib/json-schema-form.component.ts b/projects/ngx-json-schema-form/src/lib/json-schema-form.component.ts index 6808254..d0c85cb 100644 --- a/projects/ngx-json-schema-form/src/lib/json-schema-form.component.ts +++ b/projects/ngx-json-schema-form/src/lib/json-schema-form.component.ts @@ -55,8 +55,7 @@ export class JsonSchemaFormComponent implements OnChanges, OnInit { } private initializeLayout() { - // TODO - return this; + this.layoutService.layout = cloneDeep(this.layout); } private initializeSchema(): void { diff --git a/projects/ngx-json-schema-form/src/lib/layout-item.data.ts b/projects/ngx-json-schema-form/src/lib/layout-item.data.ts new file mode 100644 index 0000000..b4d66be --- /dev/null +++ b/projects/ngx-json-schema-form/src/lib/layout-item.data.ts @@ -0,0 +1,14 @@ +export interface LayoutItem { + id: string; + options: any; + $ref?: any; + // arrayItem?; + // arrayItemType?; + // dataPointer?; + // dataType?; + items?: Array; + key?: string; + // name?; + // recursiveReference?; + type?: string; +} diff --git a/projects/ngx-json-schema-form/src/lib/layout.service.spec.ts b/projects/ngx-json-schema-form/src/lib/layout.service.spec.ts index bc6bd8f..780eddc 100644 --- a/projects/ngx-json-schema-form/src/lib/layout.service.spec.ts +++ b/projects/ngx-json-schema-form/src/lib/layout.service.spec.ts @@ -1,4 +1,6 @@ -import { TestBed } from '@angular/core/testing'; +import { inject, TestBed } from '@angular/core/testing'; + +import basicJSONLayout from '../assests/example-layouts/jsf-layout-basic.json'; import { LayoutService } from './layout.service'; @@ -15,4 +17,22 @@ describe('LayoutService', () => { const service: LayoutService = TestBed.get(LayoutService); expect(service).toBeTruthy(); }); + + it('should augment layout with derived props', inject([LayoutService], (service: LayoutService) => { + service.layout = basicJSONLayout; + expect(service.layout).toEqual([{ + id: jasmine.any(String), + key: basicJSONLayout[0].key, + options: {}, + type: basicJSONLayout[0].type + }]); + })); + + it('should remove unknown props from layout', inject([LayoutService], (service: LayoutService) => { + service.layout = [{cat: 1}]; + expect(service.layout).toEqual([{ + id: jasmine.any(String), + options: {} + }]); + })); }); diff --git a/projects/ngx-json-schema-form/src/lib/layout.service.ts b/projects/ngx-json-schema-form/src/lib/layout.service.ts index b8da5fb..9cbd1c7 100644 --- a/projects/ngx-json-schema-form/src/lib/layout.service.ts +++ b/projects/ngx-json-schema-form/src/lib/layout.service.ts @@ -1,12 +1,131 @@ import { Injectable } from '@angular/core'; +import { clone, pick, uniqueId } from 'lodash'; + +import { LayoutItem } from './layout-item.data'; + @Injectable() export class LayoutService { private _layout: Array = []; set layout(value: Array) { - this._layout = value; + this._layout = LayoutService.buildLayout(value); } get layout(): Array { return this._layout; } + + private static buildLayout(layout: Array): Array { + // let hasSubmitButton = !JsonPointer.get(jsf, '/formOptions/addSubmit'); + const formLayout: Array = LayoutService.mapLayout(layout, (layoutItem, index, layoutPointer) => { + const newNode: LayoutItem = { + id: uniqueId(), + options: {}, + ...pick(layoutItem, ['key', 'type']) + }; + // Dropped code to push invalid props into options + // Dropped code to convert widget to type + // Dropped code to convert options.legend to options.title + + return newNode; + // if (isObject(layoutItem)) { + + // if (!hasOwn(newNode.options, 'validationMessages')) { + // if (hasOwn(newNode.options, 'errorMessages')) { + // newNode.options.validationMessages = newNode.options.errorMessages; + // delete newNode.options.errorMessages; + + // // Convert Angular Schema Form (AngularJS) 'validationMessage' to + // // Angular JSON Schema Form 'validationMessages' + // // TV4 codes from https://github.com/geraintluff/tv4/blob/master/source/api.js + // } else if (hasOwn(newNode.options, 'validationMessage')) { + // if (typeof newNode.options.validationMessage === 'string') { + // newNode.options.validationMessages = newNode.options.validationMessage; + // } else { + // newNode.options.validationMessages = {}; + // Object.keys(newNode.options.validationMessage).forEach(key => { + // const code = key + ''; + // const newKey = + // code === '0' ? 'type' : + // code === '1' ? 'enum' : + // code === '100' ? 'multipleOf' : + // code === '101' ? 'minimum' : + // code === '102' ? 'exclusiveMinimum' : + // code === '103' ? 'maximum' : + // code === '104' ? 'exclusiveMaximum' : + // code === '200' ? 'minLength' : + // code === '201' ? 'maxLength' : + // code === '202' ? 'pattern' : + // code === '300' ? 'minProperties' : + // code === '301' ? 'maxProperties' : + // code === '302' ? 'required' : + // code === '304' ? 'dependencies' : + // code === '400' ? 'minItems' : + // code === '401' ? 'maxItems' : + // code === '402' ? 'uniqueItems' : + // code === '500' ? 'format' : code + ''; + // newNode.options.validationMessages[newKey] = newNode.options.validationMessage[key]; + // }); + // } + // delete newNode.options.validationMessage; + // } + // } + // } 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; + // } + }); + + return formLayout; + } + + /** + * '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 + * skipped without error, and the function will still return all non-null items. + * + * @param layout - the layout to map + * @param function - the funciton to invoke on each element + * @param layoutPointer - the layoutPointer to layout, inside rootLayout + * @param rootLayout - the root layout, which conatins layout + * @return the mapped layout + */ + private static mapLayout(layout: Array, fn: (v: any, i?: number, l?: any, p?: any) => LayoutItem, + layoutPointer: string|Array = '', rootLayout: Array = layout): Array { + const indexPad = 0; + + return layout.reduce((mappedLayout: Array, item: LayoutItem, index: number) => { + const realIndex: number = +index + indexPad; + const newLayoutPointer = `${layoutPointer}/${realIndex}`; + let newNode: LayoutItem = clone(item); + let newLayout: Array = mappedLayout; + // Note: removed logic to convert tabs to items and items to [items] + // if (item.items) { + // newNode.items = LayoutService.mapLayout(item.items, fn, `${newLayoutPointer}/items`, rootLayout); + // } + newNode = fn(newNode, realIndex, newLayoutPointer, rootLayout); + // if (isNil(newNode)) { + // indexPad -= 1; + // } else { + // if (Array.isArray(newNode)) { indexPad += newNode.length - 1; } + newLayout = mappedLayout.concat(newNode); + // } + + return newLayout; + }, []); + } }