UNPKG

@davebaol/angular-formio-editor

Version:

Angular component integrating Form.io builder and renderer with a json editor

1,359 lines (1,342 loc) 70.4 kB
import { __decorate } from 'tslib'; import { Input, ViewChild, Component as Component$1, EventEmitter, Output, NgModule } from '@angular/core'; import { BsModalService, ModalModule } from 'ngx-bootstrap/modal'; import { FormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { FormioModule } from 'angular-formio'; import { AlertModule } from 'ngx-bootstrap/alert'; import JsonEditor from 'jsoneditor'; /* This is heavily inspired by https://github.com/schnittstabil/merge-options */ const { hasOwnProperty, toString } = Object.prototype; const { propertyIsEnumerable } = Object; const globalThis = this; const defaultMergeOpts = { ignoreUndefined: false }; const isPlainObject = (value) => { if (toString.call(value) !== '[object Object]') { return false; } const prototype = Object.getPrototypeOf(value); return prototype === null || prototype === Object.prototype; }; const ɵ0 = isPlainObject; const defineProperty = (obj, name, value) => { Object.defineProperty(obj, name, { value, writable: true, enumerable: true, configurable: true }); }; const ɵ1 = defineProperty; const getEnumerableOwnPropertyKeys = (value) => { const keys = []; for (const key in value) { if (hasOwnProperty.call(value, key)) { keys.push(key); } } if (Object.getOwnPropertySymbols) { const symbols = Object.getOwnPropertySymbols(value); for (const symbol of symbols) { if (propertyIsEnumerable.call(value, symbol)) { keys.push(symbol); } } } return keys; }; const ɵ2 = getEnumerableOwnPropertyKeys; const clone = (value) => { if (Array.isArray(value)) { return cloneArray(value); } if (isPlainObject(value)) { return clonePlainObject(value); } return value; }; const cloneArray = (array) => { const result = array.slice(0, 0); getEnumerableOwnPropertyKeys(array).forEach(key => { defineProperty(result, key, clone(array[key])); }); return result; }; const ɵ3 = cloneArray; const clonePlainObject = (obj) => { const result = Object.getPrototypeOf(obj) === null ? Object.create(null) : {}; getEnumerableOwnPropertyKeys(obj).forEach(key => { defineProperty(result, key, clone(obj[key])); }); return result; }; const ɵ4 = clonePlainObject; const mergeKeys = (merged, source, keys, config) => { keys.forEach(key => { if (typeof source[key] === 'undefined' && config.ignoreUndefined) { return; } // Do not recurse into prototype chain of merged if (key in merged && merged[key] !== Object.getPrototypeOf(merged)) { defineProperty(merged, key, _merge(merged[key], source[key], config)); } else { defineProperty(merged, key, clone(source[key])); } }); return merged; }; const ɵ5 = mergeKeys; // tslint:disable-next-line:variable-name const _merge = (merged, source, config) => { if (!isPlainObject(source) || !isPlainObject(merged)) { return clone(source); } return mergeKeys(merged, source, getEnumerableOwnPropertyKeys(source), config); }; const ɵ6 = _merge; const merge = (...options) => { const config = _merge(clone(defaultMergeOpts), (this !== globalThis && this) || {}, defaultMergeOpts); let merged = { _: {} }; for (const option of options) { if (option === undefined) { continue; } if (!isPlainObject(option)) { throw new TypeError('`' + option + '` is not a plain Object'); } merged = _merge(merged, { _: option }, config); } return merged._; }; const { hasOwnProperty: hasOwnProperty$1 } = Object.prototype; // ------------------------------- // SCHEMAS // ------------------------------- class Schema { constructor(component) { this.component = component; this.conditions = []; this.required = false; this.dataType = {}; if (component && component.formioComponent) { if (component.formioComponent.conditional) { // Add non-empty condition from the component const cnd = component.formioComponent.conditional; if (typeof cnd.show === 'boolean' && cnd.when) { // console.log(component.formioComponent.type, "Pushing condition") const c = { show: cnd.show, when: cnd.when, eq: cnd.eq }; c.key = this.generateSingleConditionKey(c); this.conditions.push(c); } } if (component.formioComponent.validate) { this.required = component.formioComponent.validate.required; } } } /*private*/ generateSingleConditionKey(condition) { return JSON.stringify(condition); } /*private*/ generateConditionsKey() { return JSON.stringify(this.conditions, (k, v) => k === 'key' ? undefined : v); } prepareConditions() { // Ensure the array has unique conditions this.conditions = this.conditions.sort((c1, c2) => c1.key.compare(c2.key)) .filter((x, i, a) => i === 0 || x.key !== a[i - 1].key); this.conditions.key = this.generateConditionsKey(); } shrink() { for (let k in this.properties) { if (hasOwnProperty$1.call(this.properties, k)) { let propSchema = this.properties[k]; if (propSchema instanceof MergeableObjectSchema && propSchema.component && propSchema.component.shrinkable()) { // console.log('Shrink', propSchema.component.formioComponent.type); propSchema.shrink(); delete this.properties[k]; // Remove shribkable schema from parent this.merge(propSchema); // merge its properties and conditions with parent } } } return this; } // Subclasses overriding this method MUST call super.toJsonSchema() toJsonSchema() { return Object.assign({}, this.dataType); } } class ObjectSchema extends Schema { constructor(component) { super(component); this.dataType.type = 'object'; this.properties = {}; } addProperty(name, schema, required) { this.properties[name] = schema; schema.required = required; return this; } toJsonSchema() { const jsonSchema = super.toJsonSchema(); jsonSchema.properties = {}; const required = []; const condPropMap = {}; const conditionsMap = {}; for (let pk in this.properties) { if (hasOwnProperty$1.call(this.properties, pk)) { const childSchema = this.properties[pk]; if (childSchema.conditions.length === 0) { if (childSchema.required) { required.push(pk); } jsonSchema.properties[pk] = childSchema.toJsonSchema(); continue; } childSchema.prepareConditions(); const ck = childSchema.conditions.key; conditionsMap[ck] = childSchema.conditions; if (!(ck in condPropMap)) { condPropMap[ck] = {}; } condPropMap[ck][pk] = childSchema; } } // Add required to jsonSchema if not empty if (required.length > 0) { jsonSchema.required = required; } // Generate allOf from conditional properties const allOf = []; for (let ck in condPropMap) { if (hasOwnProperty$1.call(condPropMap, ck)) { const conds = conditionsMap[ck]; const _if = { properties: conds.reduce((acc, c) => { acc[c.when] = c.show ? { const: c.eq } : { not: { const: c.eq } }; return acc; }, {}) }; const then = { required: [], properties: {} }; for (let pk in condPropMap[ck]) { if (hasOwnProperty$1.call(condPropMap[ck], pk)) { const childSchema = condPropMap[ck][pk]; if (childSchema.required) { then.required.push(pk); } then.properties[pk] = childSchema.toJsonSchema(); } } // Remove empty required if (then.required.length === 0) { delete then.required; } // Add if/then to allOf allOf.push({ if: _if, then: then }); } } // Add allOf to jsonSchema if not empty if (allOf.length > 0) { jsonSchema.allOf = allOf; } return jsonSchema; } } class MergeableObjectSchema extends ObjectSchema { constructor(component) { super(component); } merge(...sources) { const targetProps = this.properties; for (let i = 0, len = sources.length; i < len; i++) { const source = sources[i]; if (source instanceof MergeableObjectSchema) { // merge properties const sourceProps = source.properties; for (const key in sourceProps) { if (hasOwnProperty$1.call(sourceProps, key)) { // Append source schema conditions to the conditions of its sub-schemas Array.prototype.push.apply(sourceProps[key].conditions, source.conditions); // Merge properties recursively if (targetProps[key] && sourceProps[key] instanceof MergeableObjectSchema) { targetProps[key].merge(sourceProps[key]); } else { targetProps[key] = sourceProps[key]; } } } } } return this; } } class ArraySchema extends Schema { constructor(component, items) { super(component); this.dataType.type = 'array'; this.dataType.items = items; } toJsonSchema() { const jsonSchema = super.toJsonSchema(); jsonSchema.items = this.dataType.items.toJsonSchema(); return jsonSchema; } } class PrimitiveSchema extends Schema { constructor(component, type) { super(component); this.dataType.type = type; } } class BooleanSchema extends PrimitiveSchema { constructor(component) { super(component, 'boolean'); } } class NumberSchema extends PrimitiveSchema { constructor(component) { super(component, 'number'); if (component && component.formioComponent && component.formioComponent.validate) { const validate = component.formioComponent.validate; if (validate.min || typeof validate.min === 'number') this.dataType.minimum = Number(validate.min); if (validate.max || typeof validate.max === 'number') this.dataType.maximum = Number(validate.max); if (validate.integer) this.dataType.type = 'integer'; } } } class StringSchema extends PrimitiveSchema { constructor(component) { super(component, 'string'); if (component && component.formioComponent && component.formioComponent.validate) { const validate = component.formioComponent.validate; if (validate.minLength) this.dataType.minLength = Number(validate.minLength); if (validate.maxLength) this.dataType.maxLength = Number(validate.maxLength); if (validate.pattern) this.dataType.pattern = validate.pattern; } } } class EnumSchema extends Schema { constructor(component, values) { super(component); this.dataType.enum = values; } } // ------------------------------- // COMPONENT BASE CLASSES // ------------------------------- class Component { constructor(formioComponent) { this.formioComponent = formioComponent; } schema() { throw new Error('Subclasses of \'Component\' have to implement the method \'schema\'!'); } // Layout components are view-only components. From resource perspective, they are to be // shrinked, because they don't have any value neither implicit nor expressed by user. // So they don't contribute to the underlying resource because their 'API key' does not // match any field inside the resource itself. // Howewer, shrink process propagates component's condition (show/when/eq) to child components. shrinkable() { return !(this.formioComponent && this.formioComponent.input); } } class AtomicComponent extends Component { schema() { const schema = this.baseSchema(); if (this.formioComponent.multiple) { if (this.formioComponent.validate && !this.formioComponent.validate.required) { // With multiple values enabled the component can generate null items if required is false schema.dataType = { anyOf: [schema.dataType, { type: 'null' }] }; } return new ArraySchema(this, schema); } return schema; } baseSchema() { throw new Error('Subclasses of \'AtomicComponent\' have to implement the method \'baseSchema\'!'); } isDefaultCastToString() { return false; // cast defaults to 'auto' } cast(val, to) { switch (to) { case 'boolean': return val.toLowerCase() === 'true'; case 'number': return Number(val); case 'object': return JSON.parse(val); case 'string': return val; case 'auto': default: // Either autotype or string if (to !== 'auto' && this.isDefaultCastToString()) return val; if (val === "true") return true; if (val === "false") return false; if (val === "") return val; var v = Number(val); return isNaN(v) ? val : v; } } } class CompoundComponent extends Component { schema() { let schema = new MergeableObjectSchema(this); this.childrenSchema(schema); return schema.shrink(); } /*prorected*/ children() { return this.formioComponent.components; } // Subclasses can override this method to provide a default class // for children that don't have a type /*prorected*/ defaultChildClass() { return undefined; } /*prorected*/ childrenSchema(parentSchema) { const children = this.children(); for (let i = 0, len = children.length; i < len; i++) { let c = children[i]; // console.log(this.formioComponent.type, 'child', c) const type = MAP[c.type] || this.defaultChildClass(); if (type) { let schema = new (type)(c).schema(); const required = c.validate && c.validate.required; // Dotted key means nested schema const keyParts = c.key.split('.'); for (let j = keyParts.length - 1; j > 0; j--) { schema = new MergeableObjectSchema(undefined).addProperty(keyParts[j], schema, required); } parentSchema.merge(new MergeableObjectSchema(undefined).addProperty(keyParts[0], schema, required)); } else { // console.log(this.formioComponent.type, ": skipping child with unknown type", c.type); } } return parentSchema; } } // ------------------------------- // ABSTRACT COMPONENT CLASSES // ------------------------------- class StringComponent extends AtomicComponent { baseSchema() { return new StringSchema(this); } } class EnumComponent extends AtomicComponent { constructor(formioComponent, ...additionalValuesIfNotRequired) { super(formioComponent); this.additionalValuesIfNotRequired = additionalValuesIfNotRequired; } // This is needed because different components take values from different path values() { throw new Error('Subclasses of \'EnumComponent\' have to implement the method \'values\'!'); } baseSchema() { const values = this.values().map(v => this.cast(v.value, this.formioComponent.dataType)); if (this.formioComponent && this.formioComponent.validate && !this.formioComponent.validate.required) { Array.prototype.push.apply(values, this.additionalValuesIfNotRequired); } return new EnumSchema(this, values); } } // ------------------------------- // BASIC COMPONENTS // ------------------------------- class CheckboxComponent extends AtomicComponent { baseSchema() { return new BooleanSchema(this); } } class NumberComponent extends AtomicComponent { baseSchema() { return new NumberSchema(this); } } class PasswordComponent extends StringComponent { } class RadioComponent extends EnumComponent { constructor(formioComponent) { // Empty string and null are valid values if the component is not required super(formioComponent, '', null); } values() { return this.formioComponent.values; } } class SelectComponent extends EnumComponent { constructor(formioComponent) { // Empty string and null are valid values if the component is not required super(formioComponent, '', null); } values() { return this.formioComponent.data.values; } schema() { const schema = super.schema(); // If multiple values are enabled ensure uniqueness if (schema instanceof ArraySchema) { schema.dataType.uniqueItems = true; } return schema; } isDefaultCastToString() { return true; // cast defaults to 'string' } } class SelectBoxesComponent extends AtomicComponent { baseSchema() { let schema = new ObjectSchema(this); schema.dataType.additionalProperties = false; let values = this.formioComponent.values .forEach(v => schema.addProperty(v.value, new BooleanSchema(undefined), true)); if (this.formioComponent.validate && !this.formioComponent.validate.required) { // This is needed for compatibility. // Formio adds a boolean property with name "" when the component is not required. // The property itself must not be required schema.addProperty('', new BooleanSchema(undefined), false); } return schema; } } class TextAreaComponent extends StringComponent { } class TextFieldComponent extends StringComponent { } // ------------------------------- // ADVANCED COMPONENTS // ------------------------------- class DateTimeComponent extends StringComponent { } class EmailComponent extends StringComponent { } class UrlComponent extends StringComponent { } class TagsComponent extends AtomicComponent { schema() { return this.formioComponent.storeas === 'array' ? new ArraySchema(this, this.baseSchema()) : this.baseSchema(); } baseSchema() { return new StringSchema(this); } } // ------------------------------- // LAYOUT COMPONENTS // ------------------------------- class ColumnsComponent extends CompoundComponent { // Determines children from columns. // Children are calculated lazily and cached into this instance. children() { if (!this.components) { this.components = []; this.formioComponent.columns.forEach(col => Array.prototype.push.apply(this.components, col.components)); } return this.components; } } class ContentComponent extends CompoundComponent { children() { return []; // This component never has children } } class FieldSetComponent extends CompoundComponent { } class PanelComponent extends CompoundComponent { } class TableComponent extends CompoundComponent { // Determines children from table's rows and columns. // Children are calculated lazily and cached into this instance. children() { if (!this.components) { this.components = []; this.formioComponent.rows.forEach(row => { row.forEach(col => Array.prototype.push.apply(this.components, col.components)); }); } return this.components; } } class TabsComponent extends CompoundComponent { /*prorected*/ defaultChildClass() { return CompoundComponent; // Needed because children don't have a type :( } } class WellComponent extends CompoundComponent { } // ------------------------------- // DATA COMPONENTS // ------------------------------- class EditGridComponent extends CompoundComponent { schema() { return new ArraySchema(this, super.schema()); } } // ------------------------------- // FORM COMPONENT // ------------------------------- class FormComponent extends CompoundComponent { } const MAP = { editgrid: EditGridComponent, checkbox: CheckboxComponent, columns: ColumnsComponent, content: ContentComponent, datetime: DateTimeComponent, email: EmailComponent, fieldset: FieldSetComponent, number: NumberComponent, password: PasswordComponent, panel: PanelComponent, radio: RadioComponent, select: SelectComponent, selectboxes: SelectBoxesComponent, table: TableComponent, tabs: TabsComponent, tags: TagsComponent, textarea: TextAreaComponent, textfield: TextFieldComponent, url: UrlComponent, well: WellComponent }; function generateFormJsonSchema(form) { return new FormComponent(form).schema().toJsonSchema(); } // tslint:disable:object-literal-key-quotes quotemark semicolon var schema = { "title": "Form", "description": "Object containing a form.io form", "type": "object", "required": ["components"], "properties": { "display": { "title": "Display mode", "description": "The given name.", "enum": ["form", "wizard"] }, "components": { "$ref": "components" } } }; // tslint:disable:object-literal-key-quotes quotemark semicolon var components = { "title": "Component List", "description": "The list of all components", "type": "array", "items": { "$ref": "component" } }; // tslint:disable:object-literal-key-quotes quotemark semicolon var componentStrict = { "title": "Component", "description": "Object containing a form.io component", "type": "object", "required": ["type", "key", "input"], "properties": { "type": { "title": "Component Type", "description": "The type of this component", "type": "string" }, "key": { "title": "Component Key", "description": "The API key for this component", "type": "string" }, "label": { "title": "Component Label", "description": "The HTML label to give this component", "type": "string" }, "placeholder": { "title": "Component Placeholder", "description": "The text to show in the input before they type", "type": "string" }, "input": { "title": "User Input?", "description": "Determines if this is an input from the user", "type": "boolean" }, "tableView": { "title": "Component TableView", "description": "Determines if this field will show in the data tables output", "type": "boolean" }, "multiple": { "title": "Component Multiple", "description": "If this field should collect multiple values, creating an array of values", "type": "boolean" }, "protected": { "title": "Component Protected", "description": "If the value of this field should be shown to the end user via API once it is saved", "type": "boolean" }, "prefix": { "title": "Component Prefix", "description": "The prefix text to put in front of the input", "type": "string" }, "suffix": { "title": "Component Suffix", "description": "The suffix text to put after the input", "type": "string" }, "defaultValue": { "title": "Default Value", "description": "The default value to provide to this component. Its type depends on the specific component" }, "clearOnHide": { "title": "Clear on Hide", "description": "If the value of this field should be cleared when it is conditionally hidden", "type": "boolean" }, "unique": { "title": "Unique", "description": "Validates if this field should be unique amongst other submissions in the same form", "type": "boolean" }, "persistent": { "title": "Persistent", "description": "Determines if the value of this field should be saved as persistent", "type": "boolean" }, "hidden": { "title": "Hidden", "description": "Determines if this field should be hidden from view by default. This can be overridden with the conditionals.", "type": "boolean" }, "validate": { "title": "Validate", "description": "Determines validation criteria for this component.", "type": "object", "properties": { "required": { "title": "Required", "description": "Specifies if the field is required.", "type": "boolean" }, "minLength": { "title": "Min Lenngth", "description": "For text input, this checks the minimum length of text for valid input.", "type": ["number", "string"] }, "maxLength": { "title": "Max Lenngth", "description": "For text input, this checks the maximum length of text for valid input.", "type": ["number", "string"] }, "pattern": { "title": "Pattern", "description": "For text input, this checks the text agains a Regular expression pattern.", "type": "string" }, "custom": { "title": "Custom", "description": "A custom javascript based validation or a JSON object for using JSON Logic.", "type": ["string", "object"] } } }, "conditional": { "$ref": "conditional" }, "errors": { "title": "Errors", "description": "Allows customizable errors to be displayed for each component when an error occurs.", "type": "object", "properties": { "required": { "title": "Required", "description": "Error message for error 'required'.", "type": "string" }, "min": { "title": "Min", "description": "Error message for error 'min'.", "type": "string" }, "max": { "title": "Min", "description": "Error message for error 'max'.", "type": "string" }, "minLength": { "title": "Min Length", "description": "Error message for error 'minLength'.", "type": "string" }, "maxLength": { "title": "Max Length", "description": "Error message for error 'maxLength'.", "type": "string" }, "invalid_email": { "title": "Invalid Email", "description": "Error message for error 'invalid_email'.", "type": "string" }, "invalid_date": { "title": "Invalid Date", "description": "Error message for error 'invalid_date'.", "type": "string" }, "pattern": { "title": "Pattern", "description": "Error message for error 'pattern'.", "type": "string" }, "custom": { "title": "Custom", "description": "Error message for error 'custom'.", "type": ["string", "object"] } } }, "logic": { "title": "Logic", "description": "Allows changing the component definition in reaction to data entered in a form. For example, changing a field to required, disabled or hidden when a value is entered.", "type": "array", "items": { "$ref": "logic" } } }, "allOf": [ { "if": { "properties": { "type": { "const": "columns" } } }, "then": { "$ref": "columns" } }, { "if": { "properties": { "type": { "const": "table" } } }, "then": { "$ref": "table" } }, { "if": { "properties": { "type": { "const": "tabs" } } }, "then": { "$ref": "tabs" }, "else": { "properties": { "components": { "$ref": "components" } } } } ] }; var componentLoose = Object.assign({}, componentStrict, { required: ['type'] }); // tslint:disable:object-literal-key-quotes quotemark semicolon var logic = { "title": "Logic", "description": "...", "type": "object", "required": ["trigger", "actions"], "properties": { "trigger": { "$ref": "trigger" }, "actions": { "title": "Actions", "description": "The actions to perform when the logic is triggered", "type": "array", "items": { "$ref": "action" } } } }; // tslint:disable:object-literal-key-quotes quotemark semicolon var trigger = { "title": "Trigger", "description": "Determines when the logic should be triggered", "type": "object", "required": ["type"], "properties": { "type": { "title": "Type", "description": "The type of the trigger.", "enum": ["simple", "javascript", "json", "event"] } }, "allOf": [ { "if": { "properties": { "type": { "const": "simple" } } }, "then": { "required": ["simple"], "properties": { "simple": { "title": "Simple", "description": "Defines a simple trigger.", "required": ["when", "eq", "show"], "type": "object", "properties": { "when": { "title": "When", "description": "The trigger field key.", "type": "string" }, "eq": { "title": "Eq", "description": "The value to equal.", "type": "string" }, "show": { "title": "Show", "description": "Whether to trigger or not when the value is equal.", "type": "boolean" } } } } } }, { "if": { "properties": { "type": { "const": "javascript" } } }, "then": { "required": ["javascript"], "properties": { "javascript": { "title": "Javascript", "description": "Javascript logic.", "type": "string" } } } }, { "if": { "properties": { "type": { "const": "json" } } }, "then": { "required": ["json"], "properties": { "json": { "title": "Json", "description": "JSON Logic object that returns true or false.", "type": "object" } } } }, { "if": { "properties": { "type": { "const": "event" } } }, "then": { "required": ["event"], "properties": { "event": { "title": "Event", "description": "The name of the event that will trigger this logic.", "type": "string" } } } } ] }; // tslint:disable:object-literal-key-quotes quotemark semicolon var action = { "title": "Action", "description": "An action to perform when the logic is triggered", "required": ["type"], "type": "object", "properties": { "type": { "title": "Type", "description": "The type of the action.", "enum": ["property", "value"] } }, "allOf": [ { "if": { "properties": { "type": { "const": "property" } } }, "then": { "properties": { "property": { "title": "Property", "description": "The property action.", "required": ["type", "value"], "type": "object", "properties": { "type": { "title": "Property", "description": "The type of the property action (either 'boolean' or 'string').", "enum": ["boolean", "string"] }, "value": { "title": "Value", "description": "The path to the property on the component definition.", "type": "string" } } } }, "allOf": [ { "if": { "properties": { "type": { "const": "boolean" } } }, "then": { "properties": { "state": { "title": "Boolean State", "description": "Used if the type of the property action is boolean.", "type": "boolean" } } } }, { "if": { "properties": { "type": { "const": "string" } } }, "then": { "properties": { "text": { "title": "String Text", "description": "Used if the type of the property action is string.", "type": "string" } } } } ] } }, { "if": { "properties": { "type": { "const": "value" } } }, "then": { "properties": { "value": { "title": "Value", "description": "The javascript that returns the new value. It Will be evaluated with available variables of row, data, component and result (returned from trigger).", "type": "string" } } } } ] }; // tslint:disable:object-literal-key-quotes quotemark semicolon var conditional = { "title": "Conditional", "description": "Determines when this component should be added to the form for both processing and input.", "type": "object", "properties": { "show": { "$ref": "show", }, "when": { "title": "When", "description": "The field API key that it should compare its value against to determine if the condition is triggered.", "type": ["string", "null"] }, "eq": { "title": "Eq", "description": "The value that should be checked against the comparison component.", "type": "string" }, "json": { "title": "Json", "description": "The JSON Logic to determine if this component is conditionally available.", "type": "string" } } }; // tslint:disable:object-literal-key-quotes quotemark semicolon var showStrict = { "title": "Show", "description": "If the field should show if the condition is true.", "type": ["boolean", "null"] }; // tslint:disable:object-literal-key-quotes quotemark semicolon var showLoose = { "title": "Show", "description": "If the field should show if the condition is true.", "enum": [true, false, "true", "false", "", null] }; // tslint:disable:object-literal-key-quotes quotemark semicolon var columns = { "required": ["columns"], "not": { "required": ["components"] }, "properties": { "columns": { "type": "array", "items": { "required": ["components"], "type": "object", "properties": { "components": { "$ref": "components" } } } } } }; // tslint:disable:object-literal-key-quotes quotemark semicolon var table = { "required": ["rows", "numRows", "numCols"], "not": { "required": ["components"] }, "properties": { "numRows": { "type": "integer" }, "numCols": { "type": "integer" }, "rows": { "type": "array", "items": { "type": "array", "items": { "required": ["components"], "type": "object", "properties": { "components": { "$ref": "components" } } } } } } }; // tslint:disable:object-literal-key-quotes quotemark semicolon var tabs = { "required": ["components"], "properties": { "components": { "type": "array", "items": { "type": "object", "required": ["key", "components"], "not": { "required": ["type"] }, "properties": { "key": { "title": "Component Key", "description": "The API key for this component", "type": "string" }, "label": { "title": "Component Label", "description": "The HTML label to give this component", "type": "string" }, "components": { "$ref": "components" } } } } } }; const strict = { schema, schemaRefs: { columns, components, component: componentStrict, logic, trigger, action, conditional, show: showStrict, table, tabs } }; const loose = { schema, schemaRefs: Object.assign({}, strict.schemaRefs, { component: componentLoose, show: showLoose }) }; const defaultJsonEditorOptions = { enableSort: true, enableTransform: true, escapeUnicode: false, expandAll: false, history: true, indentation: 2, limitDragging: false, mode: 'view', modes: ['code', 'tree', 'view'], schema: loose.schema, schemaRefs: loose.schemaRefs, search: true, sortObjectKeys: false }; const defaultRendererResourceJsonEditorOptions = merge(defaultJsonEditorOptions, { mode: 'tree', modes: ['code', 'tree'], schema: undefined, schemaRefs: undefined }); const defaultRendererSchemaJsonEditorOptions = clone(defaultRendererResourceJsonEditorOptions); let FormioEditorComponent = class FormioEditorComponent { constructor(modalService) { this.modalService = modalService; this.builderDisplayChanged = false; this.jsonEditorChanged = false; // tslint:disable-next-line:variable-name this._jsonEditorErrors = []; this.jsonEditorWarningCounter = 0; } get options() { return this._options; } set options(options) { this.setOptions(options); } get activeTab() { return this._activeTab; } set activeTab(tab) { this._activeTab = tab; if (tab === 'renderer') { this.submissionPanel = false; // Disable submission panel when the renderer tab becomes active } } get jsonEditorErrors() { return this._jsonEditorErrors; } set jsonEditorErrors(errors) { this._jsonEditorErrors = errors; this.jsonEditorWarningCounter = 0; errors.forEach((error) => { if (error.type === 'validation') { this.jsonEditorWarningCounter++; } }); } ngOnInit() { if (!this.options) { this.setOptions(); // Set default options } this.activeTab = ['builder', 'json', 'renderer'] .find(t => { var _a; return this.options && ((_a = this.options[t]) === null || _a === void 0 ? void 0 : _a.defaultTab) ? t : undefined; }) || 'builder'; if (this.reset) { this.resetSubscription = this.reset.subscribe(() => this.resetFormBuilder()); } } ngAfterViewInit() { this.refreshJsonEditor(); } ngOnDestroy() { if (this.resetSubscription) { this.resetSubscription.unsubscribe(); } } setOptions(options = {}) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; this._options = options; this.jsonEditorOptions = merge(defaultJsonEditorOptions, (_b = (_a = options === null || options === void 0 ? void 0 : options.json) === null || _a === void 0 ? void 0 : _a.input) === null || _b === void 0 ? void 0 : _b.options); this.rendererResourceJsonEditorOptions = merge(defaultRendererResourceJsonEditorOptions, (_f = (_e = (_d = (_c = options === null || options === void 0 ? void 0 : options.renderer) === null || _c === void 0 ? void 0 : _c.submissionPanel) === null || _d === void 0 ? void 0 : _d.resourceJsonEditor) === null || _e === void 0 ? void 0 : _e.input) === null || _f === void 0 ? void 0 : _f.options); this.rendererSchemaJsonEditorOptions = merge(defaultRendererSchemaJsonEditorOptions, (_k = (_j = (_h = (_g = options === null || options === void 0 ? void 0 : options.renderer) === null || _g === void 0 ? void 0 : _g.submissionPanel) === null || _h === void 0 ? void 0 : _h.schemaJsonEditor) === null || _j === void 0 ? void 0 : _j.input) === null || _k === void 0 ? void 0 : _k.options); this.fullSubmission = (_m = (_l = options === null || options === void 0 ? void 0 : options.renderer) === null || _l === void 0 ? void 0 : _l.submissionPanel) === null || _m === void 0 ? void 0 : _m.fullSubmission; } // // Form Builder Tab // resetFormBuilder(fromJsonEditor = false) { console.log('resetFormBuilder'); // Here we have to reset builder component through *ngIf="!builderDisplayChanged" // See https://github.com/formio/angular-formio/issues/172#issuecomment-401876490 this.builderDisplayChanged = true; setTimeout(() => { this.builderDisplayChanged = false; if (!fromJsonEditor) { this.refreshJsonEditor(true); } this.resetFormRendererIfActive(); }); } onBuilderDiplayChange(event) { console.log('onBuilderDiplayChange'); this.resetFormBuilder(); } onBuilderChange(event) { console.log('onBuilderChange: event', event); this.refreshJsonEditor(true); } // // JSON Tab // onJsonEditorError(errors) { console.log('onJsonEditorError: found', errors.length, 'validation errors:'); this.jsonEditorErrors = errors; } onJsonEditorChange(event) { console.log('onJsonEditorChange'); this.jsonEditorChanged = true; } onJsonEditorApply(template) { console.log('Errors: ', this.jsonEditorErrors.length - this.jsonEditorWarningCounter, 'Warnings: ', this.jsonEditorWarningCounter); if (this.jsonEditorWarningCounter === 0) { this.jsonEditorApplyChanges(); } else { this.modalRef = this.modalService.show(template); } } jsonEditorApplyChanges() { console.log('jsonEditorApplyChanges'); this.jsonEditorChanged = false; // Remove all properties from this form // then copy the properties of the edited json to this form // and reset the builder Object.getOwnPropertyNames(this.form).forEach(p => delete this.form[p]); Object.assign(this.form, this.jsonEditor.get()); this.resetFormBuilder(true); } jsonEditorDiscardChanges() { console.log('jsonEditorDiscardChanges'); this.refreshJsonEditor(); } refreshJsonEditor(forceReset = false) { console.log('refreshJsonEditor'); if (forceReset) { this.jsonEditor.reset(true); } // Here we use update instead of set to preserve the editor status this.jsonEditor.update(this.form); this.jsonEditorChanged = false; } // // Form Renderer Tab // resetFormRendererIfActive() { console.log('resetFormRenderer'); // Here we recreate the renderer component through *ngIf="activeTab='renderer'" // by changing the active tab and then restoring it. // Although this is a rather dirty hack it is hardly noticeable to the eye :) if (this.activeTab === 'renderer') { this.activeTab = 'builder'; setTimeout(() => { this.activeTab = 'renderer'; }); } } showSubmissionPanel(submission) { var _a, _b; this.submissionPanel = !((_b = (_a = this.options.renderer) === null || _a === void 0 ? void 0 : _a.submissionPanel) === null || _b === void 0 ? void 0 : _b.disabled); if (this.submissionPanel) { if (submission) { t