chg: [metaTemplate] Started refactoring the whole feature

Objective of the refactoring is to:
Simplified metafields searches and started to add support of multi-field and edition
pull/93/head
Sami Mokaddem 2021-11-03 11:47:10 +01:00
parent 51d93d40af
commit 9373c35bc6
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
26 changed files with 999 additions and 130 deletions

View File

@ -121,12 +121,20 @@ class CRUDComponent extends Component
->where([
'scope' => $this->Table->metaFields,
'enabled' => 1
]);
$metaQuery->contain(['MetaTemplateFields']);
])
->contain('MetaTemplateFields')
->formatResults(function (\Cake\Collection\CollectionInterface $metaTemplates) { // Set meta-template && meta-template-fields indexed by their ID
return $metaTemplates
->map(function($metaTemplate) {
$metaTemplate->meta_template_fields = Hash::combine($metaTemplate->meta_template_fields, '{n}.id', '{n}');
return $metaTemplate;
})
->indexBy('id');
});
$metaTemplates = $metaQuery->all();
}
$this->Controller->set('metaTemplates', $metaTemplates);
return true;
return $metaTemplates;
}
public function add(array $params = []): void
@ -258,13 +266,14 @@ class CRUDComponent extends Component
if (empty($id)) {
throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
}
$this->getMetaTemplates();
$metaTemplates = $this->getMetaTemplates();
if ($this->taggingSupported()) {
$params['contain'][] = 'Tags';
$this->setAllTags();
}
$data = $this->Table->get($id, isset($params['get']) ? $params['get'] : $params);
$data = $this->getMetaFields($id, $data);
$data = $this->attachMetaTemplates($data, $metaTemplates->toArray());
// $data = $this->getMetaFields($id, $data);
if (!empty($params['fields'])) {
$this->Controller->set('fields', $params['fields']);
}
@ -273,6 +282,7 @@ class CRUDComponent extends Component
'associated' => []
];
$input = $this->__massageInput($params);
dd($input);
if (!empty($params['fields'])) {
$patchEntityParams['fields'] = $params['fields'];
}
@ -356,7 +366,22 @@ class CRUDComponent extends Component
return $metaTemplates;
}
public function getMetaFields($id, $data)
// public function getMetaFields($id, $data)
// {
// if (empty($this->Table->metaFields)) {
// return $data;
// }
// $query = $this->MetaFields->find();
// $query->where(['MetaFields.scope' => $this->Table->metaFields, 'MetaFields.parent_id' => $id]);
// $metaFields = $query->all();
// $data['metaFields'] = [];
// foreach($metaFields as $metaField) {
// // $data['metaFields'][$metaField->meta_template_id][$metaField->field] = $metaField->value;
// $data['metaFields'][$metaField->meta_template_id][$metaField->meta_template_field_id] = $metaField->value;
// }
// return $data;
// }
public function getMetaFields($id)
{
if (empty($this->Table->metaFields)) {
return $data;
@ -364,13 +389,34 @@ class CRUDComponent extends Component
$query = $this->MetaFields->find();
$query->where(['MetaFields.scope' => $this->Table->metaFields, 'MetaFields.parent_id' => $id]);
$metaFields = $query->all();
$data['metaFields'] = [];
$data = [];
foreach($metaFields as $metaField) {
$data['metaFields'][$metaField->meta_template_id][$metaField->field] = $metaField->value;
if (empty($data[$metaField->meta_template_id][$metaField->meta_template_field_id])) {
$data[$metaField->meta_template_id][$metaField->meta_template_field_id] = [];
}
$data[$metaField->meta_template_id][$metaField->meta_template_field_id][$metaField->id] = $metaField;
}
return $data;
}
public function attachMetaTemplates($data, $metaTemplates)
{
$metaFields = $this->getMetaFields($data->id, $data);
foreach ($metaTemplates as $i => $metaTemplate) {
if (isset($metaFields[$metaTemplate->id])) {
foreach ($metaTemplate->meta_template_fields as $j => $meta_template_field) {
if (isset($metaFields[$metaTemplate->id][$meta_template_field->id])) {
$metaTemplates[$metaTemplate->id]->meta_template_fields[$j]['metaFields'] = $metaFields[$metaTemplate->id][$meta_template_field->id];
} else {
$metaTemplates[$metaTemplate->id]->meta_template_fields[$j]['metaFields'] = [];
}
}
}
}
$data['MetaTemplates'] = $metaTemplates;
return $data;
}
public function view(int $id, array $params = []): void
{
if (empty($id)) {
@ -767,7 +813,7 @@ class CRUDComponent extends Component
$modelAlias = $this->Table->getAlias();
$subQuery = $this->Table->find('tagged', [
'name' => $tags,
'forceAnd' => true
'OperatorAND' => true
])->select($modelAlias . '.id');
return $query->where([$modelAlias . '.id IN' => $subQuery]);
}

View File

@ -0,0 +1,224 @@
<?php
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\Utility\Inflector;
use Cake\Database\Expression\QueryExpression;
use function PHPSTORM_META\type;
class MetaFieldsBehavior extends Behavior
{
protected $_defaultConfig = [
'metaFieldsAssoc' => [
'className' => 'MetaFields',
'foreignKey' => 'parent_id',
'bindingKey' => 'id',
'dependent' => true,
'cascadeCallbacks' => true,
'saveStrategy' => 'append',
'propertyName' => 'meta_fields',
],
'modelAssoc' => [
'foreignKey' => 'parent_id',
'bindingKey' => 'id',
],
'metaTemplateFieldCounter' => ['counter'],
'implementedEvents' => [
'Model.beforeMarshal' => 'beforeMarshal',
'Model.beforeFind' => 'beforeFind',
'Model.beforeSave' => 'beforeSave',
],
'implementedMethods' => [
'normalizeMetafields' => 'normalizeMetafields',
],
'implementedFinders' => [
'metafieldValue' => 'findMetafieldValue',
],
];
public function initialize(array $config): void
{
$this->bindAssociations();
$this->attachCounters();
$this->_metaTemplateFieldTable = $this->_table;
$this->_metaTemplateTable = $this->_table;
}
public function bindAssociations()
{
$config = $this->getConfig();
$metaFieldsAssoc = $config['metaFieldsAssoc'];
$modelAssoc = $config['modelAssoc'];
$table = $this->_table;
$tableAlias = $this->_table->getAlias();
$assocConditions = [
'MetaFields.scope' => Inflector::underscore(Inflector::singularize($tableAlias))
];
if (!$table->hasAssociation('MetaFields')) {
$table->hasMany('MetaFields', array_merge(
$metaFieldsAssoc,
[
'conditions' => $assocConditions
]
));
}
if (!$table->MetaFields->hasAssociation($tableAlias)) {
$table->MetaFields->belongsTo($tableAlias, array_merge(
$modelAssoc,
[
'className' => get_class($table),
]
));
}
}
public function attachCounters()
{
$config = $this->getConfig();
$metaFieldsTable = $this->_table->MetaFields;
$tableAlias = $this->_table->getAlias();
if (!$metaFieldsTable->hasBehavior('CounterCache')) {
$metaFieldsTable->addBehavior('CounterCache', [
$tableAlias => $config['metaTemplateFieldCounter']
]);
}
}
public function beforeMarshal($event, $data, $options)
{
$property = $this->getConfig('metaFieldsAssoc.propertyName');
$options['accessibleFields'][$property] = true;
$options['associated']['MetaFields']['accessibleFields']['id'] = true;
if (isset($data[$property])) {
if (!empty($data[$property])) {
$data[$property] = $this->normalizeMetafields($data[$property]);
}
}
}
public function beforeSave($event, $entity, $options)
{
if (empty($entity->metaFields)) {
return;
}
}
public function normalizeMetafields($metaFields)
{
return $metaFields;
}
/**
* Usage:
* $this->{$model}->find('metaFieldValue', [
* ['meta_template_id' => 1, 'field' => 'email', 'value' => '%@domain.test'],
* ['meta_template_id' => 1, 'field' => 'country_code', 'value' => '!LU'],
* ['meta_template_id' => 1, 'field' => 'time_zone', 'value' => 'UTC+2'],
* ])
* $this->{$model}->find('metaFieldValue', [
* 'AND' => [
* ['meta_template_id' => 1, 'field' => 'email', 'value' => '%@domain.test'],
* 'OR' => [
* ['meta_template_id' => 1, 'field' => 'time_zone', 'value' => 'UTC+1'],
* ['meta_template_id' => 1, 'field' => 'time_zone', 'value' => 'UTC+2'],
* ],
* ],
* ])
*/
public function findMetafieldValue(Query $query, array $filters)
{
if (empty($filters)) {
return $query;
}
$conjugatedFilters = $this->buildConjugatedFilters($filters);
$conditions = $this->buildConjugatedQuerySnippet($conjugatedFilters);
$query->where($conditions);
return $query;
}
protected function buildConjugatedFilters(array $filters): array
{
$conjugatedFilters = [];
foreach ($filters as $operator => $subFilters) {
if (is_numeric($operator)) {
$conjugatedFilters[] = $subFilters;
} else {
if (!empty($subFilters)) {
$conjugatedFilters[$operator] = $this->buildConjugatedFilters($subFilters);
}
}
}
return $conjugatedFilters;
}
protected function buildConjugatedQuerySnippet(array $conjugatedFilters, string $parentOperator='AND'): array
{
$conditions = [];
if (empty($conjugatedFilters['AND']) && empty($conjugatedFilters['OR'])) {
if (count(array_filter(array_keys($conjugatedFilters), 'is_string')) > 0) {
$conditions = $this->buildComposedQuerySnippet([$conjugatedFilters]);
} else {
$conditions = $this->buildComposedQuerySnippet($conjugatedFilters, $parentOperator);
}
} else {
foreach ($conjugatedFilters as $subOperator => $subFilter) {
$conditions[$subOperator] = $this->buildConjugatedQuerySnippet($subFilter, $subOperator);
}
}
return $conditions;
}
public function buildComposedQuerySnippet(array $filters, string $operator='AND'): array
{
$conditions = [];
foreach ($filters as $filterOperator => $filter) {
$subQuery = $this->buildQuerySnippet($filter, true);
$modelAlias = $this->_table->getAlias();
$conditions[$operator][] = [$modelAlias . '.id IN' => $subQuery];
}
return $conditions;
}
protected function getQueryExpressionForField(QueryExpression $exp, string $field, string $value)
{
if (substr($value, 0, 1) == '!') {
$value = substr($value, 1);
$exp->notEq($field, $value);
} else if (strpos($value, '%') != false) {
$exp->like($field, $value);
} else {
$exp->eq($field, $value);
}
return $exp;
}
protected function buildQuerySnippet(array $filter)
{
$whereClosure = function (QueryExpression $exp) use ($filter) {
foreach ($filter as $column => $value) {
$keyedColumn = 'MetaFields.' . $column;
$this->getQueryExpressionForField($exp, $keyedColumn, $value);
}
return $exp;
};
$foreignKey = $this->getConfig('modelAssoc.foreignKey');
$query = $this->_table->MetaFields->find()
->select('MetaFields.' . $foreignKey)
->where($whereClosure);
return $query;
}
}

View File

@ -17,7 +17,8 @@ class Individual extends AppModel
'uuid' => true,
];
protected $_virtual = ['full_name'];
// protected $_virtual = ['full_name', 'meta_fields', 'alternate_emails'];
protected $_virtual = ['full_name', 'alternate_emails'];
protected function _getFullName()
{
@ -26,4 +27,38 @@ class Individual extends AppModel
}
return sprintf("%s %s", $this->first_name, $this->last_name);
}
// protected function _getMetaFields()
// {
// if (!empty($this->metaTemplates)) {
// $metaFields = [];
// foreach ($this->metaTemplates as $metaTemplate) {
// if (!empty($metaTemplate['meta_template_fields'])) {
// foreach ($metaTemplate['meta_template_fields'] as $templateMetaFields) {
// foreach ($templateMetaFields['meta_fields'] as $metaField) {
// $tmpMetaTemplate = $metaTemplate->toArray();
// unset($tmpMetaTemplate['meta_template_fields']);
// $metaField['metaTemplate'] = $tmpMetaTemplate;
// $metaFields[] = $metaField;
// }
// }
// }
// }
// return $metaFields;
// }
// return null;
// }
protected function _getAlternateEmails()
{
$emails = [];
if (!empty($this->meta_fields)) {
foreach ($this->meta_fields as $metaField) {
if (str_contains($metaField->field, 'email')) {
$emails[] = $metaField;
}
}
}
return $emails;
}
}

