source/idea/idea-cluster-manager/webapp/src/components/form-field/form-field.tsx (1,964 lines of code) (raw):
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
* with the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
import React, { Component } from "react";
import { GetParamChoicesRequest, GetParamChoicesResult, GetParamDefaultRequest, GetParamDefaultResult, GetParamsRequest, GetParamsResult, SetParamRequest, SetParamResult, SocaUserInputGroupMetadata, SocaUserInputParamMetadata, SocaUserInputSectionMetadata } from "../../client/data-model";
import Utils from "../../common/utils";
import Input, { InputProps } from "@cloudscape-design/components/input";
import FormField, { FormFieldProps } from "@cloudscape-design/components/form-field";
import {AttributeEditor, Autosuggest, Button, ColumnLayout, DatePicker, ExpandableSection, FileUpload, Grid, Link, Multiselect, RadioGroup, Select, SelectProps, SpaceBetween, Textarea, Tiles, Toggle} from "@cloudscape-design/components";
import { BaseKeyDetail } from "@cloudscape-design/components/internal/events";
import { faAdd, faRemove } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OnToolsChangeEvent } from "../../App";
export interface IdeaFormFieldSyncAPI {
getParamDefault(req: GetParamDefaultRequest): Promise<GetParamDefaultResult>;
setParam(req: SetParamRequest): Promise<SetParamResult>;
getParamChoices(req: GetParamChoicesRequest): Promise<GetParamChoicesResult>;
getParams(req: GetParamsRequest): Promise<GetParamsResult>;
}
export interface IdeaFormFieldCustomActionProvider {
onCustomActionClick(customTypeName: string, formField: IdeaFormField, onSubmit: (value: any) => void): void;
getCustomActionLabel(customTypeName: string): string | null;
}
export interface IdeaFormFieldProps {
module: string;
param: SocaUserInputParamMetadata;
section?: SocaUserInputSectionMetadata;
group?: SocaUserInputGroupMetadata;
onLifecycleEvent?: IdeaFormFieldLifecycleEventHandler;
onStateChange?: IdeaFormFieldStateChangeEventHandler;
visible?: boolean;
updateTools?: any;
syncApi?: IdeaFormFieldSyncAPI;
value?: any;
onKeyEnter?: IdeaFormFieldOnKeyEnterEventHandler;
onFetchOptions?: (req: GetParamChoicesRequest) => Promise<GetParamChoicesResult>;
customActionProvider?: IdeaFormFieldCustomActionProvider;
stretch?: boolean;
toolsOpen?: boolean | null;
tools?: React.ReactNode| null;
onToolsChange?: (event: OnToolsChangeEvent) => void | null;
}
export interface IdeaFormFieldState {
value?: any;
value2?: any;
default?: any;
errorCode?: string | null;
errorMessage?: string | null;
selectedOption?: any;
selectedOptions?: any;
options: any[];
dynamicOptions: boolean;
dynamicOptionsLoading: boolean;
defaultValueLoading: boolean;
visibility: boolean;
disabled: boolean;
stringVal(): string;
stringVal2(): string;
booleanVal(): boolean;
numberVal(): number;
stringArrayVal(): string[];
booleanArrayVal(): boolean[];
numberArrayVal(): number[];
amountVal(): string;
memoryVal(): string
fileVal(): File[];
listOfRecordsVal(): Record<any, any>[]
listOfAttributeEditorRecords(): {key: string, value: string, error?: string}[]
}
export interface IdeaFormFieldStateChangeEvent {
param: SocaUserInputParamMetadata;
value?: any | any[];
errorCode?: string | null;
errorMessage?: string | null;
refresh?: boolean;
ref: IdeaFormField;
}
export interface IdeaFormFieldLifecycleEvent {
type: LifecycleEventType;
ref: IdeaFormField;
}
export interface IdeaFormFieldKeyEnterEvent {
ref: IdeaFormField;
}
type LifecycleEventType = "mounted" | "unmounted";
export type IdeaFormFieldLifecycleEventHandler = (event: IdeaFormFieldLifecycleEvent) => void;
export type IdeaFormFieldStateChangeEventHandler = (event: IdeaFormFieldStateChangeEvent) => void;
export type IdeaFormFieldOnKeyEnterEventHandler = (event: IdeaFormFieldKeyEnterEvent) => void;
export interface IdeaFormFieldRegistryEntry {
field: IdeaFormField | null;
lastKnownState: IdeaFormFieldState | null;
}
export class IdeaFormFieldRegistry {
fields: {
[k: string]: IdeaFormFieldRegistryEntry;
};
constructor() {
this.fields = {};
}
add(field: IdeaFormField) {
this.fields[field.getParamName()] = {
field: field,
lastKnownState: field.getState(),
};
}
delete(param: string) {
if (param in this.fields) {
this.fields[param].field = null;
}
}
getFormField(param: string): IdeaFormField | null {
if (!(param in this.fields)) {
return null;
}
const entry = this.fields[param];
return entry.field;
}
getLastKnownState(param: string): IdeaFormFieldState | null {
if (!(param in this.fields)) {
return null;
}
const entry = this.fields[param];
return entry.lastKnownState;
}
list(): IdeaFormField[] {
const fields: IdeaFormField[] = [];
for (const param in this.fields) {
const field = this.getFormField(param);
if (field == null) {
continue;
}
fields.push(field);
}
return fields;
}
}
class IdeaFormField extends Component<IdeaFormFieldProps, IdeaFormFieldState> {
constructor(props: IdeaFormFieldProps) {
super(props);
this.state = {
value: this.props.value ? this.props.value : this.props.param.default,
value2: undefined,
default: this.props.param.default,
options: [],
selectedOption: {},
selectedOptions: [],
dynamicOptions: false,
dynamicOptionsLoading: false,
defaultValueLoading: false,
errorCode: null,
errorMessage: null,
visibility: true,
disabled: this.props.param.readonly ? this.props.param.readonly : false,
stringVal(): string {
if (Utils.isNotEmpty(this.value)) {
return Utils.asString(this.value);
}
return "";
},
stringVal2(): string {
if (this.value2 != null) {
return Utils.asString(this.value2);
}
return "";
},
stringArrayVal(): string[] {
if (this.value != null) {
return Utils.asStringArray(this.value);
}
return [];
},
booleanVal(): boolean {
if (this.value != null) {
return Utils.asBoolean(this.value);
}
return false;
},
booleanArrayVal(): boolean[] {
if (this.value != null) {
return Utils.asBooleanArray(this.value);
}
return [];
},
numberVal(decimal: boolean = false): number {
if (this.value != null) {
return Utils.asNumber(this.value, 0, decimal);
}
return 0;
},
numberArrayVal(): number[] {
if (this.value != null) {
return Utils.asNumberArray(this.value);
}
return [];
},
amountVal(): string {
if (typeof this.value === "object") {
return Utils.asString(this.value.amount);
}
return "0.00";
},
memoryVal(): string {
if (typeof this.value === "object") {
return Utils.asString(this.value.value);
}
return "0";
},
fileVal(): File[] {
if (this.value != null) {
return this.value2;
}
return [];
},
listOfRecordsVal(): Record<any, any>[] {
if (this.value != null) {
return this.value
}
return []
},
listOfAttributeEditorRecords(): { key: string; value: string; error?: string }[] {
if (this.value != null) {
return this.value
}
return []
}
};
}
getParamName(): string {
return this.props.param.name!;
}
getParamMeta(): SocaUserInputParamMetadata {
return this.props.param;
}
isAutoComplete(): boolean {
return this.props.param.param_type === "autocomplete";
}
getState(): IdeaFormFieldState {
// shallow copy
return Object.assign({}, this.state);
}
getValueAsString(): string {
if (this.isMultiple()) {
return this.state.stringArrayVal().join(", ");
} else {
return this.state.stringVal();
}
}
getValueAsStringArray(): string[] {
return this.state.stringArrayVal();
}
getSelectedOptionLabel(): string {
if (this.state.selectedOption && this.state.selectedOption.label) {
return this.state.selectedOption.label;
}
return "";
}
getSelectOptions(): any {
return this.state.options;
}
getDataType(): string {
if (this.props.param.data_type) {
return this.props.param.data_type;
}
return "str";
}
getAnyValue(): any {
return this.state.value;
}
getListOfStringRecords(): Record<string,string>[] {
if (Utils.isListOfRecordStrings(this.state.listOfRecordsVal())) {
return this.state.listOfRecordsVal()
}
return []
}
getListOfAttributeEditorRecords() {
if(Utils.isListOfAttributeEditorRecords(this.state.listOfAttributeEditorRecords())) {
return this.state.listOfAttributeEditorRecords()
}
return []
}
getTypedValue(): any {
if (this.isMultiple()) {
const dataType = this.getDataType();
switch (dataType) {
case "int":
case "float":
return this.state.numberArrayVal();
case "bool":
return this.state.booleanArrayVal();
case "record":
return this.state.listOfRecordsVal();
case "attributes":
return this.state.listOfAttributeEditorRecords()
default:
return this.state.stringArrayVal();
}
} else {
const dataType = this.getDataType();
switch (dataType) {
case "int":
case "float":
return this.state.numberVal();
case "bool":
return this.state.booleanVal();
case "amount":
if (typeof this.state.value === "object") {
return this.state.value;
} else {
return {
amount: 0.0,
unit: "USD",
};
}
case "memory":
if (typeof this.state.value === "object") {
return this.state.value;
} else {
return {
value: 0,
unit: "bytes",
};
}
default:
return this.state.stringVal();
}
}
}
async getFileAsBase64String(file: File): Promise<string | ArrayBuffer | null> {
const reader = new FileReader()
return new Promise((resolve, reject) => {
reader.onerror = error => reject(error);
reader.onloadend = () => {
// Use a regex to remove data url part
return resolve((reader.result as string)
.replace('data:', '')
.replace(/^.+,/, '')
)};
reader.readAsDataURL(file);
})
}
getErrorCode(): string | null {
if (Utils.isEmpty(this.state.errorCode)) {
return null;
}
return this.state.errorCode!;
}
getErrorMessage(): string | null {
if (Utils.isEmpty(this.state.errorMessage)) {
return null;
}
return this.state.errorMessage!;
}
fetchDefault(reset: boolean = false) {
if (!this.props.syncApi) {
return Promise.resolve(this.state);
}
return this.props.syncApi
.getParamDefault({
module: this.props.module,
param: this.props.param.name,
reset: reset,
})
.then((result) => {
return new Promise((resolve) => {
const state = {
default: result?.default,
};
this.setState(state, () => {
resolve(state);
});
});
});
}
updateSelectedOptions(): boolean {
if (this.state.options == null) {
return false;
}
const selectedOptions: any[] = [];
if (this.isMultiple()) {
const arrayVal = this.state.stringArrayVal();
this.state.options.forEach((option) => {
if (arrayVal.find((value) => value === option.value)) {
selectedOptions.push(option);
}
});
} else {
const stringVal = this.state.stringVal();
this.state.options.forEach((option) => {
if (stringVal === option.value) {
selectedOptions.push(option);
}
});
}
if (this.isMultiple()) {
this.setState({
selectedOptions: selectedOptions,
});
} else {
this.setState({
selectedOption: selectedOptions[0],
});
}
return true;
}
setValue(value: any) {
this.setState(
{
value: value,
},
this.setStateCallback
);
}
setNull(): Promise<any> {
const syncApi = this.props.syncApi;
if (syncApi == null) {
return Promise.resolve({});
}
return new Promise((resolve, reject) => {
syncApi
.setParam({
module: this.props.module,
param: this.getParamName(),
value: "",
})
.then(
(result) => {
resolve(result);
},
(error) => {
reject(error);
}
);
});
}
reset(): Promise<boolean> {
const setStateCallback = () => {
this.updateSelectedOptions();
this.setStateCallback();
};
return this.fetchDefault().then((_) => {
if (this.isMultiple()) {
this.setState(
{
value: this.state.default,
errorCode: null,
errorMessage: null,
},
setStateCallback
);
} else {
this.setState(
{
value: this.state.default,
errorCode: null,
errorMessage: null,
},
setStateCallback
);
}
return Promise.resolve(true);
});
}
public setOptions(result: GetParamChoicesResult, dynamicOptions: boolean = false) {
const choices = result?.listing!;
const options = [];
for (let i = 0; i < choices.length; i++) {
const choice = choices[i];
if (choice.options && choice.options.length > 0) {
const level2Options: any[] = [];
const option = {
label: choice.title,
options: level2Options,
};
options.push(option);
const choicesLevel2 = choice.options;
for (let j = 0; j < choicesLevel2.length; j++) {
const choiceLevel2 = choicesLevel2[j];
const value = Utils.asString(choiceLevel2.value);
level2Options.push({
value: value,
label: choiceLevel2.title,
description: choiceLevel2.description,
disabled: choiceLevel2.disabled,
});
}
} else {
const value = Utils.asString(choice.value);
options.push({
value: value,
label: choice.title,
description: choice.description,
disabled: choice.disabled,
});
}
}
const state = {
dynamicOptions: dynamicOptions,
options: options,
};
this.setState(state, () => {
this.updateSelectedOptions();
});
}
fetchOptions(refresh: boolean = false): Promise<IdeaFormField> {
const paramType = this.props.param.param_type;
const applicableParamTypes = ["select", "raw_select", "select_or_text", "checkbox", "autocomplete", "tiles", "radio-group"];
const found = applicableParamTypes.find((value) => value === paramType);
if (Utils.isEmpty(found)) {
return Promise.resolve(this);
}
let dynamicOptions = false;
if (this.props.param.dynamic_choices != null) {
dynamicOptions = this.props.param.dynamic_choices;
} else {
dynamicOptions = this.props.param.choices == null || this.props.param.choices.length === 0;
}
const syncApi = this.props.syncApi;
if (dynamicOptions) {
let onFetchOptions;
if (syncApi != null) {
onFetchOptions = syncApi.getParamChoices;
} else if (this.props.onFetchOptions != null) {
onFetchOptions = this.props.onFetchOptions;
}
if (onFetchOptions == null) {
return Promise.resolve(this);
}
return onFetchOptions({
module: this.props.module,
param: this.props.param.name,
refresh: refresh,
}).then(
(result) => {
this.setOptions(result, dynamicOptions);
this.updateSelectedOptions();
return this;
},
(error) => {
this.setState({
options: [],
});
throw error;
}
);
} else {
this.setOptions(
{
listing: this.props.param.choices!,
},
dynamicOptions
);
return Promise.resolve(this);
}
}
componentDidMount() {
if (this.props.onLifecycleEvent) {
this.props.onLifecycleEvent({
type: "mounted",
ref: this,
});
}
this.initialize();
}
initialize() {
Promise.all([this.fetchDefault(), this.fetchOptions()]).then(
(_) => {
const value = this.getTypedValue();
this.setState(
{
value: value,
value2: value,
},
() => {
if (Utils.isNotEmpty(this.state.value)) {
this.setStateCallback();
}
}
);
},
(error) => {
console.error(error);
this.setState(
{
value: undefined,
},
this.setStateCallback
);
}
);
}
componentWillUnmount() {
if (this.props.onLifecycleEvent) {
this.props.onLifecycleEvent({
type: "unmounted",
ref: this,
});
}
}
publishStateChange(refresh: boolean = false) {
if (this.props.onStateChange != null) {
this.props.onStateChange({
param: this.props.param,
ref: this,
value: this.getTypedValue(),
errorCode: this.state.errorCode,
errorMessage: this.state.errorMessage,
refresh: refresh,
});
}
}
setStateCallback() {
const syncApi = this.props.syncApi;
if (syncApi == null) {
this.publishStateChange(false);
return;
}
syncApi
.setParam({
module: this.props.module,
param: this.props.param.name,
value: this.state.value,
})
.then(
(result) => {
// do not publish state change here
if (this.isMultiple()) {
const serverVal = Utils.asStringArray(result?.value);
const localVal = this.state.stringArrayVal();
if (!Utils.isArrayEqual(serverVal, localVal)) {
this.setState({
value: serverVal,
});
}
} else {
const serverVal = Utils.asString(result?.value);
const localVal = this.state.stringVal();
if (serverVal !== localVal) {
this.setState({
value: serverVal,
});
}
}
this.setState({
errorCode: null,
errorMessage: null,
});
this.updateSelectedOptions();
this.publishStateChange(result?.refresh);
},
(error) => {
console.error(error);
this.setState({
errorCode: error.error_code,
errorMessage: error.message,
});
}
);
}
disable(should_disable: boolean) {
this.setState({ disabled: should_disable }, this.setStateCallback);
}
validate_empty_record(record: Record<any,any>, container_items: SocaUserInputParamMetadata[]): boolean {
if (Utils.isEmpty(record)) {
return true;
}
for (let column of container_items) {
if (column.name && Utils.isEmpty(record[column.name])) {
return true;
}
}
return false;
}
validate(): string {
const validate = this.props.param.validate;
if (validate == null) {
return "OK";
}
if (!this.validateRegex()) {
return "REGEX";
}
if (validate.min != null || validate.max != null) {
const dataType = this.props.param.data_type!;
const decimal = dataType === "float";
const numberVal = this.state.numberVal();
const min = Utils.asNumber(validate.min, 0, decimal);
const max = Utils.asNumber(validate.max, 0, decimal);
if (validate.min != null && validate.max != null) {
if (numberVal < min || numberVal > max) {
return "NUMBER_RANGE";
}
} else if (validate.min != null) {
if (numberVal < min) {
return "MIN_VALUE";
}
} else if (validate.max != null) {
if (numberVal > max) {
return "MAX_VALUE";
}
}
}
if (validate.required == null || Utils.isFalse(validate.required)) {
return "OK";
}
if (this.isMultiple()) {
if (this.props.param.param_type === "container") {
if (this.getListOfStringRecords().length === 0) {
return "OK";
} else {
for (let record of this.getListOfStringRecords()) {
if (this.props.param.container_items && this.validate_empty_record(record, this.props.param.container_items)) {
return "CUSTOM_FAILED"
}
}
return "OK";
}
}
if (this.state.stringArrayVal().length === 0) {
return "REQUIRED";
} else {
let isEmpty = false;
let values = this.state.stringArrayVal();
for (let i = 0; i < values.length; i++) {
if (Utils.isEmpty(values[i])) {
isEmpty = true;
break;
}
}
if (isEmpty) {
return "REQUIRED";
}
return "OK";
}
} else if (Utils.isEmpty(this.state.stringVal())) {
return "REQUIRED";
}
if (this.props.param.param_type === "new-password") {
if (Utils.isEmpty(this.state.value2)) {
return "REQUIRED";
}
}
return "OK";
}
triggerValidate(): boolean {
const result = this.validate();
if (result === "OK") {
this.setState({
errorCode: null,
errorMessage: null,
});
return true;
} else {
const errorCode = "VALIDATION_FAILED";
let errorMessage;
let displayTitle = this.props.param.title;
if (displayTitle === undefined || displayTitle === null || displayTitle?.length === 0) {
displayTitle = this.props.param.name;
}
switch (result) {
case "REQUIRED":
errorMessage = `${displayTitle} is required.`;
break;
case "NUMBER_RANGE":
errorMessage = `${displayTitle} must be between ${this.props.param.validate?.min}
and ${this.props.param.validate?.max}.`;
break;
case "MIN_VALUE":
errorMessage = `${displayTitle} must be greater than or equal to ${this.props.param.validate?.min}`;
break;
case "MAX_VALUE":
errorMessage = `${displayTitle} must be less than or equal to ${this.props.param.validate?.max}`;
break;
case "REGEX":
errorMessage = this.props.param.validate?.message ?? `${displayTitle} must satisfy regex: ${this.props.param.validate?.regex}`;
break;
case 'CUSTOM_FAILED':
errorMessage = this.props.param.custom_error_message
break
default:
errorMessage = `${displayTitle} validation failed.`;
}
this.setState({
errorCode: errorCode,
errorMessage: errorMessage,
});
return false;
}
}
getNativeType(): string {
const dataType = this.props.param.data_type!;
let result;
if (dataType === "int" || dataType === "float") {
result = "number";
} else if (dataType === "str") {
result = "string";
} else if (dataType === "bool") {
result = "boolean";
} else if (dataType === "memory") {
result = "memory";
} else if (dataType === "amount") {
result = "amount";
} else {
result = "string";
}
return result;
}
getCustomType(): string | null {
if (this.props.param.custom_type) {
return this.props.param.custom_type;
}
return null;
}
getInputMode(): InputProps.InputMode {
const type = this.getNativeType();
const dataType = this.props.param.data_type!;
if (type === "string") {
return "text";
} else if (type === "number") {
if (dataType === "int") {
return "numeric";
} else {
return "decimal";
}
}
return "text";
}
isMultiple(): boolean {
if (this.props.param.multiple != null) {
return this.props.param.multiple;
}
return false;
}
isReadOnly(): boolean {
if (this.props.param.readonly != null) {
return this.props.param.readonly;
}
return false;
}
isAutoFocus(): boolean {
if (this.props.param.auto_focus != null) {
return this.props.param.auto_focus;
}
return false;
}
isRefreshable(): boolean {
if (this.props.param.refreshable != null) {
return this.props.param.refreshable;
}
return false;
}
getInputType(): InputProps.Type {
const type = this.getNativeType();
const paramType = this.props.param.param_type;
if (type === "string") {
if (paramType === "password") {
return "password";
} else {
return "text";
}
} else if (type === "number") {
return "number";
}
return "text";
}
getContainerInputType(param: SocaUserInputParamMetadata): InputProps.Type {
const dataType = param.data_type!;
if ( dataType === "url" ) {
return "url"
}
return "text";
}
isMarkDownAvailable(): boolean {
return Utils.isNotEmpty(this.props.param.markdown);
}
handleToolsOpen(markdown?: string) {
if (this.props.onToolsChange && markdown) {
this.props.onToolsChange({
open: true,
pageId: markdown,
})
}
}
buildFormField(field: React.ReactNode, props?: FormFieldProps, key?: string): React.ReactNode {
let label: React.ReactNode = this.props.param.optional ? <span>{this.props.param.title} <i>- optional</i></span> : this.props.param.title;
let description: React.ReactNode = this.props.param.description;
let constraintText: React.ReactNode = this.props.param.help_text;
let stretch = false;
let secondaryControl = null;
if (props != null) {
if (props.label != null) {
label = this.props.param.optional ? <span>{props.label} <i>- optional</i></span> : props.label;
}
if (props.description != null) {
description = props.description;
}
if (props.constraintText != null) {
constraintText = props.constraintText;
}
if (props.stretch != null) {
stretch = props.stretch;
}
if (props.secondaryControl != null) {
secondaryControl = props.secondaryControl;
}
}
if (!key) {
key = `f-${this.getParamName()}`;
}
return (
<FormField
key={key}
label={label}
description={description}
constraintText={constraintText}
stretch={stretch}
info={
this.isMarkDownAvailable() && (
<Link
variant="info"
onFollow={() => {
this.handleToolsOpen(this.props.param.markdown)
}}
>
Info
</Link>
)
}
errorText={this.getErrorMessage()}
secondaryControl={secondaryControl}
>
{field}
</FormField>
);
}
validateRegex(value?: string): boolean {
if (this.props.param.validate == null) {
return true;
}
const regex = this.props.param.validate.regex;
if (regex == null || Utils.isEmpty(regex)) {
return true;
}
const re = RegExp(regex);
let token = value;
if (token == null) {
token = this.state.stringVal();
}
return re.test(token);
}
triggerDynamicOptionsLoading(): Promise<any> {
return new Promise((resolve, reject) => {
this.setState(
{
dynamicOptionsLoading: true,
options: [],
selectedOption: {},
selectedOptions: [],
},
() => {
this.fetchOptions(true)
.catch((error) => reject(error))
.finally(() => {
this.setState(
{
dynamicOptionsLoading: false,
},
() => {
resolve("OK");
}
);
});
}
);
});
}
buildOptionsRefresh() {
if (this.isAutoComplete()) {
return;
}
const loadDynamicOptions = (_: any) => {
this.triggerDynamicOptionsLoading().catch((error) => {
console.error(error);
});
};
return <Button loading={this.state.dynamicOptionsLoading} iconName="refresh" onClick={(event) => loadDynamicOptions(event)} />;
}
buildDefaultValueRefresh() {
const fetchDefaultValue = (_: any) => {
this.setState(
{
defaultValueLoading: true,
},
() => {
this.fetchDefault(true)
.catch((error) => console.error(error))
.finally(() => {
this.setState(
{
value: this.state.default,
defaultValueLoading: false,
},
this.setStateCallback
);
});
}
);
};
return <Button loading={this.state.defaultValueLoading} iconName="refresh" onClick={(event) => fetchDefaultValue(event)} />;
}
buildFormFieldSecondaryControl() {
const customType = this.getCustomType();
let customControl;
if (customType && this.props.customActionProvider) {
const customControlStateCallback = () => {
this.triggerDynamicOptionsLoading().finally(() => {
this.setStateCallback();
});
};
const customActionLabel = this.props.customActionProvider.getCustomActionLabel(customType);
if (customActionLabel != null) {
customControl = (
<Button
onClick={() => {
this.props.customActionProvider?.onCustomActionClick(customType, this, (value) => {
this.setState(
{
value: value,
},
customControlStateCallback
);
});
}}
>
{customActionLabel}
</Button>
);
}
}
if (this.state.dynamicOptions) {
const optionsRefresh = this.buildOptionsRefresh();
if (customControl) {
return (
<SpaceBetween size="xxxs" direction="horizontal">
{optionsRefresh}
{customControl}
</SpaceBetween>
);
} else {
return optionsRefresh;
}
} else {
return customControl;
}
}
onKeyDown = (event: CustomEvent<BaseKeyDetail>) => {
if (event.detail.key === "Enter" && this.props.onKeyEnter) {
this.props.onKeyEnter({
ref: this,
});
}
};
onInputStateChange(value: string) {
this.setState(
{
value: value,
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
);
}
buildInput(props: FormFieldProps) {
let secondaryControl = null;
if (this.isRefreshable()) {
secondaryControl = this.buildDefaultValueRefresh();
}
return this.buildFormField(
<Input
name={this.props.param.name}
value={this.state.stringVal()}
type={this.getInputType()}
readOnly={this.isReadOnly()}
autoComplete={false}
disabled={this.state.disabled}
onKeyDown={this.onKeyDown}
autoFocus={this.isAutoFocus()}
onChange={(event) => {
this.onInputStateChange(event.detail.value);
}}
/>,
{
...props,
secondaryControl: secondaryControl,
}
);
}
buildInputArray(props: FormFieldProps) {
return this.buildFormField(
<ColumnLayout columns={1}>
{this.getValueAsStringArray().length === 0 && (
<Button
variant="normal"
onClick={() => {
this.setState(
{
value: [""],
},
this.setStateCallback
);
}}
>
<FontAwesomeIcon icon={faAdd} />
</Button>
)}
{this.getValueAsStringArray().length > 0 &&
this.getValueAsStringArray().map((value, index) => {
return (
<ColumnLayout columns={2} key={`${this.props.param.name}-${index}`}>
<Input
name={this.props.param.name}
value={value}
type={this.getInputType()}
readOnly={this.isReadOnly()}
autoComplete={false}
disabled={this.state.disabled}
onKeyDown={this.onKeyDown}
autoFocus={index === 0 && this.isAutoFocus()}
onChange={(event) => {
const values: string[] = this.state.value;
values[index] = event.detail.value;
this.setState(
{
value: values,
},
this.setStateCallback
);
}}
/>
<SpaceBetween size="xs" direction="horizontal">
<Button
variant="normal"
onClick={() => {
const values: string[] = this.state.value;
values.push("");
this.setState(
{
value: values,
},
this.setStateCallback
);
}}
>
<FontAwesomeIcon icon={faAdd} />
</Button>
<Button
variant="normal"
onClick={() => {
const values: string[] = this.state.value;
values.splice(index, 1);
this.setState(
{
value: values,
},
this.setStateCallback
);
}}
>
<FontAwesomeIcon icon={faRemove} />
</Button>
</SpaceBetween>
</ColumnLayout>
);
})}
</ColumnLayout>,
props
);
}
onPasswordStateChange(value: string, value2: boolean = false) {
const stateCallback = () => {
if (this.props.param.param_type === "new-password") {
const val1 = this.state.stringVal();
const val2 = this.state.stringVal2();
if (!Utils.isEmpty(val1) && !Utils.isEmpty(val2) && val1 === val2) {
this.setState({
errorCode: null,
errorMessage: null,
});
this.setStateCallback();
} else if (!Utils.isEmpty(val2)) {
this.setState({
errorCode: "VERIFY_PASSWORD_DOES_NOT_MATCH",
errorMessage: "Passwords do not match",
});
}
} else if (this.props.param.param_type === "password") {
this.setStateCallback();
}
};
if (value2) {
this.setState(
{
value2: value,
},
stateCallback
);
} else {
this.setState(
{
value: value,
},
stateCallback
);
}
}
buildPassword(value2: boolean = false, props: FormFieldProps) {
let label = this.props.param.title;
let description = this.props.param.description;
let value = () => this.state.stringVal();
if (value2) {
label = `Verify ${label}`;
description = `Re-${description}`;
value = () => this.state.stringVal2();
}
let key;
if (value2) {
key = `f-${this.getParamName()}-2`;
} else {
key = `f-${this.getParamName()}-1`;
}
return this.buildFormField(
<Input
name={this.props.param.name}
value={value()}
type="password"
autoComplete={false}
disabled={this.state.disabled}
onKeyDown={this.onKeyDown}
autoFocus={this.isAutoFocus() && !value2}
readOnly={this.isReadOnly()}
onChange={(event) => {
this.onPasswordStateChange(event.detail.value, value2);
}}
/>,
{
...props,
label: label,
description: description,
},
key
);
}
onAutoSuggestStateChange(value: string) {
this.setState(
{
value: value,
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
);
}
/**
* AutoSuggest will preload all options and filtering is performed locally.
*/
buildAutoSuggest(props: FormFieldProps) {
let secondaryControl = this.buildFormFieldSecondaryControl();
return this.buildFormField(
<Autosuggest
enteredTextLabel={(value) => `Use: ${value}`}
value={this.state.stringVal()}
options={this.state.options}
disabled={this.state.disabled}
autoFocus={this.isAutoFocus()}
onKeyDown={(event) => {
if (event.detail.key === "Escape") {
// this prevents the modal getting dismissed
event.stopPropagation();
}
}}
onChange={(event) => this.onAutoSuggestStateChange(event.detail.value)}
/>,
{
...props,
secondaryControl: secondaryControl,
}
);
}
onAutoCompleteStateChange(value: string) {
this.setState(
{
value: value,
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
);
}
/**
* AutoComplete loads options based on text entered by user.
* onFetchOptions is called as user types the input
*/
buildAutoComplete(props: FormFieldProps) {
let secondaryControl = this.buildFormFieldSecondaryControl();
return this.buildFormField(
<Autosuggest
enteredTextLabel={(value) => `Use: ${value}`}
value={this.state.stringVal()}
options={this.state.options}
statusType={this.state.dynamicOptionsLoading ? "loading" : "finished"}
filteringType="none"
disabled={this.state.disabled}
autoFocus={this.isAutoFocus()}
onKeyDown={(event) => {
// prevent the modal getting dismissed on Escape
if (event.detail.key === "Escape") {
event.stopPropagation();
}
}}
onLoadItems={(event) => {
if (this.props.onFetchOptions) {
this.setState(
{
options: [],
dynamicOptionsLoading: true,
},
() => {
this.props.onFetchOptions!({
param: this.getParamName(),
filters: [
{
key: this.getParamName(),
value: event.detail.filteringText,
},
],
})
.then((result) => {
this.setOptions(result);
})
.finally(() => {
this.setState({
dynamicOptionsLoading: false,
});
});
}
);
}
}}
onChange={(event) => this.onAutoCompleteStateChange(event.detail.value)}
/>,
{
...props,
secondaryControl: secondaryControl,
}
);
}
onDatePickerStateChange(selectedDate: string) {
this.setState(
{
value: selectedDate,
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
);
}
buildDatePicker(props: FormFieldProps) {
return this.buildFormField(
<DatePicker
onChange={({ detail }) => this.onDatePickerStateChange(detail.value)}
readOnly={this.isReadOnly()}
autoFocus={this.isAutoFocus()}
value={this.state.stringVal()}
disabled={this.state.disabled}
openCalendarAriaLabel={(selectedDate) => "Choose Date" + (selectedDate ? `, selected date is ${selectedDate}` : "")}
placeholder="YYYY/MM/DD"
nextMonthAriaLabel="Next month"
previousMonthAriaLabel="Previous month"
todayAriaLabel="Today"
/>,
props
);
}
onTextAreaStateChange(value: string) {
this.setState(
{
value: value,
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
);
}
buildTextArea(props: FormFieldProps) {
return this.buildFormField(<Textarea onChange={({ detail }) => this.onTextAreaStateChange(detail.value)} readOnly={this.isReadOnly()} autoFocus={this.isAutoFocus()} value={this.state.stringVal()} disabled={this.state.disabled} placeholder={`Enter ${this.props.param.title} ...`} />, props);
}
buildTextAreaArray(props: FormFieldProps) {
return this.buildFormField(<Textarea disabled={this.state.disabled} onChange={({ detail }) => this.onTextAreaStateChange(detail.value)} readOnly={this.isReadOnly()} autoFocus={this.isAutoFocus()} value={this.state.stringVal()} placeholder={`Enter ${this.props.param.title} ...`} />, props);
}
onToggleStateChange(value: boolean) {
this.setState(
{
value: value,
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
);
}
buildToggle(props: FormFieldProps) {
return this.buildFormField(
<Toggle
checked={this.state.booleanVal()}
disabled={this.state.disabled}
onChange={(event) => {
this.onToggleStateChange(event.detail.checked);
}}
></Toggle>,
props
);
}
buildExpandable(props: FormFieldProps) {
return (
<ExpandableSection
headerText={this.props.param.optional ? <span>{this.props.param.title} <i>- optional</i></span> : this.props.param.title}
expanded={this.state.booleanVal()}
onChange={(event) => {
this.onToggleStateChange(event.detail.expanded);
}}
></ExpandableSection>
)
}
onRadioGroupStateChange(value: string) {
this.setState(
{
value: value,
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
);
}
onFileUploadStateChange(value: File[]) {
this.getFileAsBase64String(value[0]).then((result) => {
this.setState(
{
value: result,
value2: value
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
)
})
}
buildRadioGroup(props: FormFieldProps) {
return this.buildFormField(<RadioGroup name={this.props.param.name} value={this.state.value} items={this.state.options} onChange={(event) => this.onRadioGroupStateChange(event.detail.value)} />, props);
}
buildTilesGroup(props: FormFieldProps) {
return this.buildFormField(<Tiles name={this.props.param.name} value={this.state.value} items={this.state.options} onChange={(event) => this.onRadioGroupStateChange(event.detail.value)} />, props);
}
onSelectStateChange(selectedOption: SelectProps.Option) {
this.setState(
{
value: selectedOption.value,
},
() => {
if (this.triggerValidate()) {
this.setState(
{
selectedOption: selectedOption,
},
this.setStateCallback
);
}
}
);
}
getEmptyOptionsLabel(): string {
if (this.props.param.choices_empty_label) {
return this.props.param.choices_empty_label;
}
return "No Options";
}
buildSelect(props: FormFieldProps) {
let secondaryControl = this.buildFormFieldSecondaryControl();
return this.buildFormField(
<Select selectedOption={this.state.selectedOption}
options={this.state.options}
empty={this.getEmptyOptionsLabel()}
disabled={this.isReadOnly() || this.state.disabled}
onChange={(event) => this.onSelectStateChange(event.detail.selectedOption)}
triggerVariant={this.props.param.triggerVariant as SelectProps.TriggerVariant} />,
{
...props,
secondaryControl: secondaryControl,
}
);
}
onMultiSelectStateChange(selectedOptions: ReadonlyArray<SelectProps.Option>) {
const values: any[] = [];
const selectedOptions_: any[] = [];
selectedOptions.forEach((option) => {
values.push(option.value);
selectedOptions_.push(option);
});
this.setState(
{
value: values,
},
() => {
if (this.triggerValidate()) {
this.setState(
{
selectedOptions: selectedOptions_,
},
this.setStateCallback
);
}
}
);
}
buildMultiSelect(props: FormFieldProps) {
let secondaryControl = this.buildFormFieldSecondaryControl();
return this.buildFormField(<Multiselect selectedOptions={this.state.selectedOptions} options={this.state.options} filteringType="auto" disabled={this.state.disabled} onChange={(event) => this.onMultiSelectStateChange(event.detail.selectedOptions)} />, {
...props,
secondaryControl: secondaryControl,
});
}
getMemoryUnit(): string {
const defaultVal: any = this.props.param.default;
if (defaultVal && defaultVal.unit) {
return defaultVal.unit;
}
return "bytes";
}
onMemoryValueStateChange(value: string) {
this.setState(
{
value: {
value: Utils.asNumber(value, 0),
unit: this.getMemoryUnit(),
},
},
() => {
if (this.triggerValidate()) {
this.setStateCallback();
}
}
);
}
buildMemory(props: FormFieldProps) {
// todo - support multiple units
return this.buildFormField(
<Grid gridDefinition={[{ colspan: 10 }, { colspan: 2 }]}>
<Input
name={this.props.param.name}
value={this.state.memoryVal()}
type="number"
inputMode="numeric"
readOnly={this.isReadOnly()}
autoFocus={this.isAutoFocus()}
disabled={this.state.disabled}
autoComplete={false}
onKeyDown={this.onKeyDown}
onChange={(event) => {
this.onMemoryValueStateChange(event.detail.value);
}}
/>
<span>{this.getMemoryUnit()}</span>
</Grid>,
props
);
}
onAmountStateChange(value: string) {
// todo remove hardcoding for currency.
this.setState(
{
value: {
amount: Utils.asNumber(value, 0.0, true),
unit: "USD",
},
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
);
}
buildAmount(props: FormFieldProps) {
return this.buildFormField(
<Grid gridDefinition={[{ colspan: 10 }, { colspan: 2 }]}>
<Input
name={this.props.param.name}
value={this.state.amountVal()}
type="number"
inputMode="decimal"
disabled={this.state.disabled}
readOnly={this.isReadOnly()}
autoFocus={this.isAutoFocus()}
autoComplete={false}
onKeyDown={this.onKeyDown}
onChange={(event) => {
this.onAmountStateChange(event.detail.value);
}}
/>
<span>USD</span>
</Grid>,
props
);
}
buildStaticText() {
let textBlock;
const paramType = this.props.param.param_type;
if (paramType === "heading1") {
textBlock = <h1>{this.getParamMeta().title}</h1>;
} else if (paramType === "heading2") {
textBlock = <h2>{this.getParamMeta().title}</h2>;
} else if (paramType === "heading3") {
textBlock = <h3>{this.getParamMeta().title}</h3>;
} else if (paramType === "heading4") {
textBlock = <h4>{this.getParamMeta().title}</h4>;
} else if (paramType === "heading5") {
textBlock = <h5>{this.getParamMeta().title}</h5>;
} else if (paramType === "heading6") {
textBlock = <h6>{this.getParamMeta().title}</h6>;
} else if (paramType === "paragraph") {
textBlock = <p>{this.getParamMeta().title}</p>;
}
return (
<div>
{textBlock}
{Utils.isNotEmpty(this.getParamMeta().description) && <p>{this.getParamMeta().description}</p>}
</div>
);
}
buildFileUpload(props: FormFieldProps) {
return this.buildFormField(
<FileUpload
onChange={({ detail }) => this.onFileUploadStateChange(detail.value)}
value={this.state.fileVal()}
i18nStrings={{
uploadButtonText: e =>
e ? "Choose files" : "Choose file",
dropzoneText: e =>
e
? "Drop files to upload"
: "Drop file to upload",
removeFileAriaLabel: e =>
`Remove file ${e + 1}`,
limitShowFewer: "Show fewer files",
limitShowMore: "Show more files",
errorIconAriaLabel: "Error"
}}
showFileSize
/>,
props
)
}
onContainerArrayStateChange(event: IdeaFormFieldStateChangeEvent, index: number) {
const values: Record<string, string>[] = this.getListOfStringRecords();
if (!event.param.name || !values[index]) {
this.setState({
errorMessage: "Unable to map container state change."
})
} else {
values[index][event.param.name] = event.value
this.setState(
{
value: values,
},
() => {
if (this.triggerValidate()) {
this.setState({}, this.setStateCallback);
}
}
);
}
}
buildContainerArray(props: FormFieldProps): React.ReactNode {
return this.buildFormField(
<ColumnLayout columns={1}>
{this.getListOfStringRecords().length === 0 && (
<Button
variant="normal"
onClick={() => {
this.setState(
{
value: [{}],
},
this.setStateCallback
);
}}
>
<FontAwesomeIcon icon={faAdd} />
</Button>
)}
{
this.getListOfStringRecords().length > 0 && this.getListOfStringRecords().map((value, index) => {
const numOfColumns = this.props.param.container_items?.length ? this.props.param.container_items.length : 0
return (
<ColumnLayout columns={numOfColumns} key={`${this.props.param.name}-${index}`}>
{
(() => {
let container: JSX.Element[] = [];
this.props.param.container_items?.map((form) => (
container.push(<IdeaFormField
key={`${form.name}`}
module={`${form.name}`}
param={form}
onStateChange={(event) => {
this.onContainerArrayStateChange(event, index)
}}
onFetchOptions={this.props.onFetchOptions}
stretch={props.stretch}
toolsOpen={this.props.toolsOpen}
tools={this.props.tools}
onToolsChange={this.props.onToolsChange}
/>)
))
return container;
})()
}
</ColumnLayout>
);
})
}
{
this.getListOfStringRecords().length > 0 && (
<SpaceBetween size="xs" direction="horizontal">
<Button
variant="normal"
onClick={() => {
const values = this.getListOfStringRecords();
values.push({});
this.setState(
{
value: values,
},
this.setStateCallback
);
}}
>
<FontAwesomeIcon icon={faAdd} />
</Button>
<Button
variant="normal"
onClick={() => {
const values = this.getListOfStringRecords();
values.pop();
this.setState(
{
value: values,
},
this.setStateCallback
);
}}
>
<FontAwesomeIcon icon={faRemove} />
</Button>
</SpaceBetween>
)
}
</ColumnLayout>,
props
)
}
buildAttributeEditor(props: FormFieldProps) {
const container_items = this.props.param.container_items
return this.buildFormField(<AttributeEditor
onAddButtonClick={() => this.setState({
value: [...this.getListOfAttributeEditorRecords(), {key: "", value: ""}],
}, this.setStateCallback
)}
onRemoveButtonClick={(changeEvent) => {
const tmpItems = [...this.getListOfAttributeEditorRecords()];
tmpItems.splice(changeEvent.detail.itemIndex, 1);
this.setState({
value: tmpItems
}, this.setStateCallback)
}}
addButtonText={`Add ${this.props.param.attributes_editor_type}`}
removeButtonText={`Remove ${this.props.param.attributes_editor_type}`}
items={this.getListOfAttributeEditorRecords()}
empty={`There are no ${this.props.param.attributes_editor_type} that have been added`}
definition={(() => {
if (container_items?.length === 2) {
const key = container_items[0]
const value = container_items[1]
return [
{
label: <FormField label={<span>{key.title}{key.description ? <i> - {key.description}</i> : ""}</span>} info={key.markdown && <Link variant="info" onFollow={() => this.handleToolsOpen(key.markdown)}>Info</Link>}></FormField>,
control: (item: { key: string, value: string, error?: string }, itemIndex: number) => (
<Input
value={item.key}
type={this.getContainerInputType(key)}
onChange={(e) => {
const tmpItems = [...this.getListOfAttributeEditorRecords()];
tmpItems[itemIndex].key = e.detail.value;
this.setState({
value: tmpItems
}, this.setStateCallback)
}}
></Input>
),
errorText: (item: {key: string, value: string, error?: string}) => { return item.error }
},
{
label: <FormField label={<span>{value.title}{value.description ? <i> - {value.description}</i> : ""}</span>} info={value.markdown && <Link variant="info" onFollow={() => this.handleToolsOpen(value.markdown)}>Info</Link>}></FormField>,
control: (item: { key: string, value: string, error?: string }, itemIndex: number) => (
<Input
value={item.value}
type={this.getContainerInputType(value)}
onChange={(e) => {
const tmpItems = [...this.getListOfAttributeEditorRecords()];
tmpItems[itemIndex].value = e.detail.value;
this.setState({
value: tmpItems
}, this.setStateCallback)
}}
></Input>
),
errorText: (item: {key: string, value: string, error?: string}) => { return item.error }
}
]
}
return [];
})()
}/>,
props
)
}
getRenderType(): string {
const type = this.getNativeType();
const param_type = this.props.param.param_type;
const multiple = this.props.param.multiple != null ? this.props.param.multiple : false;
const multiline = this.props.param.multiline != null ? this.props.param.multiline : false;
if (param_type && ["heading1", "heading2", "heading3", "heading4", "heading5", "heading6", "paragraph", "code"].includes(param_type)) {
return "static-text";
} else if (param_type === "file-upload") {
return "file-upload";
}
if (multiple) {
if (param_type === "select") {
return "multi-select";
} else if (param_type === "select_or_text") {
return "auto-suggest";
} else if (param_type === "container"){
return "parent_parameter_array"
} else if (param_type === "attribute_editor") {
return "attribute_editor"
}
else {
if (multiline) {
return "textarea-array";
} else {
return "input-array";
}
}
} else {
if (type === "string" || type === "number") {
if (multiline) {
return "textarea";
} else if (param_type === "select") {
return "select";
} else if (param_type === "select_or_text") {
return "auto-suggest";
} else if (param_type === "autocomplete") {
return "auto-complete";
} else if (param_type === "password") {
return "password";
} else if (param_type === "new-password") {
return "new-password";
} else if (param_type === "datepicker") {
return "datepicker";
} else if (param_type === "tiles") {
return "tiles";
} else if (param_type === "radio-group") {
return "radio-group";
}
return "input";
} else if (type === "boolean") {
if (param_type === "expandable") {
return "expandable"
}
return "toggle";
} else if (type === "amount") {
return "amount";
} else if (type === "memory") {
return "memory";
} else {
return "input";
}
}
}
isVisible(): boolean {
if (this.props.visible != null) {
return this.props.visible;
}
return true;
}
render() {
const type = this.getRenderType();
const stretch = this.props.stretch != null ? this.props.stretch : false;
const formFields: React.ReactNode[] = [];
if (type === "input") {
formFields.push(this.buildInput({ stretch: stretch }));
} else if (type === "input-array") {
formFields.push(this.buildInputArray({ stretch: stretch }));
} else if (type === "password") {
formFields.push(this.buildPassword(false, { stretch: stretch }));
} else if (type === "new-password") {
formFields.push(this.buildPassword(false, { stretch: stretch }));
formFields.push(this.buildPassword(true, { stretch: stretch }));
} else if (type === "auto-suggest") {
formFields.push(this.buildAutoSuggest({ stretch: stretch }));
} else if (type === "auto-complete") {
formFields.push(this.buildAutoComplete({ stretch: stretch }));
} else if (type === "textarea") {
formFields.push(this.buildTextArea({ stretch: stretch }));
} else if (type === "textarea-array") {
formFields.push(this.buildTextAreaArray({ stretch: stretch }));
} else if (type === "toggle") {
formFields.push(this.buildToggle({ stretch: stretch }));
} else if (type === "expandable") {
formFields.push(this.buildExpandable({stretch: stretch}));
} else if (type === "radio-group") {
formFields.push(this.buildRadioGroup({ stretch: stretch }));
} else if (type === "select") {
formFields.push(this.buildSelect({ stretch: stretch }));
} else if (type === "multi-select") {
formFields.push(this.buildMultiSelect({ stretch: stretch }));
} else if (type === "memory") {
formFields.push(this.buildMemory({ stretch: stretch }));
} else if (type === "amount") {
formFields.push(this.buildAmount({ stretch: stretch }));
} else if (type === "static-text") {
formFields.push(this.buildStaticText());
} else if (type === "datepicker") {
formFields.push(this.buildDatePicker({ stretch: stretch }));
} else if (type === "file-upload") {
formFields.push(this.buildFileUpload({ stretch: stretch }));
} else if (type === "tiles") {
formFields.push(this.buildTilesGroup({ stretch: stretch }));
} else if ( type === "attribute_editor") {
formFields.push(this.buildAttributeEditor({ stretch: stretch}));
} else if (type === "parent_parameter_array") {
formFields.push(this.buildContainerArray({ stretch: stretch }));
} else {
formFields.push(this.buildInput({ stretch: stretch }));
}
if (formFields.length === 1) {
return <ColumnLayout columns={1}>{formFields[0]}</ColumnLayout>;
} else {
return (
<ColumnLayout columns={1}>
{formFields.map((formField, index) => {
return <div key={index}>{formField}</div>;
})}
</ColumnLayout>
);
}
}
}
export default IdeaFormField;