work in progress on custom fields
This commit is contained in:
parent
60d3875c00
commit
19f0c1bd97
4 changed files with 160 additions and 20 deletions
|
@ -70,6 +70,7 @@ export default class CUD extends Component {
|
||||||
if (data.default_value === null) {
|
if (data.default_value === null) {
|
||||||
data.default_value = '';
|
data.default_value = '';
|
||||||
}
|
}
|
||||||
|
// TODO: Construct form fields from settings
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -79,6 +80,7 @@ export default class CUD extends Component {
|
||||||
key: '',
|
key: '',
|
||||||
default_value: '',
|
default_value: '',
|
||||||
group: null,
|
group: null,
|
||||||
|
renderTemplate: '',
|
||||||
orderListBefore: 'end', // possible values are <numeric id> / 'end' / 'none'
|
orderListBefore: 'end', // possible values are <numeric id> / 'end' / 'none'
|
||||||
orderSubscribeBefore: 'end',
|
orderSubscribeBefore: 'end',
|
||||||
orderManageBefore: 'end',
|
orderManageBefore: 'end',
|
||||||
|
@ -110,6 +112,12 @@ export default class CUD extends Component {
|
||||||
} else {
|
} else {
|
||||||
state.setIn(['key', 'error'], null);
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitHandler() {
|
async submitHandler() {
|
||||||
|
@ -132,6 +140,8 @@ export default class CUD extends Component {
|
||||||
if (data.default_value.trim() === '') {
|
if (data.default_value.trim() === '') {
|
||||||
data.default_value = null;
|
data.default_value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Construct settings field
|
||||||
});
|
});
|
||||||
|
|
||||||
if (submitSuccessful) {
|
if (submitSuccessful) {
|
||||||
|
@ -159,17 +169,124 @@ export default class CUD extends Component {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
const isEdit = !!this.props.entity;
|
const isEdit = !!this.props.entity;
|
||||||
|
|
||||||
/*
|
|
||||||
const orderColumns = [
|
|
||||||
{ data: 1, title: t('Field name'), sortable: false, searchable: false }
|
|
||||||
];
|
|
||||||
*/
|
|
||||||
|
|
||||||
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');
|
const type = this.getFormValue('type');
|
||||||
|
|
||||||
// <ACEEditor id={selectedTemplate} height="500px" mode={this.templateSettings[selectedTemplate].mode}/>
|
let fieldSettings = null;
|
||||||
|
switch (type) {
|
||||||
|
case 'text':
|
||||||
|
case 'website':
|
||||||
|
case 'longtext':
|
||||||
|
case 'gpg':
|
||||||
|
case 'number':
|
||||||
|
fieldSettings =
|
||||||
|
<Fieldset label={t('Field settings')}>
|
||||||
|
<InputField id="default_value" label={t('Default value')} help={t('Default value used when the field is empty.')}/>
|
||||||
|
</Fieldset>;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'checkbox':
|
||||||
|
case 'radio-grouped':
|
||||||
|
case 'dropdown-grouped':
|
||||||
|
fieldSettings =
|
||||||
|
<Fieldset label={t('Field settings')}>
|
||||||
|
<ACEEditor
|
||||||
|
id="renderTemplate"
|
||||||
|
label={t('Template')}
|
||||||
|
height="250px"
|
||||||
|
mode="handlebars"
|
||||||
|
help={<Trans>You can control the appearance of the merge tag with this template. The template
|
||||||
|
uses handlebars syntax and you can find all values from <code>{'{{values}}'}</code> array, for
|
||||||
|
example <code>{'{{#each values}} {{this}} {{/each}}'}</code>. If template is not defined then
|
||||||
|
multiple values are joined with commas.</Trans>}
|
||||||
|
/>
|
||||||
|
</Fieldset>;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'radio-enum':
|
||||||
|
case 'dropdown-enum':
|
||||||
|
fieldSettings =
|
||||||
|
<Fieldset label={t('Field settings')}>
|
||||||
|
<ACEEditor
|
||||||
|
id="enumOptions"
|
||||||
|
label={t('Options')}
|
||||||
|
height="250px"
|
||||||
|
mode="text"
|
||||||
|
help={<Trans><div>Specify the options to select from in the following format:<code>key|label</code>. For example:</div>
|
||||||
|
<div><code>au|Australia</code></div><div><code>at|Austria</code></div></Trans>}
|
||||||
|
/>
|
||||||
|
<InputField id="default_value" label={t('Default value')} help={<Trans>Default key (e.g. <code>au</code> used when the field is empty.')</Trans>}/>
|
||||||
|
<ACEEditor
|
||||||
|
id="renderTemplate"
|
||||||
|
label={t('Template')}
|
||||||
|
height="250px"
|
||||||
|
mode="handlebars"
|
||||||
|
help={<Trans>You can control the appearance of the merge tag with this template. The template
|
||||||
|
uses handlebars syntax and you can find all values from <code>{'{{values}}'}</code> array.
|
||||||
|
Each entry in the array is an object with attributes <code>key</code> and <code>label</code>.
|
||||||
|
For example <code>{'{{#each values}} {{this.value}} {{/each}}'}</code>. If template is not defined then
|
||||||
|
multiple values are joined with commas.</Trans>}
|
||||||
|
/>
|
||||||
|
</Fieldset>;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'date':
|
||||||
|
fieldSettings =
|
||||||
|
<Fieldset label={t('Field settings')}>
|
||||||
|
<Dropdown id="dateFormat" label={t('Date format')}
|
||||||
|
options={[
|
||||||
|
{key: 'us', label: t('MM/DD/YYYY')},
|
||||||
|
{key: 'eur', label: t('DD/MM/YYYY')}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<InputField id="default_value" label={t('Default value')} help={<Trans>Default value used when the field is empty.')</Trans>}/>
|
||||||
|
</Fieldset>;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'birthday':
|
||||||
|
fieldSettings =
|
||||||
|
<Fieldset label={t('Field settings')}>
|
||||||
|
<Dropdown id="dateFormat" label={t('Date format')}
|
||||||
|
options={[
|
||||||
|
{key: 'us', label: t('MM/DD')},
|
||||||
|
{key: 'eur', label: t('DD/MM')}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<InputField id="default_value" label={t('Default value')} help={<Trans>Default value used when the field is empty.')</Trans>}/>
|
||||||
|
</Fieldset>;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'json':
|
||||||
|
fieldSettings = <Fieldset label={t('Field settings')}>
|
||||||
|
<InputField id="default_value" label={t('Default value')} help={<Trans>Default key (e.g. <code>au</code> used when the field is empty.')</Trans>}/>
|
||||||
|
<ACEEditor
|
||||||
|
id="renderTemplate"
|
||||||
|
label={t('Template')}
|
||||||
|
height="250px"
|
||||||
|
mode="json"
|
||||||
|
help={<Trans>You can use this template to render JSON values (if the JSON is an array then the array is
|
||||||
|
exposed as <code>values</code>, otherwise you can access the JSON keys directly).</Trans>}
|
||||||
|
/>
|
||||||
|
</Fieldset>;
|
||||||
|
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('Merge Tag') }
|
||||||
|
];
|
||||||
|
|
||||||
|
fieldSettings =
|
||||||
|
<Fieldset label={t('Field settings')}>
|
||||||
|
<TableSelect id="group" label={t('Group')} withHeader dropdown dataUrl={`/rest/fields-grouped-table/${this.props.list.id}`} columns={fieldsGroupedColumns} selectionLabelIndex={1} help={t('Select group to which the options should belong.')}/>
|
||||||
|
<InputField id="default_value" label={t('Default value')} help={t('Default value used when the field is empty.')}/>
|
||||||
|
</Fieldset>;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -193,10 +310,8 @@ export default class CUD extends Component {
|
||||||
|
|
||||||
<InputField id="key" label={t('Merge tag')}/>
|
<InputField id="key" label={t('Merge tag')}/>
|
||||||
|
|
||||||
{/* type && this.fieldTypes[type].renderSettings */}
|
{fieldSettings}
|
||||||
<Fieldset label={t('Field settings')}>
|
|
||||||
<InputField id="default_value" label={t('Default value')} help={t('Default value used when the field is empty.')}/>
|
|
||||||
</Fieldset>
|
|
||||||
<Fieldset label={t('Field order')}>
|
<Fieldset label={t('Field order')}>
|
||||||
<Dropdown id="orderListBefore" label={t('Listings (before)')} options={this.state.orderListOptions} help={t('Select the field before which this field should appeara in listings. To exclude the field from listings, select "Not visible".')}/>
|
<Dropdown id="orderListBefore" label={t('Listings (before)')} options={this.state.orderListOptions} help={t('Select the field before which this field should appeara in listings. To exclude the field from listings, select "Not visible".')}/>
|
||||||
<Dropdown id="orderSubscribeBefore" label={t('Subscription form (before)')} options={this.state.orderSubscribeOptions} help={t('Select the field before which this field should appear in new subscription form. To exclude the field from the new subscription form, select "Not visible".')}/>
|
<Dropdown id="orderSubscribeBefore" label={t('Subscription form (before)')} options={this.state.orderSubscribeOptions} help={t('Select the field before which this field should appear in new subscription form. To exclude the field from the new subscription form, select "Not visible".')}/>
|
||||||
|
|
|
@ -8,25 +8,21 @@ export function getFieldTypes(t) {
|
||||||
const fieldTypes = {
|
const fieldTypes = {
|
||||||
text: {
|
text: {
|
||||||
label: t('Text'),
|
label: t('Text'),
|
||||||
renderSettings:
|
|
||||||
<Fieldset label={t('Field settings')}>
|
|
||||||
<InputField id="default_value" label={t('Default value')} help={t('Default value used when the field is empty.')}/>
|
|
||||||
</Fieldset>
|
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
label: t('Website')
|
label: t('Website'),
|
||||||
},
|
},
|
||||||
longtext: {
|
longtext: {
|
||||||
label: t('Multi-line text')
|
label: t('Multi-line text'),
|
||||||
},
|
},
|
||||||
gpg: {
|
gpg: {
|
||||||
label: t('GPG Public Key')
|
label: t('GPG Public Key'),
|
||||||
},
|
},
|
||||||
number: {
|
number: {
|
||||||
label: t('Number')
|
label: t('Number'),
|
||||||
},
|
},
|
||||||
checkbox: {
|
checkbox: {
|
||||||
label: t('Checkboxes (from option fields)')
|
label: t('Checkboxes (from option fields)'),
|
||||||
},
|
},
|
||||||
'radio-grouped': {
|
'radio-grouped': {
|
||||||
label: t('Radio Buttons (from option fields)')
|
label: t('Radio Buttons (from option fields)')
|
||||||
|
|
|
@ -78,6 +78,7 @@ fieldTypes['date'] = fieldTypes['birthday'] = {
|
||||||
grouped: false
|
grouped: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const groupedTypes = Object.keys(fieldTypes).filter(key => fieldTypes[key].grouped);
|
||||||
|
|
||||||
function hash(entity) {
|
function hash(entity) {
|
||||||
return hasher.hash(filterObject(entity, hashKeys));
|
return hasher.hash(filterObject(entity, hashKeys));
|
||||||
|
@ -150,6 +151,29 @@ async function listDTAjax(context, listId, params) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function listGroupedDTAjax(context, listId, params) {
|
||||||
|
return await dtHelpers.ajaxListWithPermissions(
|
||||||
|
context,
|
||||||
|
[{ entityTypeId: 'list', requiredOperations: ['manageFields'] }],
|
||||||
|
params,
|
||||||
|
builder => builder
|
||||||
|
.from('custom_fields')
|
||||||
|
.innerJoin('lists', 'custom_fields.list', 'lists.id')
|
||||||
|
.where('custom_fields.list', listId)
|
||||||
|
.whereIn('custom_fields.type', groupedTypes),
|
||||||
|
[ 'custom_fields.id', 'custom_fields.name', 'custom_fields.type', 'custom_fields.key', 'custom_fields.order_list' ],
|
||||||
|
{
|
||||||
|
orderByBuilder: (builder, orderColumn, orderDir) => {
|
||||||
|
if (orderColumn === 'custom_fields.order_list') {
|
||||||
|
builder.orderBy(knex.raw('-custom_fields.order_list'), orderDir === 'asc' ? 'desc' : 'asc'); // This is MySQL speciality. It sorts the rows in ascending order with NULL values coming last
|
||||||
|
} else {
|
||||||
|
builder.orderBy(orderColumn, orderDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function serverValidate(context, listId, data) {
|
async function serverValidate(context, listId, data) {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
|
@ -342,6 +366,7 @@ module.exports = {
|
||||||
getById,
|
getById,
|
||||||
list,
|
list,
|
||||||
listDTAjax,
|
listDTAjax,
|
||||||
|
listGroupedDTAjax,
|
||||||
create,
|
create,
|
||||||
updateWithConsistencyCheck,
|
updateWithConsistencyCheck,
|
||||||
remove,
|
remove,
|
||||||
|
|
|
@ -10,6 +10,10 @@ router.postAsync('/fields-table/:listId', passport.loggedIn, async (req, res) =>
|
||||||
return res.json(await fields.listDTAjax(req.context, req.params.listId, req.body));
|
return res.json(await fields.listDTAjax(req.context, req.params.listId, req.body));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.postAsync('/fields-grouped-table/:listId', passport.loggedIn, async (req, res) => {
|
||||||
|
return res.json(await fields.listGroupedDTAjax(req.context, req.params.listId, req.body));
|
||||||
|
});
|
||||||
|
|
||||||
router.getAsync('/fields/:listId/:fieldId', passport.loggedIn, async (req, res) => {
|
router.getAsync('/fields/:listId/:fieldId', passport.loggedIn, async (req, res) => {
|
||||||
const entity = await fields.getById(req.context, req.params.listId, req.params.fieldId);
|
const entity = await fields.getById(req.context, req.params.listId, req.params.fieldId);
|
||||||
entity.hash = fields.hash(entity);
|
entity.hash = fields.hash(entity);
|
||||||
|
|
Loading…
Reference in a new issue