View File

@ -16,6 +16,8 @@ class IndividualsTable extends AppTable
$this->addBehavior('UUID');
$this->addBehavior('Timestamp');
$this->addBehavior('Tags.Tag');
$this->addBehavior('MetaFields');
$this->hasMany(
'Alignments',
[
@ -36,16 +38,8 @@ class IndividualsTable extends AppTable
$this->belongsToMany('Organisations', [
'through' => 'Alignments',
]);
$this->hasMany('MetaFields')
->setForeignKey('parent_id')
->setBindingKey('id')
->setConditions([
'MetaFields.scope' => 'individual'
])
->setDependent(true);
$this->belongsToMany('MailingLists');
$this->setDisplayField('email');
}

View File

@ -15,12 +15,12 @@ class MetaFieldsTable extends AppTable
$this->setDisplayField('field');
$this->belongsTo('MetaTemplates');
$this->belongsTo('MetaTemplateFields');
$this->belongsTo('Individuals')
->setForeignKey('parent_id')
->setBindingKey('id')
->setConditions([
'scope' => 'individual'
]);
// $this->belongsTo('Individuals')
// ->setForeignKey('parent_id')
// ->setBindingKey('id')
// ->setConditions([
// 'scope' => 'individual'
// ]);
}
public function validationDefault(Validator $validator): Validator

