From cfbfaad1542a3da23bbbce8f86bd0c5ee85989cd Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 3 Nov 2020 16:14:45 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B02847=20-=20Dashboards=20edition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/dashlet.class.inc.php | 18 ++- .../components/dashlet/_dashlet.scss | 6 +- .../layout/dashboard/_dashboard.scss | 11 +- js/dashboard.js | 141 +++++++----------- js/dashlet.js | 36 ++--- pages/ajax.render.php | 32 ++-- .../UI/Component/Dashlet/DashletContainer.php | 1 + .../UI/Layout/Dashboard/DashboardColumn.php | 17 +++ 8 files changed, 123 insertions(+), 139 deletions(-) diff --git a/application/dashlet.class.inc.php b/application/dashlet.class.inc.php index e9964b35e..a01cc2873 100644 --- a/application/dashlet.class.inc.php +++ b/application/dashlet.class.inc.php @@ -21,6 +21,7 @@ use Combodo\iTop\Application\UI\Component\Dashlet\DashletFactory; use Combodo\iTop\Application\UI\Component\Html\Html; use Combodo\iTop\Application\UI\Component\Panel\PanelFactory; use Combodo\iTop\Application\UI\iUIBlock; +use Combodo\iTop\Application\UI\UIBlock; require_once(APPROOT.'application/forms.class.inc.php'); @@ -72,13 +73,16 @@ abstract class Dashlet { $refValue = $this->aProperties[$sProperty]; $sRefType = gettype($refValue); + if (gettype($sValue) == $sRefType) { // Do not change anything in that case! $ret = $sValue; } elseif ($sRefType == 'boolean') { $ret = ($sValue == 'true'); - } elseif (($sRefType == 'array') || (is_array($sValue))) { + } elseif ($sRefType == 'array') { $ret = explode(',', $sValue); + } elseif (is_array($sValue)) { + $ret = $sValue; } else { $ret = $sValue; settype($ret, $sRefType); @@ -190,10 +194,8 @@ abstract class Dashlet */ public function FromParams($aParams) { - foreach ($this->aProperties as $sProperty => $value) - { - if (array_key_exists($sProperty, $aParams)) - { + foreach ($this->aProperties as $sProperty => $value) { + if (array_key_exists($sProperty, $aParams)) { $this->aProperties[$sProperty] = $aParams[$sProperty]; } } @@ -206,7 +208,7 @@ abstract class Dashlet * @param bool $bEnclosingDiv * @param array $aExtraParams */ - public function DoRender($oPage, $bEditMode = false, $bEnclosingDiv = true, $aExtraParams = array()) + public function DoRender($oPage, $bEditMode = false, $bEnclosingDiv = true, $aExtraParams = array()): UIBlock { $sId = $this->GetID(); @@ -2279,7 +2281,7 @@ class DashletBadge extends Dashlet */ public function Render($oPage, $bEditMode = false, $aExtraParams = array()) { - $oDashletContainer = new DashletContainer(null, 'dashlet-content'); + $oDashletContainer = new DashletContainer($this->sId, 'dashlet-content'); $sClass = $this->aProperties['class']; $oFilter = new DBObjectSearch($sClass); @@ -2296,7 +2298,7 @@ class DashletBadge extends Dashlet */ public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array()) { - $oDashletContainer = new DashletContainer(null, 'dashlet-content'); + $oDashletContainer = new DashletContainer($this->sId, 'dashlet-content'); $sClass = $this->aProperties['class']; $sIconUrl = $this->oModelReflection->GetClassIcon($sClass, false); diff --git a/css/backoffice/components/dashlet/_dashlet.scss b/css/backoffice/components/dashlet/_dashlet.scss index 56eff6318..473595c66 100644 --- a/css/backoffice/components/dashlet/_dashlet.scss +++ b/css/backoffice/components/dashlet/_dashlet.scss @@ -13,7 +13,11 @@ $ibo-dashlet--elements-spacing-y: 24px !default; .ibo-dashlet{ width: $ibo-dashlet--width; margin: calc(#{$ibo-dashlet--elements-spacing-y} / 2) calc(#{$ibo-dashlet--elements-spacing-x} / 2); + + &.dashlet-selected { + position: relative; + } } .ibo-dashlet--is-inline{ width: $ibo-dashlet--width--is-inline; -} \ No newline at end of file +} diff --git a/css/backoffice/layout/dashboard/_dashboard.scss b/css/backoffice/layout/dashboard/_dashboard.scss index c625ba68d..09f228d6d 100644 --- a/css/backoffice/layout/dashboard/_dashboard.scss +++ b/css/backoffice/layout/dashboard/_dashboard.scss @@ -48,8 +48,11 @@ $ibo-dashboard--grid--elements-spacing-y: $ibo-dashlet--elements-spacing-y !defa &:not(:first-child) { margin-left: 0; } -} -.ibo-dashboard--grid-column.edit-mode { - border: 1px #ccc dashed; -} \ No newline at end of file + &.edit_mode { + margin: 1px; + border: 2px #ccc dashed; + width: 100%; + min-height: 40px; + } +} diff --git a/js/dashboard.js b/js/dashboard.js index bcf20dd00..ab45e27ba 100644 --- a/js/dashboard.js +++ b/js/dashboard.js @@ -46,55 +46,50 @@ $(function() this._make_draggable(); // Make sure we don't click on something we'll regret - $('.itop-dashboard').on('click', 'a', function(e) { e.preventDefault(); }); + $('.itop-dashboard').on('click', 'a', function (e) { + e.preventDefault(); + }); }, - + // called when created, and later when changing options - _refresh: function() - { + _refresh: function () { }, // events bound via _bind are removed automatically // revert other modifications here - _destroy: function() - { + _destroy: function () { this.element - .removeClass('itop-dashboard'); + .removeClass('itop-dashboard'); this.ajax_div.remove(); - $(document).unbind('keyup.dashboard_editor'); + $(document).unbind('keyup.dashboard_editor'); }, // _setOptions is called with a hash of all options that are changing - _setOptions: function() - { + _setOptions: function () { // in 1.9 would use _superApply this._superApply(arguments); }, // _setOption is called for each individual option that is changing - _setOption: function( key, value ) - { + _setOption: function (key, value) { // in 1.9 would use _super this._superApply(arguments); }, - _get_state: function(oMergeInto) - { + _get_state: function (oMergeInto) { var oState = oMergeInto; oState.cells = []; - this.element.find('.layout_cell').each(function() { + this.element.find('.layout_cell').each(function () { var aList = []; - $(this).find(':itop-dashlet').each(function() { + $(this).find('.itop-dashlet').each(function () { var oDashlet = $(this).data('itopDashlet'); - if(oDashlet) - { + if (oDashlet) { var oDashletParams = oDashlet.get_params(); var sId = oDashletParams.dashlet_id; - oState[sId] = oDashletParams; - aList.push({dashlet_id: sId, dashlet_class: oDashletParams.dashlet_class, dashlet_type: oDashletParams.dashlet_type} ); + oState[sId] = oDashletParams; + aList.push({dashlet_id: sId, dashlet_class: oDashletParams.dashlet_class, dashlet_type: oDashletParams.dashlet_type}); } }); - - if (aList.length == 0) - { + + if (aList.length == 0) { oState[0] = {dashlet_id: 0, dashlet_class: 'DashletEmptyCell', dashlet_type: 'DashletEmptyCell'}; aList.push({dashlet_id: 0, dashlet_class: 'DashletEmptyCell', dashlet_type: 'DashletEmptyCell'}); } @@ -105,28 +100,26 @@ $(function() oState.title = this.options.title; oState.auto_reload = this.options.auto_reload; oState.auto_reload_sec = this.options.auto_reload_sec; - + return oState; }, - _make_draggable: function() - { + _make_draggable: function () { var me = this; - this.element.find('.dashlet').draggable({ + this.element.find('.ibo-dashlet').draggable({ revert: 'invalid', appendTo: 'body', zIndex: 9999, distance: 10, - helper: function() { + helper: function () { var oDragItem = $(this).dashlet('get_drag_icon'); return oDragItem; }, - cursorAt: { top: 16, left: 16 } + cursorAt: {top: 16, left: 16} }); - this.element.find('table td').droppable({ - accept: '.dashlet,.dashlet_icon', - drop: function(event, ui) { - $( this ).find( ".placeholder" ).remove(); - var bRefresh = $(this).hasClass('layout_extension'); + this.element.find('.ibo-dashboard--grid-column').droppable({ + accept: '.ibo-dashlet,.dashlet_icon', + drop: function (event, ui) { + $(this).find(".placeholder").remove(); + var bRefresh = true; var oDropped = ui.draggable; - if (oDropped.hasClass('dashlet')) - { + if (oDropped.hasClass('ibo-dashlet')) { // moving around a dashlet oDropped.detach(); oDropped.css({top: 0, left: 0}); @@ -134,20 +127,17 @@ $(function() var oDashlet = ui.draggable.data('itopDashlet'); me.on_dashlet_moved(oDashlet, $(this), bRefresh); - } - else - { + } else { // inserting a new dashlet var sDashletClass = ui.draggable.attr('dashlet_class'); - $('.itop-dashboard').trigger('add_dashlet', {dashlet_class: sDashletClass, container: $(this), refresh: bRefresh }); + $('.itop-dashboard').trigger('add_dashlet', {dashlet_class: sDashletClass, container: $(this), refresh: bRefresh}); } } - }); + }); }, - add_dashlet: function(options) - { + add_dashlet: function (options) { var $container = options.container; - var aDashletsIds = $container.closest("table").find("div.dashlet").map(function(){ + var aDashletsIds = $container.closest(".ibo-dashboard--grid-row").find("div.ibo-dashlet").map(function () { // Note: // - At runtime a unique dashlet ID is generated (see \Dashboard::GetDashletUniqueId) to avoid JS widget collisions // - At design time, the dashlet ID is not touched (same as in the XML datamodel) @@ -164,17 +154,15 @@ $(function() this._get_dashletid_ajax(options, iHighestDashletOrigId + 1); }, // Get the real dashlet ID from the temporary ID - _get_dashletid_ajax: function(options, sTempDashletId) - { + _get_dashletid_ajax: function (options, sTempDashletId) { // Do nothing, meant for overloading }, - add_dashlet_prepare: function(options, sFinalDashletId) - { + add_dashlet_prepare: function (options, sFinalDashletId) { // 1) Create empty divs for the dashlet and its properties // - var oDashlet = $('
'); + var oDashlet = $('
'); oDashlet.appendTo(options.container); - var oDashletProperties = $('
'); + var oDashletProperties = $('
'); oDashletProperties.appendTo($('#dashlet_properties')); // 2) Ajax call to fill the divs with default values @@ -295,76 +283,63 @@ $(function() }); }, // Modified means: at least one change has been applied - mark_as_modified: function() - { + mark_as_modified: function () { this.bModified = true; }, - is_modified: function() - { + is_modified: function () { return this.bModified; }, // Dirty means: at least one change has not been committed yet - is_dirty: function() - { - if ($('#dashboard_editor .ui-layout-east .itop-property-field-modified').size() > 0) - { + is_dirty: function () { + if ($('#dashboard_editor .ui-layout-east .itop-property-field-modified').size() > 0) { return true; - } - else - { + } else { return false; } }, // Force the changes of all the properties being "dirty" - apply_changes: function() - { + apply_changes: function () { $('#dashboard_editor .ui-layout-east .itop-property-field-modified').trigger('apply_changes'); }, - save: function(dialog) - { + save: function (dialog) { var oParams = this._get_state(this.options.submit_parameters); var me = this; - $.post(this.options.submit_to, oParams, function(data){ + $.post(this.options.submit_to, oParams, function (data) { me.ajax_div.html(data); - if(dialog) - { - dialog.dialog( "close" ); - dialog.remove(); - } + if (dialog) { + dialog.dialog("close"); + dialog.remove(); + } }); }, // We need a unique dashlet id, we will get it using an ajax query - _get_dashletid_ajax: function(options, sTempDashletId) - { + _get_dashletid_ajax: function (options, sTempDashletId) { var me = this; var $container = options.container; var oParams = this.options.new_dashletid_parameters; oParams.dashboardid = me.options.dashboard_id; - oParams.iRow = $container.closest("tr").data("dashboard-row-index"); - oParams.iCol = $container.data("dashboard-column-index"); + oParams.iRow = $container.closest(".ibo-dashboard--grid-row").data("dashboard-grid-row-index"); + oParams.iCol = $container.data("dashboard-grid-column-index"); oParams.dashletid = sTempDashletId; - $.post(this.options.new_dashletid_endpoint, oParams, function(data) { - var sFinalDashletId = data; - me.add_dashlet_prepare(options, sFinalDashletId); + $.post(this.options.new_dashletid_endpoint, oParams, function (data) { + me.add_dashlet_prepare(options, data); }); }, - add_dashlet_ajax: function(options, sDashletId) - { + add_dashlet_ajax: function (options, sDashletId) { var oParams = this.options.new_dashlet_parameters; var sDashletClass = options.dashlet_class; oParams.dashlet_class = sDashletClass; oParams.dashlet_id = sDashletId; oParams.dashlet_type = options.dashlet_type; var me = this; - $.post(this.options.render_to, oParams, function(data){ + $.post(this.options.render_to, oParams, function (data) { me.ajax_div.html(data); me.add_dashlet_finalize(options, sDashletId, sDashletClass); me.mark_as_modified(); }); }, - on_dashlet_moved: function(oDashlet, oReceiver, bRefresh) - { + on_dashlet_moved: function (oDashlet, oReceiver, bRefresh) { this._superApply(arguments); this.mark_as_modified(); } diff --git a/js/dashlet.js b/js/dashlet.js index 49257c086..46d1b9e25 100644 --- a/js/dashlet.js +++ b/js/dashlet.js @@ -72,46 +72,38 @@ $(function() // in 1.9 would use _super this._superApply(arguments); }, - select: function() - { + select: function() { this.element.addClass('dashlet-selected'); this.closeBox.fadeIn(500); $('#event_bus').trigger('dashlet-selected', {'dashlet_id': this.options.dashlet_id, 'dashlet_class': this.options.dashlet_class, 'dashlet_type': this.options.dashlet_type}); }, - deselect: function() - { + deselect: function () { this.element.removeClass('dashlet-selected'); this.closeBox.hide(); }, - deselect_all: function() - { - $(':itop-dashlet').each(function(){ - var sId = $(this).attr('id'); + deselect_all: function () { + $('.itop-dashlet').each(function () { var oWidget = $(this).data('itopDashlet'); - if (oWidget) - { + if (oWidget) { oWidget.deselect(); } }); }, - _on_click: function(event) - { + _on_click: function () { this.deselect_all(); this.select(); }, - get_params: function() - { + get_params: function () { var oParams = {}; - var oProperties = $('#dashlet_properties_'+this.options.dashlet_id); - oProperties.find('.itop-property-field').each(function(){ - var oWidget = $(this).data('itopProperty_field'); - if (oWidget == undefined) - { - oWidget = $(this).data('itopSelector_property_field'); - } + var oProperties = $('#dashlet_properties_' + this.options.dashlet_id); + oProperties.find('.itop-property-field').each(function () { + var oWidget = $(this).data('itopProperty_field'); + if (oWidget === undefined) { + oWidget = $(this).data('itopSelector_property_field'); + } var oVal = oWidget._get_committed_value(); oParams[oVal.name] = oVal.value; - }); + }); oParams.dashlet_id = this.options.dashlet_id; oParams.dashlet_class = this.options.dashlet_class; diff --git a/pages/ajax.render.php b/pages/ajax.render.php index cfbf7d17b..9ca402b9f 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -19,6 +19,7 @@ use Combodo\iTop\Application\UI\Layout\ActivityPanel\ActivityEntry\ActivityEntryFactory; use Combodo\iTop\Controller\AjaxRenderController; +use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Renderer\Console\ConsoleFormRenderer; require_once('../approot.inc.php'); @@ -1268,8 +1269,7 @@ EOF $sDashletId = $aParams['attr_dashlet_id']; $aUpdatedProperties = $aParams['updated']; // Code of the changed properties as an array: 'attr_xxx', 'attr_xxy', etc... $aPreviousValues = $aParams['previous_values']; // hash array: 'attr_xxx' => 'old_value' - if (is_subclass_of($sDashletClass, 'Dashlet')) - { + if (is_subclass_of($sDashletClass, 'Dashlet')) { /** @var \Dashlet $oDashlet */ $oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId); $oDashlet->SetDashletType($sDashletType); @@ -1278,8 +1278,7 @@ EOF $aCurrentValues = $aValues; $aUpdatedDecoded = array(); - foreach($aUpdatedProperties as $sProp) - { + foreach ($aUpdatedProperties as $sProp) { $sDecodedProp = str_replace('attr_', '', $sProp); // Remove the attr_ prefix $aCurrentValues[$sDecodedProp] = (isset($aPreviousValues[$sProp]) ? $aPreviousValues[$sProp] : ''); // Set the previous value $aUpdatedDecoded[] = $sDecodedProp; @@ -1289,30 +1288,21 @@ EOF $sPrevClass = get_class($oDashlet); $oDashlet = $oDashlet->Update($aValues, $aUpdatedDecoded); $sNewClass = get_class($oDashlet); - if ($sNewClass != $sPrevClass) - { + if ($sNewClass != $sPrevClass) { $oPage->add_ready_script("$('#dashlet_$sDashletId').dashlet('option', {dashlet_class: '$sNewClass'});"); } - if ($oDashlet->IsRedrawNeeded()) - { - $offset = $oPage->start_capture(); - $oDashlet->DoRender($oPage, true /* bEditMode */, false /* bEnclosingDiv */, $aExtraParams); - $sHtml = addslashes($oPage->end_capture($offset)); - $sHtml = str_replace("\n", '', $sHtml); - $sHtml = str_replace("\r", '', $sHtml); - - $oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); // in ajax web page add_script has the same effect as add_ready_script - // but is executed BEFORE all 'ready_scripts' + if ($oDashlet->IsRedrawNeeded()) { + $oBlock = $oDashlet->DoRender($oPage, true, false, $aExtraParams); + $sHtml = BlockRenderer::RenderBlockTemplates($oBlock); + $oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); } - if ($oDashlet->IsFormRedrawNeeded()) - { + if ($oDashlet->IsFormRedrawNeeded()) { $oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed $oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams)); - $sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.itop-dashboard')); + $sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard')); $sHtml = str_replace("\n", '', $sHtml); $sHtml = str_replace("\r", '', $sHtml); - $oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')"); // in ajax web page add_script has the same effect as add_ready_script // but is executed BEFORE all 'ready_scripts' - // but is executed BEFORE all 'ready_scripts' + $oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')"); } } break; diff --git a/sources/application/UI/Component/Dashlet/DashletContainer.php b/sources/application/UI/Component/Dashlet/DashletContainer.php index fb05cf0c4..3e44c797b 100644 --- a/sources/application/UI/Component/Dashlet/DashletContainer.php +++ b/sources/application/UI/Component/Dashlet/DashletContainer.php @@ -13,4 +13,5 @@ use Combodo\iTop\Application\UI\Layout\UIContentBlock; class DashletContainer extends UIContentBlock { public const BLOCK_CODE = 'ibo-dashlet'; + public const HTML_TEMPLATE_REL_PATH = 'layouts/content-block/layout'; } \ No newline at end of file diff --git a/sources/application/UI/Layout/Dashboard/DashboardColumn.php b/sources/application/UI/Layout/Dashboard/DashboardColumn.php index 48abba953..06e46e0ca 100644 --- a/sources/application/UI/Layout/Dashboard/DashboardColumn.php +++ b/sources/application/UI/Layout/Dashboard/DashboardColumn.php @@ -110,5 +110,22 @@ class DashboardColumn extends UIBlock return $this; } + /** + * @return bool + */ + public function IsLastRow(): bool + { + return $this->bLastRow; + } + /** + * @param bool $bLastRow + * + * @return DashboardColumn + */ + public function SetLastRow(bool $bLastRow): DashboardColumn + { + $this->bLastRow = $bLastRow; + return $this; + } } \ No newline at end of file