new: [metaFields] Adding support of sane_default + improving form & crud - WiP

pull/121/head
Sami Mokaddem 2022-11-14 09:04:35 +01:00
parent 866fbc2d51
commit 7d6696e079
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
8 changed files with 199 additions and 30 deletions

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
use Migrations\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;
class MetaFieldSaneDefault extends AbstractMigration
{
public $autoId = false; // turn off automatic `id` column create. We want it to be `int(10) unsigned`
public function change()
{
$exists = $this->table('meta_template_fields')->hasColumn('sane_default');
if (!$exists) {
$this->table('meta_template_fields')
->addColumn('sane_default', 'text', [
'default' => null,
'null' => true,
'limit' => MysqlAdapter::TEXT_LONG,
'comment' => 'List of sane default values to be proposed',
])
->addColumn('values_list', 'text', [
'default' => null,
'null' => true,
'limit' => MysqlAdapter::TEXT_LONG,
'comment' => 'List of values that have to be used',
])
->update();
}
}
}

View File

@ -7,5 +7,32 @@ use Cake\ORM\Entity;
class MetaTemplateField extends AppModel
{
protected $_virtual = ['form_type', 'form_options', ];
protected function _getFormType()
{
$formType = 'text';
if (!empty($this->sane_default) || !empty($this->values_list)) {
$formType = 'dropdown';
}
return $formType;
}
protected function _getFormOptions()
{
$formOptions = [];
if ($this->formType === 'dropdown') {
$selectOptions = !empty($this->sane_default) ? $this->sane_default : $this->values_list;
$selectOptions = array_combine($selectOptions, $selectOptions);
if (!empty($this->sane_default)) {
// $selectOptions['_custom'] = __('-- custom value --');
$selectOptions[] = ['value' => '_custom', 'text' => __('-- custom value --'), 'class' => 'custom-value'];
}
$selectOptions[''] = __('-- no value --');
$formOptions['options'] = $selectOptions;
// $formOptions['empty'] = [['value' => '', 'text' => __('-- select an options --'), 'hidden disabled selected value']];
}
return $formOptions;
}
}

View File

@ -27,6 +27,8 @@ class MetaTemplateFieldsTable extends AppTable
$this->hasMany('MetaFields');
$this->setDisplayField('field');
$this->getSchema()->setColumnType('sane_default', 'json');
$this->getSchema()->setColumnType('values_list', 'json');
$this->loadTypeHandlers();
}

View File