View File

@ -313,7 +313,7 @@ class BootstrapTabs extends BootstrapGeneric
$this->bsClasses['nav'][] = 'nav-justify';
}
$activeTab = 0;
$activeTab = array_key_first($this->data['navs']);
foreach ($this->data['navs'] as $i => $nav) {
if (!is_array($nav)) {
$this->data['navs'][$i] = ['text' => $nav];
@ -862,7 +862,7 @@ class BoostrapButton extends BootstrapGeneric {
function __construct($options) {
$this->allowedOptionValues = [
'variant' => array_merge(BootstrapGeneric::$variants, ['link', 'text']),
'size' => ['', 'sm', 'lg'],
'size' => ['', 'xs', 'sm', 'lg'],
'type' => ['button', 'submit', 'reset']
];
if (empty($options['class'])) {
@ -877,6 +877,10 @@ class BoostrapButton extends BootstrapGeneric {
$this->options = array_merge($this->defaultOptions, $options);
$this->checkOptionValidity();
if (!empty($this->options['id'])) {
$this->options['params']['id'] = $this->options['id'];
}
$this->bsClasses[] = 'btn';
if ($this->options['outline']) {
$this->bsClasses[] = "btn-outline-{$this->options['variant']}";

View File

@ -129,6 +129,11 @@ echo $this->element('genericElements/IndexTable/index_table', [
'sort' => 'namespace',
'data_path' => 'namespace',
],
[
'name' => __('Version'),
'sort' => 'version',
'data_path' => 'version',
],
[
'name' => __('UUID'),
'sort' => 'uuid',

View File

@ -8,7 +8,9 @@
$fieldTemplate = $fieldData['type'] . 'Field';
}
if (empty($fieldData['label'])) {
$fieldData['label'] = \Cake\Utility\Inflector::humanize($fieldData['field']);
if (!isset($fieldData['label']) || $fieldData['label'] !== false) {
$fieldData['label'] = \Cake\Utility\Inflector::humanize($fieldData['field']);
}
}
if (!empty($fieldDesc[$fieldData['field']])) {
$fieldData['label'] .= $this->element(

View File

@ -0,0 +1,38 @@
<div class="col-12 mb-3">
<h2 class="fw-light">
<?= empty($data['title']) ? sprintf('%s %s', $actionName, $modelName) : h($data['title']) ?>
</h2>
<?= $formCreate ?>
<?= $ajaxFlashMessage ?>
<?php if (!empty($data['description'])) : ?>
<div class="pb-3 fw-light">
<?= $data['description'] ?>
</div>
<?php endif; ?>
<div class="panel col-8">
<?= $fieldsString ?>
</div>
<?php if (!empty($metaTemplateString)) : ?>
<div class="col-10">
<?=
$this->Bootstrap->accordion(
[
'class' => 'mb-3'
],
[
[
'_open' => true,
'header' => [
'title' => __('Meta fields')
],
'body' => $metaTemplateString,
],
]
);
?>
</div>
<?php endif; ?>
<?= $this->element('genericElements/Form/submitButton', $submitButtonData); ?>
<?= $formEnd; ?>
</div>

View File

@ -0,0 +1,28 @@
<?php if (!empty($data['description'])) : ?>
<div class="pb-3 fw-light">
<?= $data['description'] ?>
</div>
<?php endif; ?>
<?= $ajaxFlashMessage ?>
<?= $formCreate ?>
<?= $fieldsString ?>
<?php if (!empty($metaTemplateString)) : ?>
<?=
$this->Bootstrap->accordion(
[
'class' => 'mb-3'
],
[
[
'_open' => true,
'header' => [
'title' => __('Meta fields')
],
'body' => $metaTemplateString,
],
]
);
?>
<?php endif; ?>
<?= $formEnd; ?>

View File

@ -107,91 +107,35 @@
$seedModal = 'mseed-' . mt_rand();
echo $this->element('genericElements/genericModal', [
'title' => empty($data['title']) ? sprintf('%s %s', $actionName, $modelName) : h($data['title']),
'body' => sprintf(
'%s%s%s%s%s%s',
empty($data['description']) ? '' : sprintf(
'<div class="pb-2 fw-light">%s</div>',
$data['description']
),
$ajaxFlashMessage,
$formCreate,
$fieldsString,
empty($metaTemplateString) ? '' : $this->Bootstrap->accordion(
[
'class' => 'mb-2'
],
[
[
'_open' => true,
'header' => [
'title' => __('Meta fields')
],
'body' => $metaTemplateString,
],
]
),
$formEnd
),
'body' => $this->element('genericElements/Form/formLayouts/formRaw', [
'formCreate' => $formCreate,
'ajaxFlashMessage' => $ajaxFlashMessage,
'fieldsString' => $fieldsString,
'formEnd' => $formEnd,
'metaTemplateString' => $metaTemplateString,
]),
'actionButton' => $this->element('genericElements/Form/submitButton', $submitButtonData),
'class' => "modal-lg {$seedModal}"
]);
} else if (!empty($raw)) {
echo sprintf(
'%s%s%s%s%s%s',
empty($data['description']) ? '' : sprintf(
'<div class="pb-2">%s</div>',
$data['description']
),
$ajaxFlashMessage,
$formCreate,
$fieldsString,
empty($metaTemplateString) ? '' : $this->Bootstrap->accordion(
[
'class' => 'mb-2'
],
[
[
'_open' => true,
'header' => [
'title' => __('Meta fields')
],
'body' => $metaTemplateString,
],
]
),
$formEnd
);
echo $this->element('genericElements/Form/formLayouts/formDefault', [
'formCreate' => $formCreate,
'ajaxFlashMessage' => $ajaxFlashMessage,
'fieldsString' => $fieldsString,
'formEnd' => $formEnd,
'metaTemplateString' => $metaTemplateString,
]);
} else {
echo sprintf(
'%s<h2 class="fw-light">%s</h2>%s%s%s%s%s%s%s%s%s',
empty($ajax) ? '<div class="col-8">' : '',
empty($data['title']) ? sprintf('%s %s', $actionName, $modelName) : h($data['title']),
$formCreate,
$ajaxFlashMessage,
empty($data['description']) ? '' : sprintf(
'<div class="pb-3 fw-light">%s</div>',
$data['description']
),
sprintf('<div class="panel">%s</div>', $fieldsString),
empty($metaTemplateString) ? '' : $this->Bootstrap->accordion(
[
'class' => 'mb-2'
],
[
[
'_open' => true,
'header' => [
'title' => __('Meta fields')
],
'body' => $metaTemplateString,
],
]
),
$this->element('genericElements/Form/submitButton', $submitButtonData),
$formEnd,
'<br /><br />',
empty($ajax) ? '</div>' : ''
);
echo $this->element('genericElements/Form/formLayouts/formDefault', [
'actionName' => $actionName,
'modelName' => $modelName,
'submitButtonData' => $submitButtonData,
'formCreate' => $formCreate,
'ajaxFlashMessage' => $ajaxFlashMessage,
'fieldsString' => $fieldsString,
'formEnd' => $formEnd,
'metaTemplateString' => $metaTemplateString,
]);
}
?>
<script type="text/javascript">

View File

@ -1,8 +1,16 @@
<?php
use Cake\Utility\Inflector;
$default_template = [
'inputContainer' => '<div class="row mb-3 metafield-container">{{content}}</div>',
'inputContainerError' => '<div class="row mb-3 metafield-container has-error">{{content}}</div>',
'formGroup' => '<div class="col-sm-2 form-label" {{attrs}}>{{label}}</div><div class="col-sm-10">{{input}}{{error}}</div>',
];
$this->Form->setTemplates($default_template);
$backupTemplates = $this->Form->getTemplates();
$tabData = [];
foreach($metaTemplatesData as $i => $metaTemplate) {
foreach ($metaTemplatesData as $i => $metaTemplate) {
if ($metaTemplate->is_default) {
$tabData['navs'][$i] = [
'html' => $this->element('/genericElements/MetaTemplates/metaTemplateNav', ['metaTemplate' => $metaTemplate])
@ -13,20 +21,53 @@ foreach($metaTemplatesData as $i => $metaTemplate) {
];
}
$fieldsHtml = '';
foreach ($metaTemplate->meta_template_fields as $metaField) {
$metaField->label = Inflector::humanize($metaField->field);
$metaField->field = sprintf('%s.%s.%s', 'metaFields', $metaField->meta_template_id, $metaField->field);
$fieldsHtml .= $this->element(
'genericElements/Form/fieldScaffold', [
'fieldData' => $metaField->toArray(),
'form' => $this->Form
]
);
foreach ($metaTemplate->meta_template_fields as $metaTemplateField) {
$metaTemplateField->label = Inflector::humanize($metaTemplateField->field);
if (!empty($metaTemplateField->metaFields)) {
if (!empty($metaTemplateField->multiple)) {
$fieldsHtml .= $this->element(
'genericElements/Form/multiFieldScaffold',
[
'metaFieldsEntities' => $metaTemplateField->metaFields,
'metaTemplateField' => $metaTemplateField,
'multiple' => !empty($metaTemplateField->multiple),
'form' => $this->Form,
]
);
} else {
$metaField = reset($metaTemplateField->metaFields);
$fieldData = [
'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.%s.value', $metaField->meta_template_id, $metaField->meta_template_field_id, $metaField->id),
'label' => $metaTemplateField->field,
];
$fieldsHtml .= $this->element(
'genericElements/Form/fieldScaffold',
[
'fieldData' => $fieldData,
'metaTemplateField' => $metaTemplateField,
'form' => $this->Form
]
);
}
} else {
$this->Form->setTemplates($backupTemplates);
$fieldData = [
'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new.0', $metaTemplateField->meta_template_id, $metaTemplateField->id),
'label' => $metaTemplateField->field,
];
$fieldsHtml .= $this->element(
'genericElements/Form/fieldScaffold',
[
'fieldData' => $fieldData,
'form' => $this->Form
]
);
}
}
$tabData['content'][$i] = $fieldsHtml;
}
echo $this->Bootstrap->Tabs([
'pills' => true,
'data' => $tabData,
'nav-class' => ['pb-1']
]);
'nav-class' => ['mb-3']
]);

View File

@ -0,0 +1,68 @@
<?php
$seed = 'mfb-' . mt_rand();
?>
<div class="d-flex align-items-center">
<?php
// $content = sprintf('<a class="btn btn-primary btn-xs">%s</a><a class="btn btn-link btn-xs">%s</a>', $this->Bootstrap->icon('plus'), __('Add another {0}', h($fieldData['label'])));
$content = sprintf('<a class="btn btn-primary btn-xs">%s</a><a class="btn btn-link btn-xs">%s</a>', $this->Bootstrap->icon('plus'), __('Add another {0}', h($metaTemplateFieldName)));
$content = sprintf(
'%s%s',
$this->Bootstrap->button([
'nodeType' => 'a',
'icon' => 'plus',
'variant' => 'secondary',
'size' => 'xs',
]),
$this->Bootstrap->button([
'nodeType' => 'a',
// 'text' => __('Add another {0}', h($fieldData['label'])),
'text' => __('Add another {0}', h($metaTemplateFieldName)),
'variant' => 'link',
'class' => ['link-secondary'],
'size' => 'xs',
])
);
?>
<?=
$this->Bootstrap->button([
'id' => $seed,
'html' => $content,
'variant' => 'link',
'size' => 'xs',
]);
?>
</div>
<script>
(function() {
$('#<?= $seed ?>').click(addNewField)
function addNewField() {
const $clicked = $(this);
const $lastInputContainer = $clicked.closest('.multi-metafields-container').children().not('.template-container').find('input').last().closest('.multi-metafield-container')
const $templateContainer = $clicked.closest('.multi-metafields-container').find('.template-container').children()
const $clonedContainer = $templateContainer.clone()
$clonedContainer.removeClass('d-none', 'template-container')
const $clonedInput = $clonedContainer.find('input, select')
if ($clonedInput.length > 0) {
const injectedTemplateId = $clicked.closest('.multi-metafields-container').find('.new-metafield').length
$clonedInput.addClass('new-metafield')
adjustClonedInputAttr($clonedInput, injectedTemplateId)
$clonedContainer.insertAfter($lastInputContainer)
}
}
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('')
}
})()
</script>

View File

@ -0,0 +1,15 @@
<?php
if (!empty($metaTemplateField)) {
$fieldData = [
'label' => false,
'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.{count}', $metaTemplateField['meta_template_id'], $metaTemplateField['id']),
'class' => 'metafield-template',
];
echo $this->element(
'genericElements/Form/fieldScaffold',
[
'fieldData' => $fieldData,
'form' => $form
]
);
}

View File

@ -0,0 +1,55 @@
<?php
$default_template = [
'inputContainer' => '<div class="row pb-1 multi-metafield-container">{{content}}</div>',
'inputContainerError' => '<div class="row pb-1 metafield-container has-error">{{content}}</div>',
'formGroup' => '<div class="col-sm-2 form-label" {{attrs}}>{{label}}</div><div class="col-sm-10">{{input}}{{error}}</div>',
];
$form->setTemplates($default_template);
$fieldsHtml = '';
$labelPrintedOnce = false;
foreach ($metaFieldsEntities as $i => $metaFieldsEntity) {
$fieldData = [
'label' => $metaFieldsEntity->field,
'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.%s.value', $metaFieldsEntity->meta_template_id, $metaFieldsEntity->meta_template_field_id, $metaFieldsEntity->id),
];
if ($labelPrintedOnce) { // Only the first input can have a label
$fieldData['label'] = false;
}
$labelPrintedOnce = true;
$fieldsHtml .= $this->element(
'genericElements/Form/fieldScaffold',
[
'fieldData' => $fieldData,
'form' => $form
]
);
}
if (!empty($metaTemplateField) && !empty($multiple)) { // Add multiple field button
$emptyInputForSecurityComponent = $this->element(
'genericElements/Form/fieldScaffold',
[
'fieldData' => [
'label' => false,
'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new[]', $metaFieldsEntity->meta_template_id, $metaFieldsEntity->meta_template_field_id),
],
'form' => $form,
]
);
$multiFieldButtonHtml = sprintf(
'<div class="row pb-1 multi-metafield-container"><div class="col-sm-2 form-label"></div><div class="col-sm-10">%s</div></div>',
$this->element(
'genericElements/Form/multiFieldButton',
[
'metaTemplateFieldName' => $metaTemplateField->field,
]
)
);
$fieldsHtml .= sprintf('<div class="d-none template-container">%s</div>', $emptyInputForSecurityComponent);
$fieldsHtml .= $multiFieldButtonHtml;
}
?>
<div class="row mb-3 multi-metafields-container">
<?= $fieldsHtml; ?>
</div>

View File

@ -168,11 +168,3 @@ input[type="checkbox"]:disabled.change-cursor {
-webkit-mask-repeat: no-repeat;
-webkit-mask-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' height='223.995' width='383.98752' viewBox='0 0 383.98752 223.995' role='img'%3E%3Cpath d='m 367.9975,0 h -118.06 c -21.38,0 -32.09,25.85 -16.97,40.97 l 32.4,32.4 -73.37,73.38 -73.37,-73.37 c -12.5,-12.5 -32.76,-12.5 -45.25,0 l -68.69,68.69 c -6.25,6.25 -6.25,16.38 0,22.63 l 22.62,22.62 c 6.25,6.25 16.38,6.25 22.63,0 l 46.06,-46.07 73.37,73.37 c 12.5,12.5 32.76,12.5 45.25,0 l 96,-96 32.4,32.4 c 15.12,15.12 40.97,4.41 40.97,-16.97 V 16 c 0.01,-8.84 -7.15,-16 -15.99,-16 z' /%3E%3C/svg%3E%0A");
}
.fs-7 {
font-size: .875rem !important;
}
.fs-8 {
font-size: .7rem !important;
}

View File

@ -282,3 +282,39 @@
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%); }
.fs-7 {
font-size: .875rem !important; }
.fs-8 {
font-size: .7rem !important; }
.btn-xs, .btn-group-xs > .btn {
padding: 0.1rem;
font-size: 0.7rem;
border-radius: 0.15rem;
line-height: 1.15; }
.btn-check:focus + .btn.btn-xs, .btn.btn-xs:focus {
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); }
:not(.meta-template-container) > .multi-metafield-container {
position: relative; }
:not(.meta-template-container) > .multi-metafield-container > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
border-style: solid;
border-color: #6c757d;
position: absolute;
content: ' ';
width: 0.5rem;
height: 100%;
border-width: 0px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:first-child > .col-sm-10::before {
transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
border-top-right-radius: 3px;
border-width: 2px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:last-child > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5));
border-bottom-left-radius: 3px;
height: 50%;
border-width: 0px 0px 2px 2px; }

