'use strict'; import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {Trans} from 'react-i18next'; import {withTranslation} from '../../lib/i18n'; import { NavButton, requiresAuthenticatedUser, Title, withPageHelpers } from '../../lib/page'; import { ACEEditor, Button, ButtonRow, Dropdown, Fieldset, Form, FormSendMethod, InputField, StaticField, TableSelect, withForm } from '../../lib/form'; import {withErrorHandling} from '../../lib/error-handling'; import {DeleteModalDialog} from "../../lib/modals"; import {getFieldTypes} from './helpers'; import validators from '../../../../shared/validators'; import slugify from 'slugify'; import { DateFormat, parseBirthday, parseDate } from '../../../../shared/date'; import styles from "../../lib/styles.scss"; import 'brace/mode/json'; import 'brace/mode/handlebars'; @withTranslation() @withForm @withPageHelpers @withErrorHandling @requiresAuthenticatedUser export default class CUD extends Component { constructor(props) { super(props); this.state = {}; this.fieldTypes = getFieldTypes(props.t); this.initForm({ serverValidation: { url: `rest/fields-validate/${this.props.list.id}`, changed: ['key'], extra: ['id'] }, onChange: { name: ::this.onChangeName } }); } static propTypes = { action: PropTypes.string.isRequired, list: PropTypes.object, fields: PropTypes.array, entity: PropTypes.object } onChangeName(state, attr, oldValue, newValue) { const oldComputedKey = ('MERGE_' + slugify(oldValue, '_')).toUpperCase().replace(/[^A-Z0-9_]/g, ''); const oldKey = state.formState.getIn(['data', 'key', 'value']); if (oldKey === '' || oldKey === oldComputedKey) { const newKey = ('MERGE_' + slugify(newValue, '_')).toUpperCase().replace(/[^A-Z0-9_]/g, ''); state.formState = state.formState.setIn(['data', 'key', 'value'], newKey); } } componentDidMount() { if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity, data => { data.settings = data.settings || {}; if (data.default_value === null) { data.default_value = ''; } if (data.type !== 'option') { data.group = null; } data.enumOptions = ''; data.dateFormat = DateFormat.EUR; data.renderTemplate = ''; switch (data.type) { case 'checkbox-grouped': case 'radio-grouped': case 'dropdown-grouped': case 'json': data.renderTemplate = data.settings.renderTemplate; break; case 'radio-enum': case 'dropdown-enum': data.enumOptions = this.renderEnumOptions(data.settings.options); data.renderTemplate = data.settings.renderTemplate; break; case 'date': case 'birthday': data.dateFormat = data.settings.dateFormat; break; } data.orderListBefore = data.orderListBefore.toString(); data.orderSubscribeBefore = data.orderSubscribeBefore.toString(); data.orderManageBefore = data.orderManageBefore.toString(); }); } else { this.populateFormValues({ name: '', type: 'text', key: '', default_value: '', group: null, renderTemplate: '', enumOptions: '', dateFormat: 'eur', orderListBefore: 'end', // possible values are / 'end' / 'none' orderSubscribeBefore: 'end', orderManageBefore: 'end' }); } } localValidateFormValues(state) { const t = this.props.t; if (!state.getIn(['name', 'value'])) { state.setIn(['name', 'error'], t('nameMustNotBeEmpty')); } else { state.setIn(['name', 'error'], null); } const keyServerValidation = state.getIn(['key', 'serverValidation']); if (!validators.mergeTagValid(state.getIn(['key', 'value']))) { state.setIn(['key', 'error'], t('mergeTagIsInvalidMayMustBeUppercaseAnd')); } else if (!keyServerValidation) { state.setIn(['key', 'error'], t('validationIsInProgress')); } else if (keyServerValidation.exists) { state.setIn(['key', 'error'], t('anotherFieldWithTheSameMergeTagExists')); } else { state.setIn(['key', 'error'], null); } const type = state.getIn(['type', 'value']); const group = state.getIn(['group', 'value']); if (type === 'option' && !group) { state.setIn(['group', 'error'], t('groupHasToBeSelected')); } else { state.setIn(['group', 'error'], null); } const defaultValue = state.getIn(['default_value', 'value']); if (defaultValue === '') { state.setIn(['default_value', 'error'], null); } else if (type === 'number' && !/^[0-9]*$/.test(defaultValue.trim())) { state.setIn(['default_value', 'error'], t('defaultValueIsNotIntegerNumber')); } else if (type === 'date' && !parseDate(state.getIn(['dateFormat', 'value']), defaultValue)) { state.setIn(['default_value', 'error'], t('defaultValueIsNotAProperlyFormattedDate')); } else if (type === 'birthday' && !parseBirthday(state.getIn(['dateFormat', 'value']), defaultValue)) { state.setIn(['default_value', 'error'], t('defaultValueIsNotAProperlyFormatted')); } else { state.setIn(['default_value', 'error'], null); } if (type === 'radio-enum' || type === 'dropdown-enum') { const enumOptions = this.parseEnumOptions(state.getIn(['enumOptions', 'value'])); if (enumOptions.errors) { state.setIn(['enumOptions', 'error'],
{enumOptions.errors.map((err, idx) =>
{err}
)}
); } else { state.setIn(['enumOptions', 'error'], null); if (defaultValue !== '' && !(enumOptions.options.find(x => x.key === defaultValue))) { state.setIn(['default_value', 'error'], t('defaultValueIsNotOneOfTheAllowedOptions')); } } } else { state.setIn(['enumOptions', 'error'], null); } } parseEnumOptions(text) { const t = this.props.t; const errors = []; const options = []; const lines = text.split('\n'); for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) { const line = lines[lineIdx].trim(); if (line != '') { const matches = line.match(/^([^|]*)[|](.*)$/); if (matches) { const key = matches[1].trim(); const label = matches[2].trim(); options.push({ key, label }); } else { errors.push(t('errrorOnLineLine', { line: lineIdx + 1})); } } } if (errors.length) { return { errors }; } else { return { options }; } } renderEnumOptions(options) { return options.map(opt => `${opt.key}|${opt.label}`).join('\n'); } async submitHandler() { const t = this.props.t; let sendMethod, url; if (this.props.entity) { sendMethod = FormSendMethod.PUT; url = `rest/fields/${this.props.list.id}/${this.props.entity.id}` } else { sendMethod = FormSendMethod.POST; url = `rest/fields/${this.props.list.id}` } try { this.disableForm(); this.setFormStatusMessage('info', t('saving')); const submitSuccessful = await this.validateAndSendFormValuesToURL(sendMethod, url, data => { if (data.default_value.trim() === '') { data.default_value = null; } if (data.type !== 'option') { data.group = null; } data.settings = {}; switch (data.type) { case 'checkbox-grouped': case 'radio-grouped': case 'dropdown-grouped': case 'json': data.settings.renderTemplate = data.renderTemplate; break; case 'radio-enum': case 'dropdown-enum': data.settings.options = this.parseEnumOptions(data.enumOptions).options; data.settings.renderTemplate = data.renderTemplate; break; case 'date': case 'birthday': data.settings.dateFormat = data.dateFormat; break; } delete data.renderTemplate; delete data.enumOptions; delete data.dateFormat; if (data.type === 'option') { data.orderListBefore = data.orderSubscribeBefore = data.orderManageBefore = 'none'; } else { data.orderListBefore = Number.parseInt(data.orderListBefore) || data.orderListBefore; data.orderSubscribeBefore = Number.parseInt(data.orderSubscribeBefore) || data.orderSubscribeBefore; data.orderManageBefore = Number.parseInt(data.orderManageBefore) || data.orderManageBefore; } }); if (submitSuccessful) { this.navigateToWithFlashMessage(`/lists/${this.props.list.id}/fields`, 'success', t('fieldSaved')); } else { this.enableForm(); this.setFormStatusMessage('warning', t('thereAreErrorsInTheFormPleaseFixThemAnd')); } } catch (error) { throw error; } } render() { const t = this.props.t; const isEdit = !!this.props.entity; const getOrderOptions = fld => { return [ {key: 'none', label: t('notVisible')}, ...this.props.fields.filter(x => (!this.props.entity || x.id !== this.props.entity.id) && x[fld] !== null && x.type !== 'option').sort((x, y) => x[fld] - y[fld]).map(x => ({ key: x.id.toString(), label: `${x.name} (${this.fieldTypes[x.type].label})`})), {key: 'end', label: t('endOfList')} ]; }; const typeOptions = Object.keys(this.fieldTypes).map(key => ({key, label: this.fieldTypes[key].label})); const type = this.getFormValue('type'); let fieldSettings = null; switch (type) { case 'text': case 'website': case 'longtext': case 'gpg': case 'number': fieldSettings =
; break; case 'checkbox-grouped': case 'radio-grouped': case 'dropdown-grouped': fieldSettings =
You can control the appearance of the merge tag with this template. The template uses handlebars syntax and you can find all values from {'{{values}}'} array, for example {'{{#each values}} {{this}} {{/each}}'}. If template is not defined then multiple values are joined with commas.} />
; break; case 'radio-enum': case 'dropdown-enum': fieldSettings =
Specify the options to select from in the following format:key|label. For example:
au|Australia
at|Austria
} /> Default key (e.g. au used when the field is empty.')}/> You can control the appearance of the merge tag with this template. The template uses handlebars syntax and you can find all values from {'{{values}}'} array. Each entry in the array is an object with attributes key and label. For example {'{{#each values}} {{this.value}} {{/each}}'}. If template is not defined then multiple values are joined with commas.} />
; break; case 'date': fieldSettings =
Default value used when the field is empty.}/>
; break; case 'birthday': fieldSettings =
Default value used when the field is empty.}/>
; break; case 'json': fieldSettings =
Default key (e.g. au used when the field is empty.')}/> You can use this template to render JSON values (if the JSON is an array then the array is exposed as values, otherwise you can access the JSON keys directly).} />
; break; case 'option': const fieldsGroupedColumns = [ { data: 4, title: "#" }, { data: 1, title: t('name') }, { data: 2, title: t('type'), render: data => this.fieldTypes[data].label, sortable: false, searchable: false }, { data: 3, title: t('mergeTag') } ]; fieldSettings =
; break; } return (
{isEdit && } {isEdit ? t('editField') : t('createField')}
{isEdit ? {(this.fieldTypes[this.getFormValue('type')] || {}).label} : } {fieldSettings} {type !== 'option' &&
}
); } }