@ -30,6 +30,14 @@ echo $this->element('genericElements/IndexTable/index_table', [
'data_path' => 'multiple',
'field' => 'textarea'
],
[
'name' => __('Sane defaults'),
'data_path' => 'sane_default'
],
[
'name' => __('Values List'),
'data_path' => 'values_list'
],
[
'name' => __('Validation regex'),
'sort' => 'regex',

View File

@ -1,14 +1,87 @@
<?php
$controlParams = [
'options' => $fieldData['options'],
'empty' => $fieldData['empty'] ?? false,
'value' => $fieldData['value'] ?? null,
'multiple' => $fieldData['multiple'] ?? false,
'disabled' => $fieldData['disabled'] ?? false,
'class' => ($fieldData['class'] ?? '') . ' formDropdown form-select',
'default' => ($fieldData['default'] ?? null)
];
if (!empty($fieldData['label'])) {
$controlParams['label'] = $fieldData['label'];
$seed = 's-' . mt_rand();
$controlParams = [
'type' => 'select',
'options' => $fieldData['options'],
'empty' => $fieldData['empty'] ?? false,
'value' => $fieldData['value'] ?? null,
'multiple' => $fieldData['multiple'] ?? false,
'disabled' => $fieldData['disabled'] ?? false,
'class' => ($fieldData['class'] ?? '') . ' formDropdown form-select',
'default' => $fieldData['default'] ?? '',
];
if (!empty($fieldData['field'])) { // used for multi meta-field form
$controlParams['field'] = $fieldData['field'];
}
if (!empty($fieldData['label'])) {
$controlParams['label'] = $fieldData['label'];
}
if ($controlParams['options'] instanceof \Cake\ORM\Query) {
$controlParams['options'] = $controlParams['options']->all()->toList();
}
if (in_array('_custom', array_keys($controlParams['options']))) {
$customInputValue = $this->Form->getSourceValue($fieldData['field']);
if (!in_array($customInputValue, $controlParams['options'])) {
$controlParams['options'] = array_map(function ($option) {
if (is_array($option) && $option['value'] == '_custom') {
$option[] = 'selected';
}
return $option;
}, $controlParams['options']);
} else {
$customInputValue = '';
}
echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData);
$customControlParams = [
'value' => $fieldData['value'] ?? null,
'class' => 'd-none',
];
$controlParams['class'] .= ' dropdown-custom-value' . "-$seed";
$adaptedField = $fieldData['field'] . '_custom';
$controlParams['templates']['formGroup'] = sprintf(
'<label class="col-sm-2 col-form-label form-label" {{attrs}}>{{label}}</label><div class="col-sm-10 multi-metafield-input-container"><div class="d-flex form-dropdown-with-freetext input-group">{{input}}{{error}}%s</div></div>',
sprintf('<input type="text" class="form-control custom-value" field="%s" value="%s">', h($adaptedField), h($customInputValue))
);
}
echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData);
?>
<script>
(function() {
$(document).ready(function() {
const $select = $('select.dropdown-custom-value-<?= $seed ?>')
toggleFreetextSelectField($select[0]);
$select.attr('onclick', 'toggleFreetextSelectField(this)')
$select.parent().find('input.custom-value').attr('oninput', 'updateAssociatedSelect(this)')
// updateAssociatedSelect($select.parent().find('input.custom-value')[0])
// Multiple saves in dropdown doesn't work
// But multiple saves for custom works but save the first element as `_custom`
})
})()
function toggleFreetextSelectField(selectEl) {
const $select = $(selectEl)
const show = $select.val() == '_custom'
const $container = $(selectEl).parent()
let $freetextInput = $container.find('input.custom-value')
if (show) {
$freetextInput.removeClass('d-none')
} else {
$freetextInput.addClass('d-none')
}
}
function updateAssociatedSelect(input) {
const $input = $(input)
const $select = $input.parent().find('select')
const $customOption = $select.find('option.custom-value')
$customOption.val($input.val())
}
</script>
<style>
form div.form-dropdown-with-freetext input.custom-value {
flex-grow: 3;
}
</style>

View File

@ -31,7 +31,11 @@ foreach ($metaTemplate->meta_template_fields as $metaTemplateField) {
$metaField = reset($metaTemplateField->metaFields);
$fieldData = [
'label' => $metaTemplateField->label,
'type' => $metaTemplateField->formType,
];
if ($metaTemplateField->formType === 'dropdown') {
$fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions);
}
if (isset($metaField->id)) {
$fieldData['field'] = sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.%s.value', $metaField->meta_template_id, $metaField->meta_template_field_id, $metaField->id);
} else {
@ -63,7 +67,11 @@ foreach ($metaTemplate->meta_template_fields as $metaTemplateField) {
$fieldData = [
'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new.0', $metaTemplateField->meta_template_id, $metaTemplateField->id),
'label' => $metaTemplateField->label,
'type' => $metaTemplateField->formType,
];
if ($metaTemplateField->formType === 'dropdown') {
$fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions);
}
$fieldsHtml .= $this->element(
'genericElements/Form/fieldScaffold',
[
@ -74,4 +82,7 @@ foreach ($metaTemplate->meta_template_fields as $metaTemplateField) {
}
}
}
echo $fieldsHtml;
$fieldContainer = $this->Bootstrap->genNode('div', [
'class' => [],
], $fieldsHtml);
echo $fieldContainer;

View File

@ -45,6 +45,8 @@ $seed = 'mfb-' . mt_rand();
$clonedContainer
.removeClass('has-error')
.find('.error-message ').remove()
$clonedContainer
.find('label.form-label').text('')
const $clonedInput = $clonedContainer.find('input, select')
if ($clonedInput.length > 0) {
const injectedTemplateId = $clicked.closest('.multi-metafields-container').find('.new-metafield').length
@ -54,18 +56,21 @@ $seed = 'mfb-' . mt_rand();
}
}
function adjustClonedInputAttr($input, injectedTemplateId) {
let explodedPath = $input.attr('field').split('.').splice(0, 5)
explodedPath.push('new', injectedTemplateId)
dottedPathStr = explodedPath.join('.')
brackettedPathStr = explodedPath.map((elem, i) => {
return i == 0 ? elem : `[${elem}]`
}).join('')
$input.attr('id', dottedPathStr)
.attr('field', dottedPathStr)
.attr('name', brackettedPathStr)
.val('')
.removeClass('is-invalid')
function adjustClonedInputAttr($inputs, injectedTemplateId) {
$inputs.each(function() {
const $input = $(this)
let explodedPath = $input.attr('field').split('.').splice(0, 5)
explodedPath.push('new', injectedTemplateId)
dottedPathStr = explodedPath.join('.')
brackettedPathStr = explodedPath.map((elem, i) => {
return i == 0 ? elem : `[${elem}]`
}).join('')
$input.attr('id', dottedPathStr)
.attr('field', dottedPathStr)
.attr('name', brackettedPathStr)
.val('')
.removeClass('is-invalid')
})
}
})()
</script>

View File

@ -22,6 +22,7 @@ if (!empty($metaFieldsEntities)) {
$metaFieldsEntity->meta_template_field_id,
$metaFieldsEntity->id
),
'type' => $metaTemplateField->formType,
];
if($metaFieldsEntity->isNew()) {
$fieldData['field'] = sprintf(
@ -35,6 +36,9 @@ if (!empty($metaFieldsEntities)) {
if ($labelPrintedOnce) { // Only the first input can have a label
$fieldData['label'] = false;
}
if ($metaTemplateField->formType === 'dropdown') {
$fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions);
}
$labelPrintedOnce = true;
$fieldsHtml .= $this->element(
'genericElements/Form/fieldScaffold',
@ -49,14 +53,19 @@ if (!empty($metaTemplateField) && !empty($multiple)) { // Add multiple field but
$metaTemplateField->label = Inflector::humanize($metaTemplateField->field);
$emptyMetaFieldInput = '';
if (empty($metaFieldsEntities)) { // Include editable field for meta-template not containing a meta-field
$fieldData = [
'label' => $metaTemplateField->label,
'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new.0', $metaTemplateField->meta_template_id, $metaTemplateField->id),
'class' => 'new-metafield',
'type' => $metaTemplateField->formType,
];
if ($metaTemplateField->formType === 'dropdown') {
$fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions);
}
$emptyMetaFieldInput = $this->element(
'genericElements/Form/fieldScaffold',
[
'fieldData' => [
'label' => $metaTemplateField->label,
'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new.0', $metaTemplateField->meta_template_id, $metaTemplateField->id),
'class' => 'new-metafield',
],
'fieldData' => $fieldData,
'form' => $form,
]
);