Skip to content

Implement in-place edition in list & details actions #6928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: 4.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class App {
this.#createModalWindowsForDeleteActions();
this.#createPopovers();
this.#createTooltips();
this.#createEditInPlaceElements();

document.addEventListener('ea.collection.item-added', () => this.#createAutoCompleteFields());
}
Expand Down Expand Up @@ -449,4 +450,135 @@ class App {
});
});
}

#createEditInPlaceElements() {
const toggle_attr = 'data-edit-in-place-toggle';
const toggles = document.querySelectorAll('['+toggle_attr+']');

if (!toggles.length) {
return;
}

toggles.forEach((toggle) => {
const unique_id = toggle.getAttribute(toggle_attr);
if (!unique_id) {
console.error('There is an element with attribute "'+toggle_attr+'", but the attribute has no value.');
return;
}
const field_container = document.querySelector(`[data-edit-field="${unique_id}"]`)
if (!field_container) {
console.error('There is an element with attribute "'+toggle_attr+'", but there is no field value associated with it.');
return;
}
const form_container = document.querySelector(`[data-edit-form="${unique_id}"]`)
if (!form_container) {
console.error('There is an element with attribute "'+toggle_attr+'", but there is no edit form container associated with it.');
return;
}
const message_container = document.querySelector(`[data-edit-message="${unique_id}"]`)
if (!message_container) {
console.error('There is an element with attribute "'+toggle_attr+'", but there is no message container associated with it.');
return;
}
const form = form_container.querySelector('form');
if (!form) {
console.error('There is an element with attribute "'+toggle_attr+'", but there is no edit form associated with it.');
return;
}

field_container.style.display = 'block';
form_container.style.display = 'none';
let show_form = false;
toggle.addEventListener('click', () => {
show_form = !show_form;
form_container.style.display = show_form ? 'block' : 'none';
field_container.style.display = show_form ? 'none' : 'block';
});

form_container.addEventListener('submit', function (event) {
event.preventDefault();
event.stopPropagation();
const url = form.action;

message_container.className = '';
message_container.innerHTML = '';

const formData = new FormData(form);

[...form_container.querySelectorAll('input,select')]
.filter(el => !el.hasAttribute('disabled'))
.forEach((el) => {
el.setAttribute('disabled', 'disabled');
el.dataset.enableAfterCall = '1';
});

fetch(url, {
method: 'post',
body: formData,
headers: {
'Accept': 'application/json'
}
}).catch((e) => {
message('error', e, `HTTP error.`);
}).then((res) => {
if (!res) {
message('error', res, `Internal error: Empty response.`);
return;
}

if (res.status !== 200) {
message('error', res, `Form submit error.`);
return;
}

return res.json();
}).then((json) => {
if (!json) {
console.error('No data.');
return;
}
if (!json.field_content) {
console.error('The property "field_content" was not returned.');
message('error', json, `Internal error: The field content was not returned.`);
return;
}

console.info('Successful edit', json);
field_container.style.display = 'block';
form_container.style.display = 'none';
show_form = false;
field_container.innerHTML = json.field_content;
}).finally(() => {
[...form_container.querySelectorAll('input,select')]
.filter(el => el.dataset.enableAfterCall !== undefined)
.forEach((el) => el.removeAttribute('disabled', 'disabled'));
});
});

function message(type, object, message) {
let className = 'alert ';
if (type === 'success') {
className += 'alert-success'
} else if (type === 'error') {
className += 'alert-danger'
} else {
className += 'alert-info';
}

message = message || '';

if (message) {
message_container.className = className;
message_container.innerHTML = message;
}

if (type === 'error') {
console.error(message);
console.error(object);
} else {
console.info(message, object);
}
}
});
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"symfony/http-kernel": "^5.4|^6.0|^7.0",
"symfony/intl": "^5.4|^6.0|^7.0",
"symfony/property-access": "^5.4|^6.0|^7.0",
"symfony/polyfill-php84": "^1.32",
"symfony/security-bundle": "^5.4|^6.0|^7.0",
"symfony/string": "^5.4|^6.0|^7.0",
"symfony/translation": "^5.4|^6.0|^7.0",
Expand Down
5 changes: 5 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Filter\Configurator\TextConfigurator as TextFilterConfigurator;
use EasyCorp\Bundle\EasyAdminBundle\Form\Extension\CollectionTypeExtension;
use EasyCorp\Bundle\EasyAdminBundle\Form\Extension\EaCrudFormTypeExtension;
use EasyCorp\Bundle\EasyAdminBundle\Form\Factory\AdminSingleFieldFormFactory;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\CrudFormType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\FileUploadType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\FiltersFormType;
Expand Down Expand Up @@ -128,6 +129,9 @@
->arg(2, service('twig'))
->tag('kernel.event_listener', ['event' => 'kernel.exception', 'priority' => -64])

->set(AdminSingleFieldFormFactory::class)
->arg(0, service('form.factory'))

->set(EasyAdminTwigExtension::class)
// I don't know if we truly need the locator to get a new instance of the
// service whenever we generate a new URL, Maybe it's enough with the route parameter
Expand All @@ -138,6 +142,7 @@
->arg(3, new Reference('asset_mapper.importmap.renderer', ContainerInterface::NULL_ON_INVALID_REFERENCE))
->arg(4, service('translator'))
->arg(5, new Reference('.ux_icons.twig_icon_runtime', ContainerInterface::NULL_ON_INVALID_REFERENCE))
->arg(6, new Reference(AdminSingleFieldFormFactory::class))
->tag('twig.extension')

->set(EaCrudFormTypeExtension::class)
Expand Down
2 changes: 0 additions & 2 deletions public/app.1ecd6d7a.js

This file was deleted.

2 changes: 2 additions & 0 deletions public/app.a65bcfef.js

Large diffs are not rendered by default.

File renamed without changes.
2 changes: 1 addition & 1 deletion public/entrypoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"/app.4bcf2837.css"
],
"js": [
"/app.1ecd6d7a.js"
"/app.a65bcfef.js"
]
},
"form": {
Expand Down
2 changes: 1 addition & 1 deletion public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"app.css": "app.4bcf2837.css",
"app.js": "app.1ecd6d7a.js",
"app.js": "app.a65bcfef.js",
"form.js": "form.bcec6c2a.js",
"page-layout.js": "page-layout.3347892e.js",
"page-color-scheme.js": "page-color-scheme.30cb23c2.js",
Expand Down
Loading