diff --git a/css/DI/app.css b/css/DI/app.css index fc1a499cd..2494e7d50 100644 --- a/css/DI/app.css +++ b/css/DI/app.css @@ -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; -} diff --git a/js/DI/app.js b/js/DI/app.js index cad1fe004..1893ca82d 100644 --- a/js/DI/app.js +++ b/js/DI/app.js @@ -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 } }; diff --git a/js/DI/collection.js b/js/DI/collection.js index 244ea096a..eea70ec09 100644 --- a/js/DI/collection.js +++ b/js/DI/collection.js @@ -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>/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 } diff --git a/js/DI/dynamic.js b/js/DI/dynamic.js index 4415b3ff8..a6830e7c2 100644 --- a/js/DI/dynamic.js +++ b/js/DI/dynamic.js @@ -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); diff --git a/js/DI/form.js b/js/DI/form.js index 71386744d..6ca96c230 100644 --- a/js/DI/form.js +++ b/js/DI/form.js @@ -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. * diff --git a/sources/DI/Controller/ObjectController.php b/sources/DI/Controller/ObjectController.php index cf2d9fe61..84166190b 100644 --- a/sources/DI/Controller/ObjectController.php +++ b/sources/DI/Controller/ObjectController.php @@ -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(), ]) ]); diff --git a/sources/DI/Form/Builder/AttributeBuilder.php b/sources/DI/Form/Builder/AttributeBuilder.php index f80d2d915..702e681d9 100644 --- a/sources/DI/Form/Builder/AttributeBuilder.php +++ b/sources/DI/Form/Builder/AttributeBuilder.php @@ -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){ diff --git a/sources/DI/Form/Type/Compound/ObjectType.php b/sources/DI/Form/Type/Compound/ObjectType.php index 447b6b8e4..e5d12e29a 100644 --- a/sources/DI/Form/Type/Compound/ObjectType.php +++ b/sources/DI/Form/Type/Compound/ObjectType.php @@ -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, diff --git a/templates/DI/form/theme/classic_theme.html.twig b/templates/DI/form/theme/classic_theme.html.twig index 9a38d54ed..c26720ba5 100644 --- a/templates/DI/form/theme/classic_theme.html.twig +++ b/templates/DI/form/theme/classic_theme.html.twig @@ -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 %} - Indirect - {% endif %} +
- {% if is_abstract %} - abstract - {% endif %} + {% if is_indirect %} + Indirect + {% endif %} - {# container #} -