149 lines
4.5 KiB
JavaScript
149 lines
4.5 KiB
JavaScript
/**
|
||
* VITEC – click-to-edit for the "Current value" column of the product text
|
||
* import (Edit Product view).
|
||
* EXT:vitec/Resources/Public/Javascript/product-inline-edit.js
|
||
*
|
||
* Delegated click handler on [data-vitec-inline-edit] cells (backend CSP
|
||
* forbids inline handlers). Clicking a cell fetches the FULL raw field value
|
||
* (the cell itself only shows a truncated preview), swaps in a textarea with
|
||
* Save/Cancel, and writes through the vitec_product_field_save AJAX route,
|
||
* which uses the same whitelist and DataHandler path as the form apply.
|
||
* RTE fields are edited as raw HTML on purpose - no inline WYSIWYG
|
||
* (decision 2026-08-28).
|
||
*/
|
||
import AjaxRequest from "@typo3/core/ajax/ajax-request.js";
|
||
import Notification from "@typo3/backend/notification.js";
|
||
|
||
const SELECTOR = "[data-vitec-inline-edit]";
|
||
|
||
function previewOf(cell) {
|
||
return cell.querySelector("[data-vitec-preview]");
|
||
}
|
||
|
||
function closeEditor(cell) {
|
||
const editor = cell.querySelector("[data-vitec-editor]");
|
||
if (editor) {
|
||
editor.remove();
|
||
}
|
||
const preview = previewOf(cell);
|
||
if (preview) {
|
||
preview.hidden = false;
|
||
}
|
||
delete cell.dataset.vitecEditing;
|
||
}
|
||
|
||
async function openEditor(cell) {
|
||
if (cell.dataset.vitecEditing === "1") {
|
||
return;
|
||
}
|
||
cell.dataset.vitecEditing = "1";
|
||
|
||
const getUrl = TYPO3.settings.ajaxUrls["vitec_product_field_get"];
|
||
const saveUrl = TYPO3.settings.ajaxUrls["vitec_product_field_save"];
|
||
if (!getUrl || !saveUrl) {
|
||
Notification.error("Inline edit", "AJAX routes are not registered.");
|
||
delete cell.dataset.vitecEditing;
|
||
return;
|
||
}
|
||
|
||
let value = "";
|
||
try {
|
||
const response = await new AjaxRequest(getUrl)
|
||
.withQueryArguments({ uid: cell.dataset.uid, field: cell.dataset.field })
|
||
.get();
|
||
const data = await response.resolve();
|
||
if (!data.success) {
|
||
Notification.error("Inline edit", data.message || "Could not load the field value.");
|
||
delete cell.dataset.vitecEditing;
|
||
return;
|
||
}
|
||
value = data.value;
|
||
} catch (e) {
|
||
Notification.error("Inline edit", "Loading the field value failed.");
|
||
delete cell.dataset.vitecEditing;
|
||
return;
|
||
}
|
||
|
||
const preview = previewOf(cell);
|
||
if (preview) {
|
||
preview.hidden = true;
|
||
}
|
||
|
||
const wrap = document.createElement("div");
|
||
wrap.setAttribute("data-vitec-editor", "1");
|
||
|
||
const textarea = document.createElement("textarea");
|
||
textarea.className = "form-control";
|
||
textarea.rows = Math.min(14, Math.max(3, Math.ceil(value.length / 80)));
|
||
textarea.style.fontFamily = "monospace";
|
||
textarea.style.fontSize = "11px";
|
||
textarea.value = value;
|
||
wrap.appendChild(textarea);
|
||
|
||
const bar = document.createElement("div");
|
||
bar.style.marginTop = ".25rem";
|
||
|
||
// type="button" is essential: the cells live inside the big mapping <form>,
|
||
// a bare <button> would submit it.
|
||
const saveBtn = document.createElement("button");
|
||
saveBtn.type = "button";
|
||
saveBtn.className = "btn btn-sm btn-primary";
|
||
saveBtn.textContent = "Save";
|
||
|
||
const cancelBtn = document.createElement("button");
|
||
cancelBtn.type = "button";
|
||
cancelBtn.className = "btn btn-sm btn-default";
|
||
cancelBtn.style.marginLeft = ".5rem";
|
||
cancelBtn.textContent = "Cancel";
|
||
|
||
bar.appendChild(saveBtn);
|
||
bar.appendChild(cancelBtn);
|
||
wrap.appendChild(bar);
|
||
cell.appendChild(wrap);
|
||
textarea.focus();
|
||
|
||
cancelBtn.addEventListener("click", () => closeEditor(cell));
|
||
textarea.addEventListener("keydown", (event) => {
|
||
if (event.key === "Escape") {
|
||
closeEditor(cell);
|
||
}
|
||
});
|
||
|
||
saveBtn.addEventListener("click", async () => {
|
||
saveBtn.disabled = true;
|
||
try {
|
||
const response = await new AjaxRequest(saveUrl).post({
|
||
uid: cell.dataset.uid,
|
||
field: cell.dataset.field,
|
||
value: textarea.value,
|
||
});
|
||
const data = await response.resolve();
|
||
if (!data.success) {
|
||
Notification.error("Inline edit", data.message || "Saving failed.");
|
||
saveBtn.disabled = false;
|
||
return;
|
||
}
|
||
if (preview) {
|
||
preview.textContent = data.preview;
|
||
}
|
||
closeEditor(cell);
|
||
Notification.success("Inline edit", cell.dataset.field + " saved.");
|
||
} catch (e) {
|
||
Notification.error("Inline edit", "Saving failed.");
|
||
saveBtn.disabled = false;
|
||
}
|
||
});
|
||
}
|
||
|
||
document.addEventListener("click", (event) => {
|
||
const cell = event.target.closest(SELECTOR);
|
||
if (cell === null) {
|
||
return;
|
||
}
|
||
// Clicks on the editor's own controls must not re-open it.
|
||
if (event.target.closest("[data-vitec-editor]")) {
|
||
return;
|
||
}
|
||
openEditor(cell);
|
||
});
|