Make add / edit in dashboard interactive, and pass on data

This commit is contained in:
Stephen Abello
2026-01-09 16:31:36 +01:00
parent 7b193dd737
commit 3c365fc103
6 changed files with 132 additions and 22 deletions

View File

@@ -235,10 +235,13 @@ ibo-dashboard[data-edit-mode="view"] {
}
.ibo-dashboard[data-edit-mode="edit"] .ibo-dashboard--grid:has(ibo-dashboard-grid-slot > ibo-dashlet[data-edit-mode="edit"]) .ibo-dashboard--grid--backdrop {
position: absolute;
height: 100%;
width: 100%;
background-color: $ibo-color-grey-900;
height: calc(100% + 24px);
// 36px is $ibo-page-container--elements-padding-x, handle variable resolution
width: calc(100% + 36px + 36px);
margin: -24px -#{36px} 0 -#{36px};
background-color: $ibo-color-grey-400;
z-index: 2;
opacity: 50%;
opacity: 60%;
}

View File

@@ -55,6 +55,18 @@ class IboGrid extends HTMLElement {
}
}
GetDashletElement(sDashletId) {
const aSlots = this.getSlots();
for (let oSlot of aSlots) {
if (oSlot.oDashlet && oSlot.oDashlet.sDashletId === sDashletId) {
return oSlot.oDashlet;
}
}
return null;
}
AddDashlet(sDashlet, aOptions = {}) {
// Get the dashlet as an object
const oParser = new DOMParser();
@@ -73,6 +85,16 @@ class IboGrid extends HTMLElement {
return oDashlet.sDashletId;
}
RefreshDashlet (sDashlet, aOptions = {}) {
const oParser = new DOMParser();
const oDocument = oParser.parseFromString(sDashlet, 'text/html');
const oNewDashlet = oDocument.body.firstChild;
debugger;
// Can't use oNewDashet.sDashletId as it's not in the DOM yet and connectedCallback hasn't been called yet
const oExistingDashlet = this.GetDashletElement(oNewDashlet.getAttribute('data-dashlet-id') );
oExistingDashlet.replaceWith(oNewDashlet);
}
CloneDashlet(sDashletId) {
const aSlots = this.getSlots();
for (let oSlot of aSlots) {

View File

@@ -84,7 +84,7 @@ class IboDashboard extends HTMLElement {
}
AddNewDashlet(sDashletClass, aDashletOptions = {}) {
const sNewDashletUrl = GetAbsoluteUrlAppRoot() + '/pages/UI.php?route=dashboard.new_dashlet&dashlet_class='+encodeURIComponent(sDashletClass);
const sNewDashletUrl = GetAbsoluteUrlAppRoot() + '/pages/UI.php?route=dashboard.get_dashlet&dashlet_class='+encodeURIComponent(sDashletClass);
fetch(sNewDashletUrl)
.then(async data => {
@@ -92,31 +92,52 @@ class IboDashboard extends HTMLElement {
// TODO 3.3 Either open the dashlet form right away, or just enter edit mode
this.EditDashlet(sDashletId);
const sGetashletFormUrl = GetAbsoluteUrlAppRoot() + '/pages/UI.php?route=dashboard.get_dashlet_form&dashlet_class='+encodeURIComponent(sDashletClass);
fetch(sGetashletFormUrl)
.then(async formData => {
const sFormData = await formData.text();
this.HideDashletTogglers();
this.SetDashletForm(sFormData);
});
})
}
RefreshDashlet(sDashletId) {
const oDashletElem = this.oGrid.GetDashletElement(sDashletId);
let sGetDashletUrl = GetAbsoluteUrlAppRoot() + '/pages/UI.php?route=dashboard.get_dashlet&dashlet_class=' + encodeURIComponent(oDashletElem.sType)
+'&dashlet_id=' + encodeURIComponent(oDashletElem.sDashletId);
if(oDashletElem.formData.length > 0) {
sGetDashletUrl += '&values=' + encodeURIComponent(oDashletElem.formData);
}
return fetch(sGetDashletUrl)
.then(async data => {
const sDashletId = this.oGrid.RefreshDashlet(await data.text());
});
}
HideDashletTogglers() {
const aTogglers = document.querySelector('.ibo-dashlet-panel--entries');
aTogglers.classList.add('ibo-is-hidden');
}
ShowDashletTogglers() {
const aTogglers = document.querySelector('.ibo-dashlet-panel--entries');
aTogglers.classList.remove('ibo-is-hidden');
}
SetDashletForm(sFormData) {
const oFormContainer = document.querySelector('.ibo-dashlet-panel--form-container');
oFormContainer.innerHTML = sFormData;
oFormContainer.classList.remove('ibo-is-hidden');
}
ClearDashletForm() {
const oFormContainer = document.querySelector('.ibo-dashlet-panel--form-container');
oFormContainer.innerHTML = '';
oFormContainer.classList.add('ibo-is-hidden');
}
EditDashlet(sDashletId) {
console.log(this.oGrid);
const oDashlet = this.oGrid.GetDashletElement(sDashletId);
const me = this;
// TODO 3.3: Implement dashlet editing when forms are ready
console.log("Edit dashlet: "+sDashletId);
@@ -130,6 +151,52 @@ class IboDashboard extends HTMLElement {
this.querySelector('ibo-dashlet[data-dashlet-id="'+sDashletId+'"]').setAttribute('data-edit-mode', 'edit');
// Disable dashboard buttons so we need to finish this edition first
// Fetch dashlet form from server
let sGetashletFormUrl = GetAbsoluteUrlAppRoot() + '/pages/UI.php?route=dashboard.get_dashlet_form&dashlet_class='+encodeURIComponent(oDashlet.sType);
if(oDashlet.formData.length > 0) {
sGetashletFormUrl += '&values=' + encodeURIComponent(oDashlet.formData);
}
fetch(sGetashletFormUrl)
.then(async formData => {
const sFormData = await formData.text();
this.HideDashletTogglers();
this.SetDashletForm(sFormData);
// Listen to form submission event to display
document.addEventListener('itop:TurboStreamEvent', function (event) {
console.log(event);
if(event.detail.id === oDashlet.sType + '-turbo-stream-event' && event.detail.valid === "1") {
// Notify it all went well
CombodoToast.OpenToast('Dashlet created/updated');
// Clean edit mode
me.querySelector('ibo-dashlet[data-dashlet-id="'+sDashletId+'"]').setAttribute('data-edit-mode', 'view');
me.ShowDashletTogglers();
me.ClearDashletForm();
// Update local dashlet and refresh it
oDashlet.formData = event.detail.view_data;
me.RefreshDashlet(oDashlet.sDashletId);
// TODO 3.3 Remove both event listener by making this code a dedicated function and call removeEventListener on it
}
});
document.querySelector('.ibo-dashlet-panel--form-container button[name="dashboard_cancel"]').addEventListener('click', function (event) {
// TODO 3.3 Remove both event listener by making this code a dedicated function and call removeEventListener on it
// Clean edit mode
me.querySelector('ibo-dashlet[data-dashlet-id="'+sDashletId+'"]').setAttribute('data-edit-mode', 'view');
me.ShowDashletTogglers();
me.ClearDashletForm();
// TODO 3.3 If this is an addition, remove the previewed dashlet
// TODO 3.3 If this is an edition, revert the dashlet to its initial state
})
});
}
CloneDashlet(sDashletId) {

View File

@@ -7,9 +7,9 @@ class IboDashlet extends HTMLElement {
/** @type {string} */
this.sDashletId = this.GetDashletId();
/** @type {string} */
this.type = this.GetDashletType();
this.sType = this.GetDashletType();
/** @type {Object} */
this.formData = {};
this.formData = this.GetFormData();
/** @type {Object} unused yet */
this.meta = {};
@@ -38,6 +38,10 @@ class IboDashlet extends HTMLElement {
return this.getAttribute("data-dashlet-type") || "";
}
GetFormData() {
return this.getAttribute("data-form-view-data") || "";
}
static MakeNew(sDashlet) {
const oDashlet = document.createElement('ibo-dashlet');
oDashlet.innerHTML = sDashlet;
@@ -48,7 +52,7 @@ class IboDashlet extends HTMLElement {
Serialize() {
const aDashletData = {
id: this.sDashletId,
type: this.type,
type: this.sType,
formData: this.formData,
};

View File

@@ -15,10 +15,14 @@ class DashboardController extends AbstractController
{
public const ROUTE_NAMESPACE = 'dashboard';
public function OperationNewDashlet()
public function OperationGetDashlet()
{
$sDashletClass = utils::ReadParam('dashlet_class', '', false, utils::ENUM_SANITIZATION_FILTER_PHP_CLASS);
$sDashletId = utils::ReadParam('dashlet_id', '', false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
// TODO 3.3 Check if raw data is the right call
$sValues = utils::ReadParam('values', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$aValues = !empty($sValues) ? json_decode($sValues, true) : [];
$oPage = new AjaxPage('');
if (is_subclass_of($sDashletClass, 'Dashlet')) {
@@ -26,13 +30,16 @@ class DashboardController extends AbstractController
$sDashletId = !empty($sDashletId) ? $sDashletId : uniqid();
$oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId);
//$offset = $oPage->start_capture();
// TODO 3.3 I'd like to update dashlet from frontend's normalized data
// $oDashlet->FromNormalizedParams($aValues);
$oDashletBlock = $oDashlet->DoRender($oPage, true /* bEditMode */, false /* bEnclosingDiv */);
//$sHtml = addslashes($oPage->end_capture($offset));
if ($oDashletBlock instanceof iUIBlock) {
// Wrap the dashlet
$oDashletWrapper = new DashletWrapper($oDashletBlock, $sDashletClass, $oDashlet->GetID());
// TODO 3.3 Re-normalize Dashlet's values instead of using user input
$oDashletWrapper = new DashletWrapper($oDashletBlock, $sDashletClass, $oDashlet->GetID(), $aValues);
$oPage->AddUiBlock($oDashletWrapper);
}
}
@@ -43,6 +50,10 @@ class DashboardController extends AbstractController
public function OperationGetDashletForm()
{
$sDashletClass = utils::ReadParam('dashlet_class', '', false, utils::ENUM_SANITIZATION_FILTER_PHP_CLASS);
// TODO 3.3 Do we want to use a readparam here or SF internal mechanism ?
$sValues = utils::ReadParam('values', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$aValues = !empty($sValues) ? json_decode($sValues, true) : [];
$oPage = new AjaxPage('');
$oUIBlock = TurboFormUIBlockFactory::MakeForDashletConfiguration($sDashletClass);

View File

@@ -1,4 +1,7 @@
<ibo-dashlet class="grid-stack-item-content aa" data-dashlet-type="{{ oUIBlock.GetDashletClass() }}" data-dashlet-id="{{ oUIBlock.GetDashletId() }}" data-form-view-data="{{ oUIBlock.GetFormViewData()|json_encode }}">
<ibo-dashlet class="grid-stack-item-content aa"
data-dashlet-type="{{ oUIBlock.GetDashletClass() }}"
data-dashlet-id="{{ oUIBlock.GetDashletId() }}"
data-form-view-data="{{ oUIBlock.GetFormViewData()|json_encode }}">
<div class="ibo-dashlet--actions">
<button class="ibo-dashlet--action ibo-dashlet--action--edit ibo-button ibo-is-alternative ibo-is-neutral"
title="Edit dashlet"