poc form SDK (change dependencies implementation)

This commit is contained in:
Benjamin Dalsass
2023-09-06 11:13:42 +02:00
parent 9866a10564
commit 0248ac02a9
13 changed files with 212 additions and 126 deletions

View File

@@ -50,6 +50,9 @@ label.locked:after{
color: #ffcc00;
font-size: .7rem;
}
label.dependent{
color: #2757af;
}
form{
/*background-color: #f8f8f8;*/
border-radius: 10px;

View File

@@ -27,6 +27,11 @@ const App = function(){
$(aSelectors.darkModeButton).toggleClass('active', true);
}
const tooltips = document.querySelectorAll("[data-bs-toggle='tooltip']");
tooltips.forEach((el) => {
new bootstrap.Tooltip(el);
});
}
return {

View File

@@ -64,8 +64,6 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
*/
function addFormToCollection(e){
alert('addFormToCollection');
// retrieve link set container
const oContainer = e.currentTarget.closest('.link_set_widget_container');
@@ -113,7 +111,7 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
const cont = e.currentTarget.closest('.link_set_widget_container');
// open modal
const myModalAlternative = new bootstrap.Modal('#object_modal', []);
const myModalAlternative = new bootstrap.Modal('#object_modal', {});
myModalAlternative.show();
// compute object form url
@@ -159,8 +157,6 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
*/
function handleElement(oContainer)
{
console.log('collection handleElement ' + oContainer);
listenAddItem(oContainer);
listenCreateItem(oContainer);
listenRemoveItem(oContainer);
@@ -208,8 +204,10 @@ const Collection = function(oForm, objectFormUrl, objectSaveUrl){
// const el = oToolkit.createElementFromHtml(inner);
// console.log(el);
//
// const myModalAlternative = new bootstrap.Modal('#object_modal', []);
// myModalAlternative.hide();
const myModalAlternative = new bootstrap.Modal('#object_modal', {
hide: true
});
console.log($(`[data-att-code="${oForm.dataset.attCode}"] tbody`));

View File

@@ -54,12 +54,12 @@ const Form = function(oWidget, oDynamic){
* updateField.
*
* @param oEvent
* @param oForm
* @param oObjectContainer
* @param oElement
* @param aDependentAttCodes
* @returns {Promise<void>}
*/
async function updateField(oEvent, oForm, oElement, aDependentAttCodes){
async function updateField(oEvent, oObjectContainer, oElement, aDependentAttCodes){
const aDependenciesAttCodes = [];
@@ -119,7 +119,7 @@ const Form = function(oWidget, oDynamic){
// update fom
const sReloadResponse = await updateForm(sRequestBody, oForm.dataset.reloadUrl, oForm.getAttribute('method'));
const sReloadResponse = await updateForm(sRequestBody, oObjectContainer.dataset.reloadUrl, 'POST');
const oReloadedElement = oToolkit.parseTextToHtml(sReloadResponse);
@@ -152,9 +152,6 @@ const Form = function(oWidget, oDynamic){
*/
function initDependencies(oElement){
// retrieve parent form
const oForm = oElement.closest('form');
// compute dependencies map
let aMapDependencies = {};
@@ -164,6 +161,9 @@ const Form = function(oWidget, oDynamic){
// iterate throw dependent fields...
aDependentsFields.forEach(function (oDependentField) {
// retrieve object container
const oObjectContainer = oDependentField.closest('[data-block="object_container"]');
// retrieve dependency data
const sDependsOn = oDependentField.dataset.dependsOn;
@@ -173,27 +173,44 @@ const Form = function(oWidget, oDynamic){
// iterate throw the dependencies...
aDependsEls.forEach(function(sEl){
// add dependency att code to map
if(!(sEl in aMapDependencies)){
aMapDependencies[sEl] = [];
const sId = oObjectContainer.dataset.containerId;
if(!(sId in aMapDependencies)) {
aMapDependencies[sId] = [];
}
aMapDependencies[sEl].push(oDependentField.dataset.attCode);
// add dependency att code to map
if(!(sEl in aMapDependencies[sId])){
aMapDependencies[sId][sEl] = [];
}
aMapDependencies[sId][sEl].push(oDependentField.dataset.attCode);
});
});
// iterate throw dependencies map...
for(let sAttCode in aMapDependencies){
for(let sContainerId in aMapDependencies) {
// retrieve corresponding field
const oDependsOnElement = oElement.querySelector(String.format(aSelectors.dataAttCode, sAttCode));
// retrieve object container
const oObjectContainer = document.querySelector(`[data-container-id="${sContainerId}"]`);
// listen changes
if(oDependsOnElement !== null){
oDependsOnElement.addEventListener('change', (event) => updateField(event, oForm, oElement, aMapDependencies[sAttCode]));
const aMapContainer = aMapDependencies[sContainerId];
// iterate throw dependencies map...
for (let sAttCode in aMapContainer) {
// retrieve corresponding field
const oDependsOnElement = oObjectContainer.querySelector(String.format(aSelectors.dataAttCode, sAttCode));
// listen changes
if (oDependsOnElement !== null) {
oDependsOnElement.addEventListener('change', (event) => updateField(event, oObjectContainer, oElement, aMapContainer[sAttCode]));
}
}
}
}
/**

View File

@@ -81,10 +81,7 @@ class ObjectController extends AbstractController
// create object form
$oForm = $this->createForm(ObjectType::class, $oObject, [
'object_class' => $class,
'attr' => [
'data-reload-url' => $this->generateUrl('object_reload', ['class' => $class, "id" => $id])
]
'object_class' => $class
]);
// handle HTTP request

View File

@@ -94,6 +94,10 @@ class AttributeBuilder
if(count($oAttributeDefinition->GetPrerequisiteAttributes()) > 0){
$dependencies = implode(' ', $oAttributeDefinition->GetPrerequisiteAttributes());
$aFormType['options']['attr']['data-depends-on'] = $dependencies;
$aFormType['options']['label_attr']['data-bs-toggle'] = 'tooltip';
$aFormType['options']['label_attr']['data-bs-title'] = '<b>Depends on</b> ' . $dependencies;
$aFormType['options']['label_attr']['data-bs-html'] = 'true';
$aFormType['options']['label_attr']['class'] .= ' dependent';
$aFormType['depends_on'] = $dependencies;
}

View File

@@ -3,8 +3,6 @@
namespace Combodo\iTop\DI\Form\Type\Compound;
use cmdbAbstractObject;
use Combodo\iTop\DI\Form\Builder\AttributeBuilder;
use Combodo\iTop\DI\Form\Builder\LayoutBuilder;
use Combodo\iTop\DI\Form\Listener\ObjectFormListener;
use Combodo\iTop\DI\Services\ObjectPresentationService;
use Symfony\Component\Form\AbstractType;
@@ -32,8 +30,6 @@ class ObjectType extends AbstractType
*
* @param ObjectFormListener $oObjectFormModifier
* @param ObjectPresentationService $objectPresentationService
* @param \Combodo\iTop\DI\Form\Builder\AttributeBuilder $oAttributeBuilder
* @param \Combodo\iTop\DI\Form\Builder\LayoutBuilder $oLayoutBuilder
*/
public function __construct(ObjectFormListener $oObjectFormModifier, ObjectPresentationService $objectPresentationService)
{
@@ -80,6 +76,7 @@ class ObjectType extends AbstractType
parent::buildView($view, $form, $options);
$view->vars['z_list'] = $options['z_list'];
$view->vars['object_class'] = $options['object_class'];
}
}

View File

@@ -32,58 +32,12 @@
<body data-bs-theme="light">
<!-- navbar -->
<nav class="navbar navbar-expand-lg fixed-top bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#"><img class="app_icon" src="{{ asset_image('DI/flask-solid.svg') }}" width="24px"> Forms SDK</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{ path('home') }}">Home</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Edit Object
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'Organization', 'id': 1}) }}">Organization</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'Person', 'id': 1}) }}">Person</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'UserLocal', 'id': 1}) }}">UserLocal</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'UserRequest', 'id': 1}) }}">UserRequest</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
New Object
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'Organization', 'id': 0}) }}">Organization</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'Person', 'id': 0}) }}">Person</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'UserLocal', 'id': 0}) }}">UserLocal</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'UserRequest', 'id': 0}) }}">UserRequest</a></li>
</ul>
</li>
<li class="nav-item">
<a href="{{ path('configuration_edit') }}" class="nav-link">Edit Configuration</a>
</li>
</ul>
<div>
<button id="dark_mode" type="button" class="btn" role="button" data-bs-toggle="button" aria-pressed="false"><i class="fa-solid fa-moon text-warning"></i></button>
</div>
<form class="actions d-flex">
{% block actions %}{% endblock %}
</form>
</div>
</div>
</nav>
{# navbar #}
{% include 'DI/navbar.html.twig' %}
{# - BLOCK BODY - #}
<div class="m-3">
{% block body %}{% endblock %}
</div>
{# - BLOCK TOASTS - #}
@@ -91,29 +45,10 @@
{% block toasts %}{% endblock %}
</div>
<!-- Full screen modal -->
<div class="modal fade" id="object_modal" tabindex="-1" aria-labelledby="object_modal1" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-4" id="exampleModalFullscreenLabel">Create object</h1>
<div>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" aria-label="Close">Cancel</button>
<button type="button" class="btn btn-primary" data-action="save_modal_object" aria-label="Confirm">Confirm</button>
</div>
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>
<div id="test">
</div>
{# - BLOCK MODALS - #}
{% block modals %}{% endblock %}
{# scripts #}
<script>
// Toolkit

View File

@@ -4,9 +4,6 @@
Configuration Edition
{% endblock %}
{% block actions %}
{% endblock %}
{% block body %}

View File

@@ -143,8 +143,16 @@
{# Object #}
{%- block object_widget -%}
{# convert object id if new objet (>> controller use -1 instead of 0 for new objects) #}
{% if form.vars.data is not empty %}
{% set objectId = form.vars.data.id %}
{% if objectId == -1 %}
{% set objectId = 0 %}
{% endif %}
{% endif %}
{% if z_list == 'list' %}
<tr data-block="container">
<tr data-block="object_container" {% if form.vars.data is not empty %}data-container-id="{{ form.vars.id }}" data-object-id="{{ form.vars.data.id }}" data-reload-url="{{ path('object_reload', {class: form.vars.object_class, id: objectId }) }}"{% endif %}>
{% for child in form %}
<td data-block="container">
{{ form_widget(child) }}
@@ -155,9 +163,9 @@
</td>
</tr>
{% else %}
{{ form_widget(form) }}
<div data-block="object_container" data-container-id="{{ form.vars.id }}" data-object-id="{{ form.vars.data.id }}" data-reload-url="{{ path('object_reload', {class: form.vars.object_class, id: objectId }) }}">
{{ form_widget(form) }}
</div>
{% endif %}
{%- endblock object_widget -%}

View File

@@ -0,0 +1,57 @@
<nav class="navbar navbar-expand-lg fixed-top bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#"><img class="app_icon" src="{{ asset_image('DI/flask-solid.svg') }}" width="24px"> Forms SDK</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{ path('home') }}">Home</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Edit Object
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'Organization', 'id': 1}) }}">Organization</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'Person', 'id': 1}) }}">Person</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'UserLocal', 'id': 1}) }}">UserLocal</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'UserRequest', 'id': 1}) }}">UserRequest</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
New Object
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'Organization', 'id': 0}) }}">Organization</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'Person', 'id': 0}) }}">Person</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'UserLocal', 'id': 0}) }}">UserLocal</a></li>
<li><a class="dropdown-item" href="{{ path('object_edit', {'class': 'UserRequest', 'id': 0}) }}">UserRequest</a></li>
</ul>
</li>
<li class="nav-item">
<a href="{{ path('configuration_edit') }}" class="nav-link">Edit Configuration</a>
</li>
</ul>
<div>
<button id="dark_mode" type="button" class="btn" role="button" data-bs-toggle="button" aria-pressed="false"><i class="fa-solid fa-moon text-warning"></i></button>
</div>
{% if aAction is defined %}
<form class="actions d-flex">
{% for button in aAction|sort((a, b) => a.rank <=> b.rank ) %}
{% if button.type == 'link' %}
<a href="{{ button.href }}" class="btn {% if button.primary is defined and button.primary %}btn-primary{% else %}btn-secondary{% endif %} btn {% if button.disabled is defined and button.disabled %} disabled {% endif %}"><i class="{{ button.icon }}"></i>{% if button.label is defined %} {{ button.label }}{% endif %}</a>
{% elseif button.type == 'reset' %}
<button type="reset" class="{% if button.primary is defined and button.primary %}btn-primary{% else %}btn-secondary{% endif %} btn" form="{{ button.form }}">{{ button.label }}</button>
{% elseif button.type == 'submit' %}
<button type="submit" class="{% if button.primary is defined and button.primary %}btn-primary{% else %}btn-secondary{% endif %} btn" form="{{ button.form }}">{{ button.label }}</button>
{% endif %}
{% endfor %}
</form>
{% endif %}
</div>
</div>
</nav>

