mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°2847 - Dashboards edition
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
&.edit_mode {
|
||||
margin: 1px;
|
||||
border: 2px #ccc dashed;
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
141
js/dashboard.js
141
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 = $('<div class="dashlet" id="dashlet_'+sFinalDashletId+'"/>');
|
||||
var oDashlet = $('<div class="dashlet" id="dashlet_' + sFinalDashletId + '"/>');
|
||||
oDashlet.appendTo(options.container);
|
||||
var oDashletProperties = $('<div class="dashlet_properties" id="dashlet_properties_'+sFinalDashletId+'"/>');
|
||||
var oDashletProperties = $('<div class="dashlet_properties" id="dashlet_properties_' + sFinalDashletId + '"/>');
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user