View File

@ -283,6 +283,42 @@
left: 50%;
transform: translateX(-50%) translateY(-50%); }
.fs-7 {
font-size: .875rem !important; }
.fs-8 {
font-size: .7rem !important; }
.btn-xs, .btn-group-xs > .btn {
padding: 0.1rem;
font-size: 0.7rem;
border-radius: 0.15rem;
line-height: 1.15; }
.btn-check:focus + .btn.btn-xs, .btn.btn-xs:focus {
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); }
:not(.meta-template-container) > .multi-metafield-container {
position: relative; }
:not(.meta-template-container) > .multi-metafield-container > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
border-style: solid;
border-color: #444;
position: absolute;
content: ' ';
width: 0.5rem;
height: 100%;
border-width: 0px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:first-child > .col-sm-10::before {
transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
border-top-right-radius: 3px;
border-width: 2px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:last-child > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5));
border-bottom-left-radius: 3px;
height: 50%;
border-width: 0px 0px 2px 2px; }
/* Body */
body {
background-color: var(--bs-body-bg);

View File

@ -283,6 +283,42 @@
left: 50%;
transform: translateX(-50%) translateY(-50%); }
.fs-7 {
font-size: .875rem !important; }
.fs-8 {
font-size: .7rem !important; }
.btn-xs, .btn-group-xs > .btn {
padding: 0.1rem;
font-size: 0.7rem;
border-radius: 0.15rem;
line-height: 1.15; }
.btn-check:focus + .btn.btn-xs, .btn.btn-xs:focus {
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); }
:not(.meta-template-container) > .multi-metafield-container {
position: relative; }
:not(.meta-template-container) > .multi-metafield-container > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
border-style: solid;
border-color: #6c757d;
position: absolute;
content: ' ';
width: 0.5rem;
height: 100%;
border-width: 0px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:first-child > .col-sm-10::before {
transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
border-top-right-radius: 3px;
border-width: 2px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:last-child > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5));
border-bottom-left-radius: 3px;
height: 50%;
border-width: 0px 0px 2px 2px; }
/* Body */
body {
background-color: var(--bs-body-bg);

View File

@ -283,6 +283,42 @@
left: 50%;
transform: translateX(-50%) translateY(-50%); }
.fs-7 {
font-size: .875rem !important; }
.fs-8 {
font-size: .7rem !important; }
.btn-xs, .btn-group-xs > .btn {
padding: 0.1rem;
font-size: 0.7rem;
border-radius: 0.15rem;
line-height: 1.15; }
.btn-check:focus + .btn.btn-xs, .btn.btn-xs:focus {
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); }
:not(.meta-template-container) > .multi-metafield-container {
position: relative; }
:not(.meta-template-container) > .multi-metafield-container > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
border-style: solid;
border-color: #95a5a6;
position: absolute;
content: ' ';
width: 0.5rem;
height: 100%;
border-width: 0px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:first-child > .col-sm-10::before {
transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
border-top-right-radius: 3px;
border-width: 2px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:last-child > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5));
border-bottom-left-radius: 3px;
height: 50%;
border-width: 0px 0px 2px 2px; }
/* Body */
body {
background-color: var(--bs-body-bg);

View File

@ -283,6 +283,42 @@
left: 50%;
transform: translateX(-50%) translateY(-50%); }
.fs-7 {
font-size: .875rem !important; }
.fs-8 {
font-size: .7rem !important; }
.btn-xs, .btn-group-xs > .btn {
padding: 0.1rem;
font-size: 0.7rem;
border-radius: 0.15rem;
line-height: 1.15; }
.btn-check:focus + .btn.btn-xs, .btn.btn-xs:focus {
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); }
:not(.meta-template-container) > .multi-metafield-container {
position: relative; }
:not(.meta-template-container) > .multi-metafield-container > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
border-style: solid;
border-color: #95a5a6;
position: absolute;
content: ' ';
width: 0.5rem;
height: 100%;
border-width: 0px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:first-child > .col-sm-10::before {
transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
border-top-right-radius: 3px;
border-width: 2px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:last-child > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5));
border-bottom-left-radius: 3px;
height: 50%;
border-width: 0px 0px 2px 2px; }
/* Body */
body {
background-color: var(--bs-body-bg);

View File

@ -283,6 +283,42 @@
left: 50%;
transform: translateX(-50%) translateY(-50%); }
.fs-7 {
font-size: .875rem !important; }
.fs-8 {
font-size: .7rem !important; }
.btn-xs, .btn-group-xs > .btn {
padding: 0.1rem;
font-size: 0.7rem;
border-radius: 0.15rem;
line-height: 1.15; }
.btn-check:focus + .btn.btn-xs, .btn.btn-xs:focus {
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); }
:not(.meta-template-container) > .multi-metafield-container {
position: relative; }
:not(.meta-template-container) > .multi-metafield-container > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
border-style: solid;
border-color: rgba(255, 255, 255, 0.4);
position: absolute;
content: ' ';
width: 0.5rem;
height: 100%;
border-width: 0px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:first-child > .col-sm-10::before {
transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
border-top-right-radius: 3px;
border-width: 2px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:last-child > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5));
border-bottom-left-radius: 3px;
height: 50%;
border-width: 0px 0px 2px 2px; }
/* Body */
.panel {
background-color: transparent;

View File

@ -279,6 +279,42 @@
left: 50%;
transform: translateX(-50%) translateY(-50%); }
.fs-7 {
font-size: .875rem !important; }
.fs-8 {
font-size: .7rem !important; }
.btn-xs, .btn-group-xs > .btn {
padding: 0.1rem;
font-size: 0.7rem;
border-radius: 0.15rem;
line-height: 1.15; }
.btn-check:focus + .btn.btn-xs, .btn.btn-xs:focus {
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); }
:not(.meta-template-container) > .multi-metafield-container {
position: relative; }
:not(.meta-template-container) > .multi-metafield-container > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
border-style: solid;
border-color: #7a8288;
position: absolute;
content: ' ';
width: 0.5rem;
height: 100%;
border-width: 0px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:first-child > .col-sm-10::before {
transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
border-top-right-radius: 3px;
border-width: 2px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:last-child > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5));
border-bottom-left-radius: 3px;
height: 50%;
border-width: 0px 0px 2px 2px; }
/* Body */
body {
background-color: var(--bs-body-bg);

View File

@ -283,6 +283,42 @@
left: 50%;
transform: translateX(-50%) translateY(-50%); }
.fs-7 {
font-size: .875rem !important; }
.fs-8 {
font-size: .7rem !important; }
.btn-xs, .btn-group-xs > .btn {
padding: 0.1rem;
font-size: 0.7rem;
border-radius: 0.15rem;
line-height: 1.15; }
.btn-check:focus + .btn.btn-xs, .btn.btn-xs:focus {
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); }
:not(.meta-template-container) > .multi-metafield-container {
position: relative; }
:not(.meta-template-container) > .multi-metafield-container > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
border-style: solid;
border-color: #ea39b8;
position: absolute;
content: ' ';
width: 0.5rem;
height: 100%;
border-width: 0px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:first-child > .col-sm-10::before {
transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
border-top-right-radius: 3px;
border-width: 2px 2px 0px 0px; }
:not(.meta-template-container) > .multi-metafield-container:last-child > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5));
border-bottom-left-radius: 3px;
height: 50%;
border-width: 0px 0px 2px 2px; }
/* Body */
.panel {
background-color: #363636;

View File

@ -1 +1,71 @@
@import "../bootstrap/scss/bootstrap";
$secondary: #6c757d !default;
// .multi-metafield-container {
// position: relative;
// }
// .multi-metafield-container .col-sm-10::before {
// transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
// border-style: solid;
// border-color: $secondary;
// }
// .multi-metafield-container:first-child .col-sm-10::before {
// transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
// border-top-right-radius: 3px;
// position: absolute;
// content: ' ';
// width: 0.5rem;
// border-width: 2px 2px 0px 0px;
// }
// .multi-metafield-container .col-sm-10::before {
// position: absolute;
// content: ' ';
// width: 0.5rem;
// height: 100%;
// border-width: 0px 2px 0px 0px;
// }
// .multi-metafield-container:last-child .col-sm-10::before {
// transform: translateX(calc(1.5rem * -.5));
// border-bottom-left-radius: 3px;
// position: absolute;
// content: ' ';
// width: 0.5rem;
// height: 50%;
// border-width: 0px 0px 2px 2px;
// }
// :not(.meta-template-container) .multi-metafield-container {
// .multi-metafield-container :not(.meta-template-container) {
:not(.meta-template-container) > .multi-metafield-container {
position: relative;
& > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5 - 0.25rem - 2px));
border-style: solid;
border-color: $secondary;
position: absolute;
content: ' ';
width: 0.5rem;
height: 100%;
border-width: 0px 2px 0px 0px;
}
&:first-child > .col-sm-10::before {
transform: translate(calc(1.5rem * -.5 - 0.25rem - 2px), 12px);
border-top-right-radius: 3px;
border-width: 2px 2px 0px 0px;
}
&:last-child > .col-sm-10::before {
transform: translateX(calc(1.5rem * -.5));
border-bottom-left-radius: 3px;
height: 50%;
border-width: 0px 0px 2px 2px;
}
}

View File

@ -164,4 +164,24 @@ $toast-color-level: 70% !default;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
}
.fs-7 {
font-size: .875rem !important;
}
.fs-8 {
font-size: .7rem !important;
}
.btn-xs, .btn-group-xs > .btn {
padding: 0.1rem;
font-size: 0.7rem;
border-radius: 0.15rem;
line-height: 1.15;
}
.btn-check:focus + .btn.btn-xs, .btn.btn-xs:focus {
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
@import '../../../scss/custom.scss';