View File

@@ -1,5 +1,43 @@
{% extends "DI/base.html.twig" %}
{% set aAction = {
reset: {
rank: 2,
type: 'reset',
label: 'Reset',
form: 'object_form'
},
submit: {
rank: 3,
type: 'submit',
label: 'Save',
form: 'object_form',
primary: true
}
}
%}
{% if id != 0 %}
{% set aAction = aAction|merge({
previous: {
rank: 0,
type: 'link',
disabled: id <= 1,
href: path('object_edit', {'class': class, 'id': id - 1}),
icon: 'fa-solid fa-arrow-left'
},
next: {
rank: 1,
type: 'link',
href: path('object_edit', {'class': class, 'id': id + 1}),
icon: 'fa-solid fa-arrow-right'
}
})
%}
{% endif %}
{% block title %}
Object Edition
{% endblock %}
@@ -14,15 +52,6 @@
{% endblock %}
{% block actions %}
{% if id != 0 %}
<a href="{{ path('object_edit', {'class': class, 'id': id - 1}) }}" class="btn btn-secondary btn {% if id <= 1 %} disabled {% endif %}"><i class="fa-solid fa-arrow-left"></i></a>
<a href="{{ path('object_edit', {'class': class, 'id': id + 1}) }}" class="btn btn-secondary btn"><i class="fa-solid fa-arrow-right"></i></a>
{% endif %}
<button type="reset" class="btn-secondary btn" form="object_form">Reset</button>
<button type="submit" class="btn-primary btn" form="object_form">Save</button>
{% endblock %}
{% block toasts %}
{# database information toast #}
@@ -44,6 +73,27 @@
{% endblock %}
{% block modals %}
<!-- Full screen modal -->
<div class="modal fade" id="object_modal" tabindex="-1" aria-labelledby="object_modal1" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-4" id="exampleModalFullscreenLabel">Create object</h1>
<div>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" aria-label="Close">Cancel</button>
<button type="button" class="btn btn-primary" data-action="save_modal_object" aria-label="Confirm">Confirm</button>
</div>
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>
{% endblock %}
{% block ready_scripts %}
{# Widget #}
@@ -54,9 +104,7 @@
{# Form #}
const oForm = new Form(oWidget, oDynamic);
document.querySelectorAll('form').forEach((formEl) => {
oForm.handleElement(formEl);
});
oForm.handleElement(document);
{# Collection #}
const oCollection = new Collection(oForm, '{{ path('object_form', {'class': 'object_class', 'id' : 0, 'name': 'form_name'}) }}', '{{ path('object_save', {'class': 'object_class', 'id' : 0, 'name': 'form_name'}) }}');

View File

@@ -1,15 +1,35 @@
{% extends "DI/base.html.twig" %}
{% set aAction = {
previous: {
rank: 0,
type: 'link',
disabled: id <= 1,
href: path('object_view', {'class': class, 'id': id - 1}),
icon: 'fa-solid fa-arrow-left'
},
next: {
rank: 1,
type: 'link',
disabled: id <= 1,
href: path('object_view', {'class': class, 'id': id + 1}),
icon: 'fa-solid fa-arrow-left'
},
edit: {
rank: 2,
type: 'link',
href: path('object_edit', {'class' : class, 'id': object.id}),
icon: 'fa-solid fa-pen-to-square',
label: 'Edit Object',
primary: true
}
}
%}
{% block title %}
Object Edition
{% endblock %}
{% block actions %}
<a href="{{ path('object_view', {'class': class, 'id': id - 1}) }}" class="btn btn-light btn {% if id <= 1 %}disabled{% endif %}"><i class="fa-solid fa-arrow-left"></i></a>
<a href="{{ path('object_view', {'class': class, 'id': id + 1}) }}" class="btn btn-light btn"><i class="fa-solid fa-arrow-right"></i></a>
<a href="{{ path('object_edit', {'class' : class, 'id': object.id}) }}" class="btn-primary btn"><i class="fa-solid fa-pen-to-square"></i> Edit Object</a>
{% endblock %}
{% block body %}
<div>