poc form SDK (css)

This commit is contained in:
Benjamin Dalsass
2023-09-08 08:26:28 +02:00
parent 6f513a0402
commit 73328d6e99
9 changed files with 222 additions and 291 deletions

View File

@@ -1,3 +1,4 @@
/* body */
body{
font-family: 'Montserrat', serif;
padding-top: 64px;
@@ -26,26 +27,36 @@ body[data-bs-theme="dark"] .app_icon{
[data-block="row_container"] > div{
display: flex;
}
.combodo-row fieldset{
/* column */
[data-block="column_container"]{
padding: 10px;
flex-grow: 1;
width: 0;
}
/* fieldset */
[data-block="fieldset_container"]{
flex-grow: 1;
}
.combodo-column{
padding: 10px;
}
.combodo-field-set{
border: #b7b7b7 dashed 1px;
padding: 10px;
border-radius: 10px;
}
.combodo-field-set-label{
[data-block="fieldset_container"] > legend{
font-weight: bold;
border-bottom: 2px solid #b7b7b7;
margin-bottom: 10px;
}
.loading{
[data-block="fieldset_container"] > div{
border: #b7b7b7 dashed 1px;
padding: 10px;
border-radius: 10px;
}
/* ATTRIBUTES */
/* attribute */
[data-block="attribute_container"].loading{
position: relative;
}
.loading:after{
[data-block="attribute_container"].loading:after{
content: 'loading...';
position: absolute;
top: 0;
@@ -58,11 +69,21 @@ body[data-bs-theme="dark"] .app_icon{
justify-content: center;
border-radius: 10px;
}
label.required:after{
/* FORM */
/* form */
form{
border-radius: 10px;
padding: 5px;
}
/* label */
form label.required:after{
content: ' *';
color: red;
}
label.locked:after{
form label.locked:after{
content: "\f023";
font-family: "Font Awesome 5 Free", serif;
font-weight: 600;
@@ -70,40 +91,6 @@ label.locked:after{
color: #ffcc00;
font-size: .7rem;
}
label.dependent{
form label.dependent{
color: #2757af;
}
form{
/*background-color: #f8f8f8;*/
border-radius: 10px;
padding: 5px;
}
form.actions button,a{
margin: 0 3px;
}
[data-block="container"]:has(.test:empty) {
display: none;
}
.z_list_list{
display: flex;
}
body[data-bs-theme="dark"] .app_icon{
filter: invert(1);
}
.combodo-field-set-label.is_indirect_1:after{
content: 'indirect';
margin-left: 5px;
font-size: .8rem;
color: #0C63E4;
}
.combodo-field-set-label.is_abstract_1:after{
content: 'abstract';
margin-left: 5px;
font-size: .8rem;
color: #0C63E4;
}
.dropdown_scroll_300{
max-height: 300px;
overflow-y: auto;
overflow-x: clip;
}

View File

@@ -45,9 +45,20 @@ const App = function(){
});
}
/**
* ckeditor save editors.
*
*/
function saveCkEditors(){
for(let instanceName in CKEDITOR.instances) {
CKEDITOR.instances[instanceName].updateElement();
}
}
return {
init,
handleTooltips
handleTooltips,
saveCkEditors
}
};

View File

@@ -9,12 +9,17 @@
*/
const Collection = function(oForm, objectFormUrl, objectSaveUrl){
const MODAL_LOADING_HTML = 'loading...';
// dom selectors
const aSelectors = {
addItem: '.add_item_link',
createItem: '.create_item_link',
removeItem: '.btn-remove-link',
linkSetContainer: '.link_set_widget_container',
dataAttributeContainer: '[data-block="attribute_container"]',
dataObjectContainer: '[data-block="object_container"]',
dataAttCode: '[data-att-code]',
dataAttCodeSpecific: '[data-att-code="{0}"]',
};
/**
@@ -57,12 +62,12 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
*/
function addFormToCollection(e){
// retrieve link set container
const oContainer = e.currentTarget.closest('.link_set_widget_container');
// retrieve attribute container
const oAttributeContainer = e.currentTarget.closest(aSelectors.dataAttributeContainer);
// retrieve collection holder (replace ':' character otherwise the selector is invalid)
const exp = e.currentTarget.dataset.collectionHolderClass.replaceAll(/:/g, '\\:');
const collectionHolder = oContainer.querySelector('.' + exp);
const collectionHolder = oAttributeContainer.querySelector('.' + exp);
// compute template
const text = collectionHolder
@@ -85,8 +90,8 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
// store new index
collectionHolder.dataset.index++;
// remove no data row
oContainer.querySelector('.no_data').style.display = 'none';
// hide no data row
oAttributeContainer.querySelector('.no_data').style.display = 'none';
}
/**
@@ -96,32 +101,40 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
*/
function createObject(e){
let objectId = e.currentTarget.closest('form').dataset.objectId;
// retrieve attribute container
const oAttributeContainer = e.currentTarget.closest(aSelectors.dataAttributeContainer);
// set modal loading state
$('#object_modal .modal-body').html('loading...');
// retrieve attribute field
const oAttributeField = oAttributeContainer.querySelector(aSelectors.dataAttCode);
const cont = e.currentTarget.closest('.link_set_widget_container');
// retrieve attribute object container
const oObjectContainer = e.currentTarget.closest(aSelectors.dataObjectContainer);
// open modal
const myModalAlternative = new bootstrap.Modal('#object_modal', {});
myModalAlternative.show();
const oModalBody= document.querySelector('#object_modal .modal-body');
oModalBody.innerHTML = MODAL_LOADING_HTML;
const oModal = new bootstrap.Modal('#object_modal', {});
oModal.show();
// compute object form url
const url = objectFormUrl
const sUrl = objectFormUrl
.replaceAll('object_class', e.currentTarget.dataset.objectClass)
.replaceAll('form_name', 'new');
// prepare request data
const aLockedAttributes = {};
aLockedAttributes[e.currentTarget.dataset.extKeyToMe] = objectId;
if(!e.currentTarget.dataset.isIndirect) {
aLockedAttributes[e.currentTarget.dataset.extKeyToMe] = oObjectContainer.dataset.objectId;
}
const aData = {
locked_attributes: aLockedAttributes,
att_code: cont.dataset.attCode
att_code: oAttributeField.dataset.attCode
}
const sExtKeyToMe = e.currentTarget.dataset.extKeyToMe;
// fetch url
fetch(url, {
fetch(sUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
@@ -131,21 +144,81 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
})
.then((response) => response.json())
.then((data) => {
const oModalBody = $('#object_modal .modal-body');
oModalBody.html(data.template);
oModalBody[0].querySelectorAll('form').forEach((formEl) => {
oForm.handleElement(formEl);
handleElement(formEl);
oApp.handleTooltips(formEl);
});
listenSaveModalObject(myModalAlternative);
oModalBody.innerHTML = data.template;
oForm.handleElement(oModalBody);
handleElement(oModalBody);
oApp.handleTooltips(oModalBody);
listenSaveModalObject(oModal, oModalBody, oObjectContainer, oAttributeField.dataset.attCode, sExtKeyToMe, oAttributeContainer.dataset.objectClass);
})
.catch(function (error) {
console.error(error);
});
}
/**
*
*/
function listenSaveModalObject(oModal, oModalBody, oObjectContainer, sAttCode, sExtKeyToMe, sObjectClass)
{
const oSave = document.querySelector('[data-action="save_modal_object"]');
oSave.addEventListener('click', function(e){
const oForm = document.querySelector('form[name="new"]');
// set loading state
oModalBody.innerHTML = MODAL_LOADING_HTML;
// save CK editors
oApp.saveCkEditors();
// prepare data
const data = new URLSearchParams();
for (const pair of new FormData(oForm)) {
data.append(pair[0], pair[1]);
}
data.append('ext_key_to_me', sExtKeyToMe);
data.append('object_class', sObjectClass);
// compute object form url
const url = objectSaveUrl
.replaceAll('object_class', oForm.dataset.objectClass)
.replaceAll('form_name', 'new');
// fetch url
fetch(url, {
method: 'POST',
body: data,
})
.then((response) => response.json())
.then((data) => {
// on success
if(data.succeeded){
// extract form content
const reg = new RegExp(/<form .*?>(.*)<\/form>/gs);
const res = reg.exec(data.template);
// append new row
const row = oToolkit.createElementFromHtml(res[1]);
oObjectContainer.querySelector(`[data-att-code="${sAttCode}"] tbody`).appendChild(row);
// hide modal
oModal.hide();
}
else{
console.error('Error while saving object');
}
})
.catch(function (error) {
console.error(error);
});
});
}
/**
* Remove an item.
*
@@ -153,15 +226,15 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
*/
function removeItem(e)
{
// retrieve link set container
const oContainer = e.currentTarget.closest(aSelectors.linkSetContainer);
// retrieve attribute container
const oAttributeContainer = e.currentTarget.closest(aSelectors.dataAttributeContainer);
// remove row
e.currentTarget.closest('tr').remove();
// handle no data row visibility
if(oContainer.querySelectorAll('tbody tr').length === 1) {
oContainer.querySelector('.no_data').style.display = 'table-row';
if(oAttributeContainer.querySelectorAll('tbody tr').length === 1) {
oAttributeContainer.querySelector('.no_data').style.display = 'table-row';
}
}
@@ -177,73 +250,6 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
listenRemoveItem(oContainer);
}
/**
*
*/
function listenSaveModalObject(myModalAlternative)
{
const oSave = document.querySelector('[data-action="save_modal_object"]');
oSave.addEventListener('click', function(e){
const oForm = document.querySelector('form[name="new"]');
for(let instanceName in CKEDITOR.instances) {
CKEDITOR.instances[instanceName].updateElement();
}
const data = new URLSearchParams();
for (const pair of new FormData(oForm)) {
data.append(pair[0], pair[1]);
}
data.append('locked_attributes', '');
// compute object form url
const url = objectSaveUrl
.replaceAll('object_class', oForm.dataset.objectClass)
.replaceAll('form_name', 'new');
// fetch url
fetch(url, {
method: 'POST',
body: new URLSearchParams(new FormData(oForm))
})
.then((response) => response.json())
.then((data) => {
if(data.succeeded){
let form = $(data.template);
console.log(form);
//
// console.log(oForm.dataset.attCode);
//
// const fragment = oToolkit.createElementFromHtml(data.template);
// const inner = fragment.querySelector('form').innerHTML;
// const el = oToolkit.createElementFromHtml(inner);
// console.log(el);
//
myModalAlternative.hide();
console.log($(`[data-att-code="${oForm.dataset.attCode}"] tbody`));
$(`[data-att-code="${oForm.dataset.attCode}"] tbody`).append($(form.innerHTML));
}
else{
console.error('Error while saving object');
}
})
.catch(function (error) {
console.error(error);
});
});
}
return {
handleElement
}

View File

@@ -41,6 +41,9 @@ const Dynamic = function(){
// retrieve condition
const aHideWhenCondition = JSON.parse(oInvisibleField.dataset.hideWhen);
if(aHideWhenCondition === null){
return;
}
// retrieve condition data
const oHideWhenElement = oElement.querySelector(String.format(aSelectors.dataAttCode, aHideWhenCondition.att_code));
@@ -72,9 +75,18 @@ const Dynamic = function(){
// retrieve condition
const aDisableWhenCondition = JSON.parse(oDisabledField.dataset.disableWhen);
if(aDisableWhenCondition === null){
return;
}
// retrieve condition data
const oDisableWhenElement = oElement.querySelector(`[data-att-code="${aDisableWhenCondition.att_code}"]`);
if(oDisableWhenElement === null){
return;
}
// retrieve container
const oContainer = oDisabledField.closest(aSelectors.dataAllBlocks);
// initial disabled state
oDisabledField.closest(aSelectors.dataBlockContainer).disabled = (oDisableWhenElement.value === aDisableWhenCondition.value);

View File

@@ -10,6 +10,8 @@ const Form = function(oWidget, oDynamic){
const DEPENDS_ON_SEPARATOR = ' ';
const LOADING_MESSAGE = 'loading';
const aSelectors = {
dataDependsOn: '[data-depends-on]',
dataBlockContainer: '[data-block="container"]',
@@ -70,13 +72,13 @@ const Form = function(oWidget, oDynamic){
aDependentAttCodes.forEach((sAttCode) => {
// field to update
const oDependsOnElement = oElement.querySelector(String.format(aSelectors.dataAttCode, sAttCode));
const oDependsOnElement = oElement.querySelector(String.format(aSelectors.dataAttCodeSpecific, sAttCode));
// retrieve field container
const oContainer = oDependsOnElement.closest(aSelectors.dataAttributeContainer);
// set field container loading state
oContainer.classList.add('loading');
oContainer.classList.add(LOADING_MESSAGE);
// retrieve dependency data
const sDependsOn = oDependsOnElement.dataset.dependsOn;
@@ -103,7 +105,8 @@ const Form = function(oWidget, oDynamic){
// iterate throw dependencies...
aAllAttCodes.forEach(function(sAtt) {
const oDependsOnElement = oElement.querySelector(String.format(aSelectors.dataAttCode, sAtt));
const oDependsOnElement = oElement.querySelector(String.format(aSelectors.dataAttCodeSpecific, sAtt));
if(!$bFirst){
sRequestBody += '&';
}
@@ -128,13 +131,13 @@ const Form = function(oWidget, oDynamic){
aDependentAttCodes.forEach((sAtt) => {
// dependent element
const oDependentElement = oElement.querySelector(String.format(aSelectors.dataAttCode, sAtt));
const oDependentElement = oElement.querySelector(String.format(aSelectors.dataAttCodeSpecific, sAtt));
const oContainer = oDependentElement.closest(aSelectors.dataAttributeContainer);
const sId = oDependentElement.getAttribute('id');
const sName = oDependentElement.getAttribute('name');
// new element
const oNewElement = oPartial.querySelector(String.format(aSelectors.dataAttCode, sAtt));
const oNewElement = oPartial.querySelector(String.format(aSelectors.dataAttCodeSpecific, sAtt));
const oNewContainer = oNewElement.closest(aSelectors.dataAttributeContainer);
oNewElement.setAttribute('id', sId);
oNewElement.setAttribute('name', sName);
@@ -183,7 +186,11 @@ const Form = function(oWidget, oDynamic){
if(!(sEl in aMapDependencies[sId])){
aMapDependencies[sId][sEl] = [];
}
aMapDependencies[sId][sEl].push(oDependentField.dataset.attCode);
if(!aMapDependencies[sId][sEl].includes(oDependentField.dataset.attCode)){
aMapDependencies[sId][sEl].push(oDependentField.dataset.attCode);
}
});
@@ -207,7 +214,7 @@ const Form = function(oWidget, oDynamic){
for (let sAttCode in aMapContainer) {
// retrieve corresponding field
const oDependsOnElement = oObjectContainer.querySelector(String.format(aSelectors.dataAttCode, sAttCode));
const oDependsOnElement = oObjectContainer.querySelector(String.format(aSelectors.dataAttCodeSpecific, sAttCode));
// listen changes
if (oDependsOnElement !== null) {
@@ -219,66 +226,6 @@ const Form = function(oWidget, oDynamic){
}
/**
* initDynamicsInvisible.
*
* @param oElement
*/
function initDynamicsInvisible(oElement){
// get all dynamic hide fields
const aInvisibleFields = oElement.querySelectorAll(aSelectors.dataHideWhen);
// iterate throw fields...
aInvisibleFields.forEach(function (oInvisibleField) {
// retrieve condition
const aHideWhenCondition = JSON.parse(oInvisibleField.dataset.hideWhen);
// retrieve condition data
const oHideWhenElement = oElement.querySelector(String.format(aSelectors.dataAttCode, aHideWhenCondition.att_code));
// initial hidden state
oInvisibleField.closest(aSelectors.dataBlockContainer).hidden = (oHideWhenElement.value === aHideWhenCondition.value);
// listen for changes
oHideWhenElement.addEventListener('change', (e) => {
oInvisibleField.closest(aSelectors.dataBlockContainer).hidden = (e.target.value === aHideWhenCondition.value);
oInvisibleField.closest(aSelectors.dataBlockContainer).style.visibility = (e.target.value === aHideWhenCondition.value) ? 'hidden' : '';
});
});
}
/**
* initDynamicsDisable.
*
* @param oElement
*/
function initDynamicsDisable(oElement){
// get all dynamic hide fields
const aDisabledFields = oElement.querySelectorAll(aSelectors.dataDisableWhen);
// iterate throw fields...
aDisabledFields.forEach(function (oDisabledField) {
// retrieve condition
const aDisableWhenCondition = JSON.parse(oDisabledField.dataset.disableWhen);
// retrieve condition data
const oDisableWhenElement = oElement.querySelector(`[data-att-code="${aDisableWhenCondition.att_code}"]`);
// initial disabled state
oDisabledField.closest(aSelectors.dataBlockContainer).disabled = (oDisableWhenElement.value === aDisableWhenCondition.value);
// listen for changes
oDisableWhenElement.addEventListener('change', (e) => {
oDisabledField.closest(aSelectors.dataBlockContainer).disabled = (e.target.value === aDisableWhenCondition.value);
});
});
}
/**
* handleElement.
*

View File

@@ -215,6 +215,9 @@ class ObjectController extends AbstractController
// apply locked attributes to object
$oObjectFormManager->applyRequestLockedAttributesToObject($request, $oObject, 'new');
$sExtKeyToMe = $request->get('ext_key_to_me');
$sObjectClass = $request->get('object_class');
// submitted and valid
if ($oForm->isSubmitted() && $oForm->isValid()) {
@@ -233,8 +236,10 @@ class ObjectController extends AbstractController
// create object form
$oForm = $oFormFactory->createNamed($name, ObjectType::class, $oObject, [
'object_class' => $class,
'z_list' => 'list'
'object_class' => $sObjectClass,
'z_list' => 'list',
'is_link_set' => true,
'ext_key_to_me' => $sExtKeyToMe
]);
// return object form
@@ -242,7 +247,7 @@ class ObjectController extends AbstractController
'succeeded' => true,
'template' => $this->renderView('DI/form/form.html.twig', [
'id' => $id,
'class' => $class,
'class' => $sObjectClass,
'form' => $oForm->createView(),
])
]);

View File

@@ -82,7 +82,7 @@ class AttributeBuilder
'data-att-code' => $sCode,
],
'row_attr' => [
'data-block' => 'container'
'data-block' => 'attribute_container',
],
'label_attr' => [
'class' => $bIsLocked ? 'locked' : ''
@@ -159,6 +159,7 @@ class AttributeBuilder
$aFormType['options']['is_indirect'] = $oAttributeDefinition->IsIndirect();
$aFormType['options']['is_abstract'] = MetaModel::IsAbstract(LinkSetModel::GetTargetClass($oAttributeDefinition));
$aFormType['options']['target_class'] = LinkSetModel::GetTargetClass($oAttributeDefinition);
$aFormType['options']['row_attr']['data-object-class'] = $oAttributeDefinition->GetLinkedClass();
if($aFormType['options']['is_abstract']){
$aFormType['options']['object_classes'] = $this->oObjectService->listConcreteChildClasses(LinkSetModel::GetTargetClass($oAttributeDefinition));
}
@@ -168,15 +169,6 @@ class AttributeBuilder
'is_link_set' => true,
'ext_key_to_me' => $oAttributeDefinition->GetExtKeyToMe(),
'z_list' => 'list',
'attr' => [
'class' => 'z_list_list'
]
];
$aFormType['options']['attr'] = [
'class' => 'link_set'
];
$aFormType['options']['label_attr'] = [
'class' => 'combodo-field-set-label'
];
}
else if($oAttributeDefinition instanceof AttributeText){

View File

@@ -44,9 +44,6 @@ class ObjectType extends AbstractType
'z_list' => 'details',
'is_link_set' => false,
'ext_key_to_me' => null,
'attr' => [
'class' => 'z_list_details'
],
'object_class' => null,
'locked_attributes' => null,
'data_class' => cmdbAbstractObject::class,

View File

@@ -1,34 +1,5 @@
{% use "bootstrap_5_layout.html.twig" %}
{# LAYOUT #}
{# column #}
{%- block column_widget -%}
{{ form_widget(form, {'attr': {'class': 'combodo-column'}}) }}
{{ form_help(form) }}
{{ form_errors(form) }}
{%- endblock column_widget -%}
{# row #}
{%- block row_widget -%}
{{ form_widget(form, {'attr': {'class': 'combodo-row'}}) }}
{{ form_help(form) }}
{{ form_errors(form) }}
{%- endblock row_widget -%}
{# fieldset #}
{%- block field_set_widget -%}
{{ form_widget(form, {'attr': {'class': 'combodo-field-set'}}) }}
{{ form_help(form) }}
{{ form_errors(form) }}
{%- endblock field_set_widget -%}
{# ATTRIBUTE #}
{# DocumentType #}
@@ -68,19 +39,18 @@
{# LinkSetType #}
{%- block link_set_widget -%}
{% if is_indirect %}
<span class="badge bg-warning">Indirect</span>
{% endif %}
<div {{ block('widget_container_attributes') }}>
{% if is_abstract %}
<span class="badge bg-danger">abstract</span>
{% endif %}
{% if is_indirect %}
<span class="badge bg-warning">Indirect</span>
{% endif %}
{# container #}
<div class="link_set_widget_container" data-att-code="{{ att_code }}">
{% if is_abstract %}
<span class="badge bg-danger">abstract</span>
{% endif %}
{# table #}
<table class="table link_set_widget" >
<table class="table" >
{# header #}
<thead>
@@ -109,33 +79,37 @@
</table>
{% if is_abstract %}
{# create item button #}
<div class="d-inline-block">
<div class="dropdown">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Create
</button>
<ul class="dropdown-menu dropdown_scroll_300">
{% for object_class in object_classes %}
<li><a class="dropdown-item create_item_link" href="#" data-object-class="{{ object_class }}" data-ext-key-to-me="{{ ext_key_to_me }}">{{ object_class }}</a></li>
{% endfor %}
</ul>
<div>
{% if is_abstract %}
{# create item button #}
<div class="d-inline-block">
<div class="dropdown">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Create
</button>
<ul class="dropdown-menu dropdown_scroll_300">
{% for object_class in object_classes %}
<li><a class="dropdown-item create_item_link" href="#" data-object-class="{{ object_class }}" data-ext-key-to-me="{{ ext_key_to_me }}">{{ object_class }}</a></li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% elseif is_indirect %}
{# add item button #}
<button type="button" class="add_item_link btn btn-secondary btn-sm" data-collection-holder-class="{{ form.vars.id }}">Add {{ target_class }}</button>
{% else %}
{# create item button #}
<button type="button" class="btn btn-secondary create_item_link btn-sm" data-object-class="{{ target_class }}" data-ext-key-to-me="{{ ext_key_to_me }}">Create {{ target_class }}</button>
{% endif %}
{% elseif is_indirect %}
{# add item button #}
<button type="button" class="add_item_link btn btn-secondary btn-sm" data-collection-holder-class="{{ form.vars.id }}">Add {{ target_class }}</button>
{% else %}
{# create item button #}
<button type="button" class="btn btn-secondary create_item_link btn-sm" data-object-class="{{ target_class }}" data-ext-key-to-me="{{ ext_key_to_me }}">Create {{ target_class }}</button>
{% endif %}
</div>
{{ form_help(form) }}
{{ form_errors(form) }}
</div>
{{ form_help(form) }}
{{ form_errors(form) }}
{%- endblock link_set_widget -%}
{# COMPOUND #}
@@ -156,7 +130,7 @@
{% if z_list == 'list' %}
<tr data-block="object_container" data-container-id="{{ form.vars.id }}" data-object-id="{{ objectId }}" data-reload-url="{{ path('object_reload', {class: form.vars.object_class, id: objectId }) }}">
{% for child in form %}
<td data-block="container">
<td data-block="attribute_container">
{{ form_widget(child) }}
</td>
{% endfor %}