diff --git a/client/package.json b/client/package.json
index 1c050ad3..515670ff 100644
--- a/client/package.json
+++ b/client/package.json
@@ -30,6 +30,7 @@
"react-dom": "^15.6.1",
"react-i18next": "^4.6.1",
"react-router-dom": "^4.1.1",
+ "slugify": "^1.1.0",
"url-parse": "^1.1.9"
},
"devDependencies": {
diff --git a/client/src/lib/form.js b/client/src/lib/form.js
index e2494c9f..b39ac952 100644
--- a/client/src/lib/form.js
+++ b/client/src/lib/form.js
@@ -143,7 +143,8 @@ class StaticField extends Component {
static propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
- help: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
+ help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+ className: PropTypes.string
}
render() {
@@ -152,8 +153,13 @@ class StaticField extends Component {
const id = this.props.id;
const htmlId = 'form_' + id;
+ let className = 'form-control';
+ if (props.className) {
+ className += ' ' + props.className;
+ }
+
return wrapInput(null, htmlId, owner, props.label, props.help,
-
{props.children}
+ {props.children}
);
}
}
diff --git a/client/src/lib/page.css b/client/src/lib/page.css
index 28bfa709..f67709fb 100644
--- a/client/src/lib/page.css
+++ b/client/src/lib/page.css
@@ -23,6 +23,11 @@
display: block;
}
+.mt-form-disabled {
+ background-color: #eeeeee;
+ opacity: 1;
+}
+
.ace_editor {
border: 1px solid #ccc;
}
diff --git a/client/src/lists/fields/CUD.js b/client/src/lists/fields/CUD.js
index b47afd90..b3289d24 100644
--- a/client/src/lists/fields/CUD.js
+++ b/client/src/lists/fields/CUD.js
@@ -6,7 +6,7 @@ import { translate, Trans } from 'react-i18next';
import {requiresAuthenticatedUser, withPageHelpers, Title, NavButton} from '../../lib/page';
import {
withForm, Form, FormSendMethod, InputField, TextArea, TableSelect, ButtonRow, Button,
- Fieldset, Dropdown, AlignedRow, ACEEditor
+ Fieldset, Dropdown, AlignedRow, ACEEditor, StaticField
} from '../../lib/form';
import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling';
import {DeleteModalDialog} from "../../lib/delete";
@@ -14,6 +14,8 @@ import { getFieldTypes } from './field-types';
import axios from '../../lib/axios';
import interoperableErrors from '../../../../shared/interoperable-errors';
import validators from '../../../../shared/validators';
+import slugify from 'slugify';
+import { parseDate, parseBirthday } from '../../../../shared/fields';
@translate()
@withForm
@@ -33,6 +35,9 @@ export default class CUD extends Component {
url: `/rest/fields-validate/${this.props.list.id}`,
changed: ['key'],
extra: ['id']
+ },
+ onChange: {
+ name: ::this.onChangeName
}
});
}
@@ -43,6 +48,16 @@ export default class CUD extends Component {
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);
+ }
+ }
+
@withAsyncErrorHandler
async loadOrderOptions() {
const t = this.props.t;
@@ -70,7 +85,34 @@ export default class CUD extends Component {
if (data.default_value === null) {
data.default_value = '';
}
- // TODO: Construct form fields from settings
+
+ if (data.type !== 'option') {
+ data.group = null;
+ }
+
+ data.enumOptions = '';
+ data.dateFormat = 'eur';
+ data.renderTemplate = '';
+
+ switch (data.type) {
+ case 'checkbox':
+ 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.enumOptions);
+ data.renderTemplate = data.settings.renderTemplate;
+ break;
+
+ case 'date':
+ case 'birthday':
+ data.dateFormat = data.settings.dateFormat;
+ break;
+ }
});
} else {
@@ -81,6 +123,8 @@ export default class CUD extends Component {
default_value: '',
group: null,
renderTemplate: '',
+ enumOptions: '',
+ dateFormat: 'eur',
orderListBefore: 'end', // possible values are / 'end' / 'none'
orderSubscribeBefore: 'end',
orderManageBefore: 'end',
@@ -113,13 +157,71 @@ export default class CUD extends Component {
state.setIn(['key', 'error'], null);
}
- // TODO: Validate field settings:
- // TODO: parse and check options for enums
- // TODO: make sure group is selected for option
- // TODO: check default date/birthday is in the right format
- // TODO: check number is a number
+ const type = state.getIn(['type', 'value']);
+
+ const group = state.getIn(['group', 'value']);
+ if (type === 'option' && !group) {
+ state.setIn(['group', 'error'], t('Group has to be selected'));
+ } else {
+ state.setIn(['group', 'error'], null);
+ }
+
+ const defaultValue = state.getIn(['default_value', 'value']);
+ if (type === 'number' && !/^[0-9]*$/.test(defaultValue.trim())) {
+ state.setIn(['default_value', 'error'], t('Default value is not integer number'));
+ } else if (type === 'date' && !parseDate(state.getIn(['dateFormat', 'value']), defaultValue)) {
+ state.setIn(['default_value', 'error'], t('Default value is not a properly formatted date'));
+ } else if (type === 'birthday' && !parseBirthday(state.getIn(['dateFormat', 'value']), defaultValue)) {
+ state.setIn(['default_value', 'error'], t('Default value is not a properly formatted birthday date'));
+ } else {
+ state.setIn(['default_value', 'error'], null);
+ }
+
+ let enumOptionErrors;
+ if ((type === 'radio-enum' || type === 'dropdown-enum') && (enumOptionErrors = this.parseEnumOptions(state.getIn(['enumOptions', 'value'])).errors)) {
+ state.setIn(['enumOptions', 'error'], {enumOptionErrors.map((err, idx) =>
{err}
)}
);
+ } 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[key] = label;
+ } else {
+ errors.push(t('Errror on line {{ line }}', { line: lineIdx + 1}));
+ }
+ }
+ }
+
+ if (errors.length) {
+ return {
+ errors
+ };
+ } else {
+ return {
+ options
+ };
+ }
+ }
+
+ renderEnumOptions(options) {
+ return Object.keys(options).map(key => `${key}|${options[key]}`).join('\n');
+ }
+
+
async submitHandler() {
const t = this.props.t;
@@ -141,7 +243,34 @@ export default class CUD extends Component {
data.default_value = null;
}
- // TODO: Construct settings field
+ if (data.type !== 'option') {
+ data.group = null;
+ }
+
+ data.settings = {};
+ switch (data.type) {
+ case 'checkbox':
+ case 'radio-grouped':
+ case 'dropdown-grouped':
+ case 'json':
+ data.settings.renderTemplate = data.renderTemplate;
+ break;
+
+ case 'radio-enum':
+ case 'dropdown-enum':
+ data.settings.enumOptions = 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 (submitSuccessful) {
@@ -169,7 +298,7 @@ export default class CUD extends Component {
const t = this.props.t;
const isEdit = !!this.props.entity;
- const typeOptions = Object.keys(this.fieldTypes).map(key => ({key, label:this.fieldTypes[key].label}));
+ const typeOptions = Object.keys(this.fieldTypes).map(key => ({key, label: this.fieldTypes[key].label}));
const type = this.getFormValue('type');
@@ -240,7 +369,7 @@ export default class CUD extends Component {
{key: 'eur', label: t('DD/MM/YYYY')}
]}
/>
- Default value used when the field is empty.')}/>
+ Default value used when the field is empty.}/>
;
break;
@@ -253,7 +382,7 @@ export default class CUD extends Component {
{key: 'eur', label: t('DD/MM')}
]}
/>
- Default value used when the field is empty.')}/>
+ Default value used when the field is empty.}/>
;
break;
@@ -306,7 +435,11 @@ export default class CUD extends Component {