Dashboard re-engineering

SVN:trunk[2782]
This commit is contained in:
Romain Quetiez
2013-06-27 15:21:35 +00:00
parent 721a654152
commit 3ab539e2ba
11 changed files with 981 additions and 369 deletions

View File

@@ -34,15 +34,16 @@ abstract class Dashboard
protected $oDOMNode;
protected $sId;
protected $aCells;
protected $oMetaModel;
public function __construct($sId)
{
$this->sLayoutClass = null;
$this->sLayoutClass = 'DashboardLayoutOneCol';
$this->aCells = array();
$this->oDOMNode = null;
$this->sId = $sId;
}
public function FromXml($sXml)
{
$this->aCells = array(); // reset the content of the dashboard
@@ -50,60 +51,83 @@ abstract class Dashboard
$oDoc = new DOMDocument();
$oDoc->loadXML($sXml);
restore_error_handler();
$this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0);
$oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0);
$this->sLayoutClass = $oLayoutNode->textContent;
$oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0);
$this->sTitle = $oTitleNode->textContent;
$oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0);
$oCellsList = $oCellsNode->getElementsByTagName('cell');
$aCellOrder = array();
$iCellRank = 0;
foreach($oCellsList as $oCellNode)
{
$aDashletList = array();
$oCellRank = $oCellNode->getElementsByTagName('rank')->item(0);
if ($oCellRank)
{
$iCellRank = (float)$oCellRank->textContent;
}
$oDashletsNode = $oCellNode->getElementsByTagName('dashlets')->item(0);
$oDashletList = $oDashletsNode->getElementsByTagName('dashlet');
$iRank = 0;
$aDashletOrder = array();
foreach($oDashletList as $oDomNode)
{
$sDashletClass = $oDomNode->getAttribute('xsi:type');
$oRank = $oDomNode->getElementsByTagName('rank')->item(0);
if ($oRank)
{
$iRank = (float)$oRank->textContent;
}
$sId = $oDomNode->getAttribute('id');
$oNewDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sId);
$oNewDashlet->FromDOMNode($oDomNode);
$aDashletOrder[] = array('rank' => $iRank, 'dashlet' => $oNewDashlet);
}
usort($aDashletOrder, array(get_class($this), 'SortOnRank'));
$aDashletList = array();
foreach($aDashletOrder as $aItem)
{
$aDashletList[] = $aItem['dashlet'];
}
$aCellOrder[] = array('rank' => $iCellRank, 'dashlets' => $aDashletList);
}
usort($aCellOrder, array(get_class($this), 'SortOnRank'));
foreach($aCellOrder as $aItem)
{
$this->aCells[] = $aItem['dashlets'];
}
$this->FromDOMDocument($oDoc);
}
public function FromDOMDocument(DOMDocument $oDoc)
{
$this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0);
if ($oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0))
{
$this->sLayoutClass = $oLayoutNode->textContent;
}
else
{
$this->sLayoutClass = 'DashboardLayoutOneCol';
}
if ($oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0))
{
$this->sTitle = $oTitleNode->textContent;
}
else
{
$this->sTitle = '';
}
if ($oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0))
{
$oCellsList = $oCellsNode->getElementsByTagName('cell');
$aCellOrder = array();
$iCellRank = 0;
foreach($oCellsList as $oCellNode)
{
$aDashletList = array();
$oCellRank = $oCellNode->getElementsByTagName('rank')->item(0);
if ($oCellRank)
{
$iCellRank = (float)$oCellRank->textContent;
}
$oDashletsNode = $oCellNode->getElementsByTagName('dashlets')->item(0);
{
$oDashletList = $oDashletsNode->getElementsByTagName('dashlet');
$iRank = 0;
$aDashletOrder = array();
foreach($oDashletList as $oDomNode)
{
$sDashletClass = $oDomNode->getAttribute('xsi:type');
$oRank = $oDomNode->getElementsByTagName('rank')->item(0);
if ($oRank)
{
$iRank = (float)$oRank->textContent;
}
$sId = $oDomNode->getAttribute('id');
$oNewDashlet = new $sDashletClass($this->oMetaModel, $sId);
$oNewDashlet->FromDOMNode($oDomNode);
$aDashletOrder[] = array('rank' => $iRank, 'dashlet' => $oNewDashlet);
}
usort($aDashletOrder, array(get_class($this), 'SortOnRank'));
$aDashletList = array();
foreach($aDashletOrder as $aItem)
{
$aDashletList[] = $aItem['dashlet'];
}
$aCellOrder[] = array('rank' => $iCellRank, 'dashlets' => $aDashletList);
}
}
usort($aCellOrder, array(get_class($this), 'SortOnRank'));
foreach($aCellOrder as $aItem)
{
$this->aCells[] = $aItem['dashlets'];
}
}
else
{
$this->aCells = array();
}
}
static function SortOnRank($aItem1, $aItem2)
{
return ($aItem1['rank'] > $aItem2['rank']) ? +1 : -1;
@@ -133,14 +157,24 @@ abstract class Dashboard
$oMainNode->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
$oDoc->appendChild($oMainNode);
$this->ToDOMNode($oMainNode);
$sXml = $oDoc->saveXML();
return $sXml;
}
public function ToDOMNode($oDefinition)
{
$oDoc = $oDefinition->ownerDocument;
$oNode = $oDoc->createElement('layout', $this->sLayoutClass);
$oMainNode->appendChild($oNode);
$oDefinition->appendChild($oNode);
$oNode = $oDoc->createElement('title', $this->sTitle);
$oMainNode->appendChild($oNode);
$oDefinition->appendChild($oNode);
$oCellsNode = $oDoc->createElement('cells');
$oMainNode->appendChild($oCellsNode);
$oDefinition->appendChild($oCellsNode);
$iCellRank = 0;
foreach ($this->aCells as $aCell)
@@ -167,11 +201,9 @@ abstract class Dashboard
$oDashlet->ToDOMNode($oNode);
}
}
$sXml = $oDoc->saveXML();
return $sXml;
}
public function FromParams($aParams)
{
$this->sLayoutClass = $aParams['layout_class'];
@@ -184,7 +216,7 @@ abstract class Dashboard
{
$sDashletClass = $aDashletParams['dashlet_class'];
$sId = $aDashletParams['dashlet_id'];
$oNewDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sId);
$oNewDashlet = new $sDashletClass($this->oMetaModel, $sId);
$oForm = $oNewDashlet->GetForm();
$oForm->SetParamsContainer($sId);
@@ -197,7 +229,7 @@ abstract class Dashboard
}
}
public function Save()
{
@@ -267,24 +299,43 @@ abstract class Dashboard
$oPage->add('</div>');
$oForm = new DesignerForm();
$oField = new DesignerHiddenField('dashboard_id', '', $this->sId);
$oForm->AddField($oField);
$oField = new DesignerLongTextField('dashboard_title', Dict::S('UI:DashboardEdit:DashboardTitle'), $this->sTitle);
$oForm->AddField($oField);
$this->SetFormParams($oForm);
$oForm->RenderAsPropertySheet($oPage, false, ':itop-dashboard');
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
$oPage->add('</div>');
$oPage->add_ready_script(
<<<EOF
$('#select_layout').buttonset();
$('#select_layout input').click( function() {
var sLayoutClass = $(this).val();
$(':itop-dashboard').dashboard('option', {layout_class: sLayoutClass});
} );
$('#row_attr_dashboard_title').property_field('option', {parent_selector: ':itop-dashboard', auto_apply: false, 'do_apply': function() {
var sTitle = $('#attr_dashboard_title').val();
$(':itop-dashboard').dashboard('option', {title: sTitle});
return true;
}
$('#select_dashlet').droppable({
accept: '.dashlet',
drop: function(event, ui) {
$( this ).find( ".placeholder" ).remove();
var oDashlet = ui.draggable.data('itopDashlet');
oDashlet._remove_dashlet();
},
});
$('#event_bus').bind('dashlet-selected', function(event, data){
var sDashletId = data.dashlet_id;
var sPropId = 'dashlet_properties_'+sDashletId;
$('.dashlet_properties').each(function() {
var sId = $(this).attr('id');
var bShow = (sId == sPropId);
if (bShow)
{
$(this).show();
}
else
{
$(this).hide();
}
});
});
EOF
);
@@ -319,7 +370,6 @@ EOF
$oPage->add('</div>');
$oPage->add_ready_script("$('.dashlet_icon').draggable({helper: 'clone', appendTo: 'body', zIndex: 10000, revert:'invalid'});");
$oPage->add_ready_script("$('.layout_cell').droppable({accept:'.dashlet_icon', hoverClass:'dragHover'});");
}
public function RenderDashletsProperties($oPage)
@@ -339,7 +389,7 @@ EOF
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$sId.'" style="display:none">');
$oForm = $oDashlet->GetForm();
$this->SetFormParams($oForm);
$oForm->RenderAsPropertySheet($oPage, false, ':itop-dashboard');
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
$oPage->add('</div>');
}
}
@@ -368,11 +418,12 @@ EOF
class RuntimeDashboard extends Dashboard
{
protected $bCustomized;
public function __construct($sId)
{
parent::__construct($sId);
$this->bCustomized = false;
$this->oMetaModel = new ModelReflectionRuntime();
}
public function SetCustomFlag($bCustomized)
@@ -482,7 +533,28 @@ EOF
);
}
}
public function RenderProperties($oPage)
{
parent::RenderProperties($oPage);
$oPage->add_ready_script(
<<<EOF
$('#select_layout input').click( function() {
var sLayoutClass = $(this).val();
$('.itop-dashboard').runtimedashboard('option', {layout_class: sLayoutClass});
} );
$('#row_attr_dashboard_title').property_field('option', {parent_selector: '.itop-dashboard', auto_apply: false, 'do_apply': function() {
var sTitle = $('#attr_dashboard_title').val();
$('.itop-dashboard').runtimedashboard('option', {title: sTitle});
return true;
}
});
EOF
);
}
public function RenderEditor($oPage)
{
$oPage->add('<div id="dashboard_editor">');
@@ -521,7 +593,7 @@ $('#dashboard_editor').dialog({
title: '$sDialogTitle',
buttons: [
{ text: "$sOkButtonLabel", click: function() {
var oDashboard = $(':itop-dashboard').data('itopDashboard');
var oDashboard = $('.itop-dashboard').data('itopRuntimedashboard');
if (oDashboard.is_dirty())
{
if (!confirm('$sAutoApplyConfirmationMessage'))
@@ -537,7 +609,7 @@ $('#dashboard_editor').dialog({
oDashboard.save();
} },
{ text: "$sCancelButtonLabel", click: function() {
var oDashboard = $(':itop-dashboard').data('itopDashboard');
var oDashboard = $('.itop-dashboard').data('itopRuntimedashboard');
if (oDashboard.is_modified())
{
if (!confirm('$sCancelConfirmationMessage'))
@@ -553,40 +625,13 @@ $('#dashboard_editor').dialog({
close: function() { $(this).remove(); }
});
$('#dashboard_editor .ui-layout-center').dashboard({
$('#dashboard_editor .ui-layout-center').runtimedashboard({
dashboard_id: '$sId', layout_class: '$sLayoutClass', title: '$sTitle',
submit_to: '$sUrl', submit_parameters: {operation: 'save_dashboard'},
render_to: '$sUrl', render_parameters: {operation: 'render_dashboard'},
new_dashlet_parameters: {operation: 'new_dashlet'}
});
$('#select_dashlet').droppable({
accept: '.dashlet',
drop: function(event, ui) {
$( this ).find( ".placeholder" ).remove();
var oDashlet = ui.draggable;
oDashlet.remove();
},
});
$('#event_bus').bind('dashlet-selected', function(event, data){
var sDashletId = data.dashlet_id;
var sPropId = 'dashlet_properties_'+sDashletId;
$('.dashlet_properties').each(function() {
var sId = $(this).attr('id');
var bShow = (sId == sPropId);
if (bShow)
{
$(this).show();
}
else
{
$(this).hide();
}
});
});
dashboard_prop_size = GetUserPreference('dashboard_prop_size', 350);
$('#dashboard_editor').layout({
east: {
@@ -607,7 +652,7 @@ $('#dashboard_editor').layout({
window.onbeforeunload = function() {
if (!window.bLeavingOnUserAction)
{
var oDashboard = $(':itop-dashboard').data('itopDashboard');
var oDashboard = $('.itop-dashboard').data('itopRuntimedashboard');
if (oDashboard)
{
if (oDashboard.is_dirty())
@@ -688,7 +733,7 @@ EOF
foreach($aDashlets as $sDashletClass => $aDashletInfo)
{
$oSubForm = new DesignerForm();
$oDashlet = new $sDashletClass(new ModelReflectionRuntime(), 0);
$oDashlet = new $sDashletClass($this->oMetaModel, 0);
$oDashlet->GetPropertiesFieldsFromOQL($oSubForm, $sOQL);
$oSelectorField->AddSubForm($oSubForm, $aDashletInfo['label'], $aDashletInfo['class']);

View File

@@ -107,14 +107,16 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
$oPage->add('<table style="width:100%"><tbody>');
$iCellIdx = 0;
$fColSize = 100 / $this->iNbCols;
$sStyle = $bEditMode ? 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode"' : 'style="width: '.$fColSize.'%;" class="dashboard"';
$sStyle = $bEditMode ? 'border: 1px #ccc dashed; width:'.$fColSize.'%;' : 'width: '.$fColSize.'%;';
$sClass = $bEditMode ? 'layout_cell edit_mode' : 'dashboard';
$iNbRows = ceil(count($aCells) / $this->iNbCols);
for($iRows = 0; $iRows < $iNbRows; $iRows++)
{
$oPage->add('<tr>');
for($iCols = 0; $iCols < $this->iNbCols; $iCols++)
{
$oPage->add("<td $sStyle>");
$sCellClass = ($iRows == $iNbRows-1) ? $sClass.' layout_last_used_rank' : $sClass;
$oPage->add("<td style=\"$sStyle\" class=\"$sCellClass\" data-dashboard-cell-index=\"$iCellIdx\">");
if (array_key_exists($iCellIdx, $aCells))
{
$aDashlets = $aCells[$iCellIdx];
@@ -144,7 +146,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
}
if ($bEditMode) // Add one row for extensibility
{
$sStyle = 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode layout_extension"';
$sStyle = 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode layout_extension" data-dashboard-cell-index="'.$iCellIdx.'"';
$oPage->add('<tr>');
for($iCols = 0; $iCols < $this->iNbCols; $iCols++)
{

View File

@@ -87,11 +87,10 @@ abstract class Dashlet
{
foreach ($this->aProperties as $sProperty => $value)
{
$this->oDOMNode = $oDOMNode->getElementsByTagName($sProperty)->item(0);
if ($this->oDOMNode != null)
$oPropNode = $oDOMNode->getElementsByTagName($sProperty)->item(0);
if ($oPropNode != null)
{
$newvalue = $this->Str2Prop($sProperty, $this->oDOMNode->textContent);
$this->aProperties[$sProperty] = $newvalue;
$this->aProperties[$sProperty] = $this->PropertyFromDOMNode($oPropNode, $sProperty);
}
}
}
@@ -100,12 +99,26 @@ abstract class Dashlet
{
foreach ($this->aProperties as $sProperty => $value)
{
$sXmlValue = $this->Prop2Str($value);
$oPropNode = $oDOMNode->ownerDocument->createElement($sProperty, $sXmlValue);
$oPropNode = $oDOMNode->ownerDocument->createElement($sProperty);
$oDOMNode->appendChild($oPropNode);
$this->PropertyToDOMNode($oPropNode, $sProperty, $value);
}
}
protected function PropertyFromDOMNode($oDOMNode, $sProperty)
{
$res = $this->Str2Prop($sProperty, $oDOMNode->textContent);
return $res;
}
protected function PropertyToDOMNode($oDOMNode, $sProperty, $value)
{
$sXmlValue = $this->Prop2Str($value);
$oTextNode = $oDOMNode->ownerDocument->createTextNode($sXmlValue);
$oDOMNode->appendChild($oTextNode);
}
public function FromXml($sXml)
{
$oDomDoc = new DOMDocument('1.0', 'UTF-8');
@@ -139,10 +152,24 @@ abstract class Dashlet
$oPage->add('<div class="'.$sCSSClasses.'">');
}
}
else
{
foreach ($this->aCSSClasses as $sCSSClass)
{
$oPage->add_ready_script("$('#dashlet_".$sId."').addClass('$sCSSClass');");
}
}
try
{
$this->Render($oPage, $bEditMode, $aExtraParams);
if (get_class($this->oModelReflection) == 'ModelReflectionRuntime')
{
$this->Render($oPage, $bEditMode, $aExtraParams);
}
else
{
$this->RenderNoData($oPage, $bEditMode, $aExtraParams);
}
}
catch(UnknownClassOqlException $e)
{
@@ -195,6 +222,12 @@ EOF
}
abstract public function Render($oPage, $bEditMode = false, $aExtraParams = array());
/* Rendering without the real data */
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$this->Render($oPage, $bEditMode, $aExtraParams);
}
abstract public function GetPropertiesFields(DesignerForm $oForm);
@@ -312,7 +345,7 @@ class DashletPlainText extends Dashlet
$sText = htmlentities($this->aProperties['text'], ENT_QUOTES, 'UTF-8');
$sId = 'plaintext_'.($bEditMode? 'edit_' : '').$this->sId;
$oPage->add('<div id='.$sId.'" class="dashlet-content">'.$sText.'</div>');
$oPage->add('<div id="'.$sId.'" class="dashlet-content">'.$sText.'</div>');
}
public function GetPropertiesFields(DesignerForm $oForm)
@@ -365,6 +398,27 @@ class DashletObjectList extends Dashlet
$oPage->add('</div>');
}
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
$sQuery = $this->aProperties['query'];
$sShowMenu = $this->aProperties['menu'] ? '1' : '0';
$oPage->add('<div class="dashlet-content">');
$sHtmlTitle = htmlentities($this->oModelReflection->DictString($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
if ($sHtmlTitle != '')
{
$oPage->add('<h1>'.$sHtmlTitle.'</h1>');
}
$oQuery = $this->oModelReflection->GetQuery($sQuery);
$sClass = $oQuery->GetClass();
$oPage->add('<div id="block_fake_'.$this->sId.'" class="display_block">');
$oPage->p(Dict::S('UI:NoObjectToDisplay'));
$oPage->p('<a href="">'.Dict::Format('UI:ClickToCreateNew', $this->oModelReflection->GetName($sClass)).'</a>');
$oPage->add('</div>');
$oPage->add('</div>');
}
public function GetPropertiesFields(DesignerForm $oForm)
{
$oField = new DesignerTextField('title', Dict::S('UI:DashletObjectList:Prop-Title'), $this->aProperties['title']);
@@ -531,10 +585,74 @@ abstract class DashletGroupBy extends Dashlet
}
}
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
$sQuery = $this->aProperties['query'];
$sGroupBy = $this->aProperties['group_by'];
$sStyle = $this->aProperties['style'];
$oQuery = $this->oModelReflection->GetQuery($sQuery);
$sClass = $oQuery->GetClass();
$aDisplayValues = array();
$sAttLabel = '';
if ($this->oModelReflection->IsValidAttCode($sClass, $sGroupBy))
{
$sAttLabel = $this->oModelReflection->GetLabel($sClass, $sGroupBy);
$aAllowed = $this->oModelReflection->GetAllowedValues_att($sClass, $sGroupBy);
if ($aAllowed) // null for non enums
{
$iTotal = 0;
foreach ($aAllowed as $sValue => $sValueLabel)
{
$iCount = (int) rand(2, 100);
$iTotal += $iCount;
$aDisplayValues[] = array(
'label' => $sValueLabel,
'count' => $iCount
);
}
}
}
else
{
$oPage->add('<div class="dashlet-content">Sorry, grouping by date/time cannot be viewed in the designer</div>');
}
$oPage->add('<div class="dashlet-content">');
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$oPage->add('<div id="'.$sBlockId.'" class="display_block">');
$oPage->add('<p>'.Dict::Format('UI:Pagination:HeaderNoSelection', $iTotal).'</p>');
$oPage->add('<table class="listResults">');
$oPage->add('<thead>');
$oPage->add('<tr>');
$oPage->add('<th class="header" title="">'.$sAttLabel.'</th>');
$oPage->add('<th class="header" title="'.Dict::S('UI:GroupBy:Count+').'">'.Dict::S('UI:GroupBy:Count').'</th>');
$oPage->add('</tr>');
$oPage->add('</thead>');
$oPage->add('<tbody>');
foreach($aDisplayValues as $aDisplayData)
{
$oPage->add('<tr class="even">');
$oPage->add('<td class=""><span title="Active">'.$aDisplayData['label'].'</span></td>');
$oPage->add('<td class=""><a href="">'.$aDisplayData['count'].'</a></td>');
$oPage->add('</tr>');
}
$oPage->add('</tbody>');
$oPage->add('</table>');
$oPage->add('</div>');
$oPage->add('</div>');
}
protected function GetGroupByOptions($sOql)
{
$oSearch = DBObjectSearch::FromOQL($sOql);
$sClass = $oSearch->GetClass();
$oQuery = $this->oModelReflection->GetQuery($sOql);
$sClass = $oQuery->GetClass();
$aGroupBy = array();
foreach($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType)
{
@@ -604,11 +722,11 @@ abstract class DashletGroupBy extends Dashlet
try
{
$sCurrQuery = $aValues['query'];
$oCurrSearch = DBObjectSearch::FromOQL($sCurrQuery);
$oCurrSearch = $this->oModelReflection->GetQuery($sCurrQuery);
$sCurrClass = $oCurrSearch->GetClass();
$sPrevQuery = $this->aProperties['query'];
$oPrevSearch = DBObjectSearch::FromOQL($sPrevQuery);
$oPrevSearch = $this->oModelReflection->GetQuery($sPrevQuery);
$sPrevClass = $oPrevSearch->GetClass();
if ($sCurrClass != $sPrevClass)
@@ -631,15 +749,15 @@ abstract class DashletGroupBy extends Dashlet
{
// Style changed, mutate to the specified type of chart
case 'pie':
$oDashlet = new DashletGroupByPie($this->sId);
$oDashlet = new DashletGroupByPie($this->oModelReflection, $this->sId);
break;
case 'bars':
$oDashlet = new DashletGroupByBars($this->sId);
$oDashlet = new DashletGroupByBars($this->oModelReflection, $this->sId);
break;
case 'table':
$oDashlet = new DashletGroupByTable($this->sId);
$oDashlet = new DashletGroupByTable($this->oModelReflection, $this->sId);
break;
}
$oDashlet->FromParams($aValues);
@@ -756,9 +874,8 @@ class DashletHeaderStatic extends Dashlet
{
parent::__construct($oModelReflection, $sId);
$this->aProperties['title'] = Dict::S('UI:DashletHeaderStatic:Prop-Title:Default');
$sIcon = $this->oModelReflection->GetClassIcon('Contact', false);
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIcon);
$this->aProperties['icon'] = $sIcon;
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$this->aProperties['icon'] = $oIconSelect->GetDefaultValue('Contact');
}
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
@@ -766,13 +883,14 @@ class DashletHeaderStatic extends Dashlet
$sTitle = $this->aProperties['title'];
$sIcon = $this->aProperties['icon'];
$sIconPath = utils::GetAbsoluteUrlModulesRoot().$sIcon;
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$sIconPath = $oIconSelect->MakeFileUrl($sIcon);
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="main_header">');
$oPage->add('<img src="'.$sIconPath.'">');
$oPage->add('<h1>'.Dict::S($sTitle).'</h1>');
$oPage->add('<h1>'.$this->oModelReflection->DictString($sTitle).'</h1>');
$oPage->add('</div>');
$oPage->add('</div>');
@@ -783,18 +901,36 @@ class DashletHeaderStatic extends Dashlet
$oField = new DesignerTextField('title', Dict::S('UI:DashletHeaderStatic:Prop-Title'), $this->aProperties['title']);
$oForm->AddField($oField);
$oField = new DesignerIconSelectionField('icon', Dict::S('UI:DashletHeaderStatic:Prop-Icon'), $this->aProperties['icon']);
$aAllIcons = self::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment());
ksort($aAllIcons);
$aValues = array();
foreach($aAllIcons as $sFilePath)
{
$aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
}
$oField->SetAllowedValues($aValues);
$oField = $this->oModelReflection->GetIconSelectionField('icon', Dict::S('UI:DashletHeaderStatic:Prop-Icon'), $this->aProperties['icon']);
$oForm->AddField($oField);
}
protected function PropertyFromDOMNode($oDOMNode, $sProperty)
{
if ($sProperty == 'icon')
{
$oIconField = $this->oModelReflection->GetIconSelectionField('icon');
return $oIconField->ValueFromDOMNode($oDOMNode);
}
else
{
return parent::PropertyFromDOMNode($oDOMNode, $sProperty);
}
}
protected function PropertyToDOMNode($oDOMNode, $sProperty, $value)
{
if ($sProperty == 'icon')
{
$oIconField = $this->oModelReflection->GetIconSelectionField('icon');
$oIconField->ValueToDOMNode($oDOMNode, $value);
}
else
{
parent::PropertyToDOMNode($oDOMNode, $sProperty, $value);
}
}
static public function GetInfo()
{
return array(
@@ -803,30 +939,6 @@ class DashletHeaderStatic extends Dashlet
'description' => Dict::S('UI:DashletHeaderStatic:Description'),
);
}
static public function FindIcons($sBaseDir, $sDir = '')
{
$aResult = array();
// Populate automatically the list of icon files
if ($hDir = @opendir($sBaseDir.'/'.$sDir))
{
while (($sFile = readdir($hDir)) !== false)
{
$aMatches = array();
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
{
$sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
$aResult = array_merge($aResult, self::FindIcons($sBaseDir, $sDirSubPath));
}
if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
{
$aResult[$sFile.'_'.$sDir] = $sDir.'/'.$sFile;
}
}
closedir($hDir);
}
return $aResult;
}
}
@@ -836,28 +948,27 @@ class DashletHeaderDynamic extends Dashlet
{
parent::__construct($oModelReflection, $sId);
$this->aProperties['title'] = Dict::S('UI:DashletHeaderDynamic:Prop-Title:Default');
$sIcon = $this->oModelReflection->GetClassIcon('Contact', false);
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIcon);
$this->aProperties['icon'] = $sIcon;
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$this->aProperties['icon'] = $oIconSelect->GetDefaultValue('Contact');
$this->aProperties['subtitle'] = Dict::S('UI:DashletHeaderDynamic:Prop-Subtitle:Default');
$this->aProperties['query'] = 'SELECT Contact';
$this->aProperties['group_by'] = 'status';
$this->aProperties['values'] = array('active', 'inactive');
}
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
protected function GetValues()
{
$sTitle = $this->aProperties['title'];
$sIcon = $this->aProperties['icon'];
$sSubtitle = $this->aProperties['subtitle'];
$sQuery = $this->aProperties['query'];
$sGroupBy = $this->aProperties['group_by'];
$aValues = $this->aProperties['values'];
$oFilter = DBObjectSearch::FromOQL($sQuery);
$sClass = $oFilter->GetClass();
if (empty($aValues))
{
$aValues = array();
}
$sIconPath = utils::GetAbsoluteUrlModulesRoot().$sIcon;
$oQuery = $this->oModelReflection->GetQuery($sQuery);
$sClass = $oQuery->GetClass();
if ($this->oModelReflection->IsValidAttCode($sClass, $sGroupBy))
{
@@ -870,6 +981,21 @@ class DashletHeaderDynamic extends Dashlet
}
}
}
return $aValues;
}
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
$sIcon = $this->aProperties['icon'];
$sSubtitle = $this->aProperties['subtitle'];
$sQuery = $this->aProperties['query'];
$sGroupBy = $this->aProperties['group_by'];
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$sIconPath = $oIconSelect->MakeFileUrl($sIcon);
$aValues = $this->GetValues();
if (count($aValues) > 0)
{
// Stats grouped by <group_by>
@@ -897,6 +1023,7 @@ class DashletHeaderDynamic extends Dashlet
$oPage->add('<img src="'.$sIconPath.'">');
$oFilter = DBObjectSearch::FromOQL($sQuery);
$oBlock = new DisplayBlock($oFilter, 'summary');
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$oBlock->Display($oPage, $sBlockId, $aExtraParams);
@@ -905,20 +1032,78 @@ class DashletHeaderDynamic extends Dashlet
$oPage->add('</div>');
}
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
$sIcon = $this->aProperties['icon'];
$sSubtitle = $this->aProperties['subtitle'];
$sQuery = $this->aProperties['query'];
$sGroupBy = $this->aProperties['group_by'];
$aValues = $this->aProperties['values'];
$oQuery = $this->oModelReflection->GetQuery($sQuery);
$sClass = $oQuery->GetClass();
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$sIconPath = $oIconSelect->MakeFileUrl($sIcon);
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="main_header">');
$oPage->add('<img src="'.$sIconPath.'">');
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$iTotal = 0;
$aValues = $this->GetValues();
if (count($aValues) > 0)
{
// Stats grouped by <group_by>
}
else
{
// Simple stats
}
$oPage->add('<div class="display_block" id="'.$sBlockId.'">');
$oPage->add('<div class="summary-details">');
$oPage->add('<table><tbody>');
$oPage->add('<tr>');
foreach ($aValues as $sValue)
{
$sValueLabel = $this->oModelReflection->GetValueLabel($sClass, $sGroupBy, $sValue);
$oPage->add(' <th>'.$sValueLabel.'</th>');
}
$oPage->add('</tr>');
$oPage->add('<tr>');
foreach ($aValues as $sValue)
{
$iCount = (int) rand(2, 100);
$iTotal += $iCount;
$oPage->add(' <td>'.$iCount.'</td>');
}
$oPage->add('</tr>');
$oPage->add('</tbody></table>');
$oPage->add('</div>');
$sTitle = $this->oModelReflection->DictString($sTitle);
$sSubtitle = $this->oModelReflection->DictFormat($sSubtitle, $iTotal);
// $sSubtitle = "original: $sSubtitle, S:".$this->oModelReflection->DictString($sSubtitle).", Format: '".$this->oModelReflection->DictFormat($sSubtitle, $iTotal)."'";
$oPage->add('<h1>'.$sTitle.'</h1>');
$oPage->add('<a href="" class="summary">'.$sSubtitle.'</a>');
$oPage->add('</div>');
$oPage->add('</div>');
$oPage->add('</div>');
}
public function GetPropertiesFields(DesignerForm $oForm)
{
$oField = new DesignerTextField('title', Dict::S('UI:DashletHeaderDynamic:Prop-Title'), $this->aProperties['title']);
$oForm->AddField($oField);
$oField = new DesignerIconSelectionField('icon', Dict::S('UI:DashletHeaderDynamic:Prop-Icon'), $this->aProperties['icon']);
$aAllIcons = DashletHeaderStatic::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment());
ksort($aAllIcons);
$aValues = array();
foreach($aAllIcons as $sFilePath)
{
$aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
}
$oField->SetAllowedValues($aValues);
$oField = $this->oModelReflection->GetIconSelectionField('icon', Dict::S('UI:DashletHeaderDynamic:Prop-Icon'), $this->aProperties['icon']);
$oForm->AddField($oField);
$oField = new DesignerTextField('subtitle', Dict::S('UI:DashletHeaderDynamic:Prop-Subtitle'), $this->aProperties['subtitle']);
@@ -931,8 +1116,8 @@ class DashletHeaderDynamic extends Dashlet
try
{
// Group by field: build the list of possible values (attribute codes + ...)
$oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
$sClass = $oSearch->GetClass();
$oQuery = $this->oModelReflection->GetQuery($this->aProperties['query']);
$sClass = $oQuery->GetClass();
$aGroupBy = array();
foreach($this->oModelReflection->ListAttributes($sClass, 'AttributeEnum,AttributeFinalClass') as $sAttCode => $sAttType)
{
@@ -975,11 +1160,11 @@ class DashletHeaderDynamic extends Dashlet
try
{
$sCurrQuery = $aValues['query'];
$oCurrSearch = DBObjectSearch::FromOQL($sCurrQuery);
$oCurrSearch = $this->oModelReflection->GetQuery($sCurrQuery);
$sCurrClass = $oCurrSearch->GetClass();
$sPrevQuery = $this->aProperties['query'];
$oPrevSearch = DBObjectSearch::FromOQL($sPrevQuery);
$oPrevSearch = $this->oModelReflection->GetQuery($sPrevQuery);
$sPrevClass = $oPrevSearch->GetClass();
if ($sCurrClass != $sPrevClass)
@@ -1003,6 +1188,32 @@ class DashletHeaderDynamic extends Dashlet
return parent::Update($aValues, $aUpdatedFields);
}
protected function PropertyFromDOMNode($oDOMNode, $sProperty)
{
if ($sProperty == 'icon')
{
$oIconField = $this->oModelReflection->GetIconSelectionField('icon');
return $oIconField->ValueFromDOMNode($oDOMNode);
}
else
{
return parent::PropertyFromDOMNode($oDOMNode, $sProperty);
}
}
protected function PropertyToDOMNode($oDOMNode, $sProperty, $value)
{
if ($sProperty == 'icon')
{
$oIconField = $this->oModelReflection->GetIconSelectionField('icon');
$oIconField->ValueToDOMNode($oDOMNode, $value);
}
else
{
parent::PropertyToDOMNode($oDOMNode, $sProperty, $value);
}
}
static public function GetInfo()
{
return array(
@@ -1023,7 +1234,7 @@ class DashletBadge extends Dashlet
$this->aCSSClasses[] = 'dashlet-inline';
$this->aCSSClasses[] = 'dashlet-badge';
}
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$sClass = $this->aProperties['class'];
@@ -1039,52 +1250,63 @@ class DashletBadge extends Dashlet
$oBlock->Display($oPage, $sBlockId, $aExtraParams);
$oPage->add('</div>');
if ($bEditMode)
{
// Since the container div is not rendered the same way in edit mode, add the 'inline' style to it
$oPage->add_ready_script("$('#dashlet_".$this->sId."').addClass('dashlet-inline');");
}
}
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$sClass = $this->aProperties['class'];
$sIconUrl = $this->oModelReflection->GetClassIcon($sClass, false);
$sClassLabel = $this->oModelReflection->GetName($sClass);
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div id="block_fake_'.$this->sId.'" class="display_block">');
$oPage->add('<p>');
$oPage->add(' <a class="actions" href=""><img src="'.$sIconUrl.'" style="vertical-align:middle;float;left;margin-right:10px;border:0;">'.$sClassLabel.': 947</a>');
$oPage->add('</p>');
$oPage->add('<p>');
$oPage->add(' <a href="">'.Dict::Format('UI:ClickToCreateNew', $sClassLabel).'</a>');
$oPage->add(' <br/>');
$oPage->add(' <a href="http://localhost/trunk/pages/UI.php?operation=search_form&amp;class=Server&amp;c[menu]=WelcomeMenuPage">Search for Server objects</a>');
$oPage->add('</p>');
$oPage->add('</div>');
$oPage->add('</div>');
}
static protected $aClassList = null;
public function GetPropertiesFields(DesignerForm $oForm)
{
$oClassesSet = new ValueSetEnumClasses('bizmodel', array());
$aClasses = $oClassesSet->GetValues(array());
$aLinkClasses = array();
foreach($this->oModelReflection->GetClasses('bizmodel') as $sClass)
{
foreach($this->oModelReflection->ListAttributes($sClass, 'AttributeLinkedSetIndirect') as $sAttCode => $sAttType)
{
$sLinkedClass = $this->oModelReflection->GetAttributeProperty($sClass, $sAttCode, 'linked_class');
if ($sLinkedClass != null)
{
$aLinkClasses[$sLinkedClass] = true;
}
}
}
$oField = new DesignerIconSelectionField('class', Dict::S('UI:DashletBadge:Prop-Class'), $this->aProperties['class']);
ksort($aClasses);
$aValues = array();
foreach($aClasses as $sClass => $sClass)
if (is_null(self::$aClassList))
{
if (!array_key_exists($sClass, $aLinkClasses))
// Cache the ordered list of classes (ordered on the label)
// (has a significant impact when editing a page with lots of badges)
//
$aClasses = array();
foreach($this->oModelReflection->GetClasses('bizmodel', true /*exclude links*/) as $sClass)
{
$aClasses[$sClass] = $this->oModelReflection->GetName($sClass);
}
asort($aClasses);
self::$aClassList = array();
foreach($aClasses as $sClass => $sLabel)
{
$sIconUrl = $this->oModelReflection->GetClassIcon($sClass, false);
$sIconFilePath = str_replace(utils::GetAbsoluteUrlAppRoot(), APPROOT, $sIconUrl);
if (($sIconUrl == '') || !file_exists($sIconFilePath))
if ($sIconUrl == '')
{
// The icon does not exist, leet's use a transparent one of the same size.
// The icon does not exist, let's use a transparent one of the same size.
$sIconUrl = utils::GetAbsoluteUrlAppRoot().'images/transparent_32_32.png';
}
$aValues[] = array('value' => $sClass, 'label' => $this->oModelReflection->GetName($sClass), 'icon' => $sIconUrl);
self::$aClassList[] = array('value' => $sClass, 'label' => $sLabel, 'icon' => $sIconUrl);
}
}
$oField->SetAllowedValues($aValues);
$oField = new DesignerIconSelectionField('class', Dict::S('UI:DashletBadge:Prop-Class'), $this->aProperties['class']);
$oField->SetAllowedValues(self::$aClassList);
$oForm->AddField($oField);
}

View File

@@ -828,19 +828,26 @@ class DesignerHiddenField extends DesignerFormField
class DesignerIconSelectionField extends DesignerFormField
{
protected $sUploadUrl;
protected $aAllowedValues;
public function __construct($sCode, $sLabel = '', $defaultValue = '')
{
parent::__construct($sCode, $sLabel, $defaultValue);
$this->bAutoApply = true;
$this->sUploadUrl = null;
}
public function SetAllowedValues($aAllowedValues)
{
$this->aAllowedValues = $aAllowedValues;
}
public function EnableUpload($sIconUploadUrl)
{
$this->sUploadUrl = $sIconUploadUrl;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
$sId = $this->oForm->GetFieldId($this->sCode);
@@ -868,6 +875,71 @@ EOF
}
}
class RunTimeIconSelectionField extends DesignerIconSelectionField
{
public function __construct($sCode, $sLabel = '', $defaultValue = '')
{
parent::__construct($sCode, $sLabel, $defaultValue);
$aAllIcons = self::FindIconsOnDisk(APPROOT.'env-'.utils::GetCurrentEnvironment());
ksort($aAllIcons);
$aValues = array();
foreach($aAllIcons as $sFilePath)
{
$aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
}
$this->SetAllowedValues($aValues);
}
static protected function FindIconsOnDisk($sBaseDir, $sDir = '')
{
$aResult = array();
// Populate automatically the list of icon files
if ($hDir = @opendir($sBaseDir.'/'.$sDir))
{
while (($sFile = readdir($hDir)) !== false)
{
$aMatches = array();
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
{
$sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
$aResult = array_merge($aResult, self::FindIconsOnDisk($sBaseDir, $sDirSubPath));
}
if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
{
$aResult[$sFile.'_'.$sDir] = $sDir.'/'.$sFile;
}
}
closedir($hDir);
}
return $aResult;
}
public function ValueFromDOMNode($oDOMNode)
{
return $oDOMNode->textContent;
}
public function ValueToDOMNode($oDOMNode, $value)
{
$oTextNode = $oDOMNode->ownerDocument->createTextNode($value);
$oDOMNode->appendChild($oTextNode);
}
public function MakeFileUrl($value)
{
return utils::GetAbsoluteUrlModulesRoot().$value;
}
public function GetDefaultValue($sClass = 'Contact')
{
$sIconPath = MetaModel::GetClassIcon($sClass, false);
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
return $sIcon;
}
}
class DesignerSortableField extends DesignerFormField
{
protected $aAllowedValues;

View File

@@ -24,25 +24,59 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
interface ModelReflection
abstract class ModelReflection
{
public function GetClassIcon($sClass, $bImgTag = true);
public function IsValidAttCode($sClass, $sAttCode);
public function GetName($sClass);
public function GetLabel($sClass, $sAttCodeEx);
public function ListAttributes($sClass, $sScope = null);
public function GetAttributeProperty($sClass, $sAttCode, $sPropName, $default = null);
public function GetAllowedValues_att($sClass, $sAttCode);
public function HasChildrenClasses($sClass);
public function GetClasses($sCategories = '');
public function IsValidClass($sClass);
public function IsSameFamilyBranch($sClassA, $sClassB);
public function GetParentClass($sClass);
public function GetFiltersList($sClass);
public function IsValidFilterCode($sClass, $sFilterCode);
abstract public function GetClassIcon($sClass, $bImgTag = true);
abstract public function IsValidAttCode($sClass, $sAttCode);
abstract public function GetName($sClass);
abstract public function GetLabel($sClass, $sAttCodeEx);
abstract public function GetValueLabel($sClass, $sAttCode, $sValue);
abstract public function ListAttributes($sClass, $sScope = null);
abstract public function GetAttributeProperty($sClass, $sAttCode, $sPropName, $default = null);
abstract public function GetAllowedValues_att($sClass, $sAttCode);
abstract public function HasChildrenClasses($sClass);
abstract public function GetClasses($sCategories = '', $bExcludeLinks = false);
abstract public function IsValidClass($sClass);
abstract public function IsSameFamilyBranch($sClassA, $sClassB);
abstract public function GetParentClass($sClass);
abstract public function GetFiltersList($sClass);
abstract public function IsValidFilterCode($sClass, $sFilterCode);
abstract public function GetQuery($sOQL);
abstract public function DictString($sStringCode, $sDefault = null, $bUserLanguageOnly = false);
public function DictFormat($sFormatCode /*, ... arguments ....*/)
{
$sLocalizedFormat = $this->DictString($sFormatCode);
$aArguments = func_get_args();
array_shift($aArguments);
if ($sLocalizedFormat == $sFormatCode)
{
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
return $sFormatCode.' - '.implode(', ', $aArguments);
}
return vsprintf($sLocalizedFormat, $aArguments);
}
abstract public function GetIconSelectionField($sCode, $sLabel = '', $defaultValue = '');
}
class ModelReflectionRuntime implements ModelReflection
abstract class QueryReflection
{
/**
* Throws an exception in case of an invalid syntax
*/
abstract public function __construct($sOQL);
abstract public function GetClass();
abstract public function GetClassAlias();
}
class ModelReflectionRuntime extends ModelReflection
{
public function __construct()
{
@@ -68,6 +102,12 @@ class ModelReflectionRuntime implements ModelReflection
return MetaModel::GetLabel($sClass, $sAttCodeEx);
}
public function GetValueLabel($sClass, $sAttCode, $sValue)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
return $oAttDef->GetValueLabel($sValue);
}
public function ListAttributes($sClass, $sScope = null)
{
$aScope = null;
@@ -133,9 +173,26 @@ class ModelReflectionRuntime implements ModelReflection
return MetaModel::HasChildrenClasses($sClass);
}
public function GetClasses($sCategories = '')
public function GetClasses($sCategories = '', $bExcludeLinks = false)
{
return MetaModel::GetClasses($sCategories);
$aClasses = MetaModel::GetClasses($sCategories);
if ($bExcludeLinks)
{
$aExcluded = ProfilesConfig::GetLinkClasses(); // table computed at compile time
$aRes = array();
foreach ($aClasses as $sClass)
{
if (!array_key_exists($sClass, $aExcluded))
{
$aRes[] = $sClass;
}
}
}
else
{
$aRes = $aClasses;
}
return $aRes;
}
public function IsValidClass($sClass)
@@ -162,4 +219,43 @@ class ModelReflectionRuntime implements ModelReflection
{
return MetaModel::IsValidFilterCode($sClass, $sFilterCode);
}
}
public function GetQuery($sOQL)
{
return new QueryReflectionRuntime($sOQL);
}
public function DictString($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly);
}
public function GetIconSelectionField($sCode, $sLabel = '', $defaultValue = '')
{
return new RunTimeIconSelectionField($sCode, $sLabel, $defaultValue);
}
}
class QueryReflectionRuntime extends QueryReflection
{
protected $oFilter;
/**
* throws an exception in case of a wrong syntax
*/
public function __construct($sOQL)
{
$this->oFilter = DBObjectSearch::FromOQL($sOQL);
}
public function GetClass()
{
return $this->oFilter->GetClass();
}
public function GetClassAlias()
{
return $this->oFilter->GetClassAlias();
}
}

View File

@@ -1,4 +1,9 @@
// jQuery UI style "widget" for editing an iTop "dashboard"
////////////////////////////////////////////////////////////////////////////////
//
// dashboard
//
$(function()
{
// the widget definition, where "itop" is the namespace,
@@ -25,23 +30,18 @@ $(function()
this.element
.addClass('itop-dashboard')
.bind('mark_as_modified.itop-dashboard', function(){me.mark_as_modified();} );
.bind('add_dashlet.itop_dashboard', function(event, oParams){
me.add_dashlet(oParams);
});
this.ajax_div = $('<div></div>').appendTo(this.element);
this.ajax_div = $('<div></div>');
this.element.after(this.ajax_div);
this._make_draggable();
this.bModified = false;
},
// called when created, and later when changing options
_refresh: function()
{
var oParams = this._get_state(this.options.render_parameters);
var me = this;
$.post(this.options.render_to, oParams, function(data){
me.element.html(data);
me._make_draggable();
});
},
// events bound via _bind are removed automatically
// revert other modifications here
@@ -58,7 +58,6 @@ $(function()
{
// in 1.9 would use _superApply
this._superApply(arguments);
this._refresh();
},
// _setOption is called for each individual option that is changing
_setOption: function( key, value )
@@ -96,6 +95,170 @@ $(function()
return oState;
},
_get_new_id: function()
{
var iMaxId = 0;
this.element.find(':itop-dashlet').each(function() {
var oDashlet = $(this).data('itopDashlet');
if(oDashlet)
{
var oDashletParams = oDashlet.get_params();
var id = parseInt(oDashletParams.dashlet_id, 10);
if (id > iMaxId) iMaxId = id;
}
});
return 1 + iMaxId;
},
_make_draggable: function()
{
var me = this;
this.element.find('.dashlet').draggable({
revert: 'invalid', appendTo: 'body', zIndex: 9999,
helper: function() {
var oDragItem = $(this).dashlet('get_drag_icon');
return oDragItem;
},
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');
var oDropped = ui.draggable;
if (oDropped.hasClass('dashlet'))
{
// moving around a dashlet
oDropped.detach();
oDropped.css({top: 0, left: 0});
oDropped.appendTo($(this));
var oDashlet = ui.draggable.data('itopDashlet');
me.on_dashlet_moved(oDashlet, $(this), bRefresh);
}
else
{
// inserting a new dashlet
var sDashletClass = ui.draggable.attr('dashlet_class');
$('.itop-dashboard').trigger('add_dashlet', {dashlet_class: sDashletClass, container: $(this), refresh: bRefresh });
}
}
});
},
add_dashlet: function(options)
{
// 1) Create empty divs for the dashlet and its properties
//
var sDashletId = this._get_new_id();
var oDashlet = $('<div class="dashlet" id="dashlet_'+sDashletId+'"/>');
oDashlet.appendTo(options.container);
var oDashletProperties = $('<div class="dashlet_properties" id="dashlet_properties_'+sDashletId+'"/>');
oDashletProperties.appendTo($('#dashlet_properties'));
// 2) Ajax call to fill the divs with default values
// => in return, it must call add_dashlet_finalize
//
this.add_dashlet_ajax(options, sDashletId);
},
add_dashlet_finalize: function(options, sDashletId, sDashletClass)
{
$('#dashlet_'+sDashletId)
.dashlet({dashlet_id: sDashletId, dashlet_class: sDashletClass})
.dashlet('deselect_all')
.dashlet('select')
.draggable({
revert: 'invalid', appendTo: 'body', zIndex: 9999,
helper: function() {
var oDragItem = $(this).dashlet('get_drag_icon');
return oDragItem;
},
cursorAt: { top: 16, left: 16 }
});
if (options.refresh)
{
this._refresh();
}
},
on_dashlet_moved: function(oDashlet, oReceiver, bRefresh)
{
if (bRefresh)
{
// The layout was extended... refresh the whole dashboard
this._refresh();
}
}
});
});
////////////////////////////////////////////////////////////////////////////////
//
// runtimedashboard (extends dashboard)
//
$(function()
{
// the widget definition, where "itop" is the namespace,
// "dashboard" the widget name
$.widget( "itop.runtimedashboard", $.itop.dashboard,
{
// default options
options:
{
dashboard_id: '',
layout_class: '',
title: '',
submit_to: 'index.php',
submit_parameters: {},
render_to: 'index.php',
render_parameters: {},
new_dashlet_parameters: {}
},
// the constructor
_create: function()
{
var me = this;
this._superApply(arguments);
this.element
.addClass('itop-runtimedashboard')
.bind('mark_as_modified.itop-dashboard', function(){me.mark_as_modified();} );
this.bModified = false;
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function()
{
this.element
.removeClass('itop-runtimedashboard');
this._superApply(arguments);
},
// _setOptions is called with a hash of all options that are changing
_setOptions: function()
{
this._superApply(arguments);
this._refresh();
},
// _setOption is called for each individual option that is changing
_setOption: function( key, value )
{
this._superApply(arguments);
},
// called when created, and later when changing options
_refresh: function()
{
this._super();
var oParams = this._get_state(this.options.render_parameters);
var me = this;
$.post(this.options.render_to, oParams, function(data){
me.element.html(data);
me._make_draggable();
});
},
// Modified means: at least one change has been applied
mark_as_modified: function()
{
@@ -130,14 +293,8 @@ $(function()
me.ajax_div.html(data);
});
},
add_dashlet: function(options)
add_dashlet_ajax: function(options, sDashletId)
{
var sDashletId = this._get_new_id();
var oDashlet = $('<div class="dashlet" id="dashlet_'+sDashletId+'"/>');
oDashlet.appendTo(options.container);
var oDashletProperties = $('<div class="dashlet_properties" id="dashlet_properties_'+sDashletId+'"/>');
oDashletProperties.appendTo($('#dashlet_properties'));
var oParams = this.options.new_dashlet_parameters;
var sDashletClass = options.dashlet_class;
oParams.dashlet_class = sDashletClass;
@@ -145,79 +302,17 @@ $(function()
var me = this;
$.post(this.options.render_to, oParams, function(data){
me.ajax_div.html(data);
$('#dashlet_'+sDashletId)
.dashlet({dashlet_id: sDashletId, dashlet_class: sDashletClass})
.dashlet('deselect_all')
.dashlet('select')
.draggable({
revert: 'invalid', appendTo: 'body', zIndex: 9999,
helper: function() {
var oDragItem = $(this).dashlet('get_drag_icon');
return oDragItem;
},
cursorAt: { top: 16, left: 16 }
});
if (options.refresh)
{
me._refresh();
}
me.add_dashlet_finalize(options, sDashletId, sDashletClass);
});
},
_get_new_id: function()
{
var iMaxId = 0;
this.element.find(':itop-dashlet').each(function() {
var oDashlet = $(this).data('itopDashlet');
if(oDashlet)
{
var oDashletParams = oDashlet.get_params();
var id = parseInt(oDashletParams.dashlet_id, 10);
if (id > iMaxId) iMaxId = id;
}
});
return 1 + iMaxId;
},
_make_draggable: function()
{
var me = this;
this.element.find('.dashlet').draggable({
revert: 'invalid', appendTo: 'body', zIndex: 9999,
helper: function() {
var oDragItem = $(this).dashlet('get_drag_icon');
return oDragItem;
},
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');
var oDashlet = ui.draggable;
if (oDashlet.hasClass('dashlet'))
{
// moving around a dashlet
oDashlet.detach();
oDashlet.css({top: 0, left: 0});
oDashlet.appendTo($(this));
if( bRefresh )
{
// The layout was extended... refresh the whole dashboard
me._refresh();
}
}
else
{
// inserting a new dashlet
var sDashletClass = ui.draggable.attr('dashlet_class');
$(':itop-dashboard').dashboard('add_dashlet', {dashlet_class: sDashletClass, container: $(this), refresh: bRefresh });
}
}
});
}
});
});
////////////////////////////////////////////////////////////////////////////////
//
// Helper to upload the file selected in the "import dashboard" dialog
//
function UploadDashboard(oOptions)
{
var sFileId = 'dashboard_upload_file';
@@ -229,6 +324,10 @@ function UploadDashboard(oOptions)
}
////////////////////////////////////////////////////////////////////////////////
//
// dashboard_upload_dlg
//
//jQuery UI style "widget" for managing a "import dashboard" dialog (file upload)
$(function()
{

View File

@@ -123,8 +123,13 @@ $(function()
},
_remove_dashlet: function()
{
$('#dashlet_properties_'+this.options.dashlet_id).remove();
var iDashletId = this.options.dashlet_id;
var sDashletClass = this.options.dashlet_class;
var oContainer = this.element.parent();
$('#dashlet_properties_'+iDashletId).remove();
this.element.remove();
$('#event_bus').trigger('dashlet-removed', {'dashlet_id': iDashletId, 'dashlet_class': sDashletClass, 'container': oContainer});
}
});
});

View File

@@ -187,7 +187,7 @@ $(function()
});
this.element.closest('form').find('.itop-property-field').each(function()
{
var oWidget = $(this).data('property_field');
var oWidget = $(this).data('itopProperty_field');
if (oWidget && oWidget._is_visible())
{
var oVal = oWidget._get_committed_value();

View File

@@ -745,12 +745,11 @@ try
$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
$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'
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property'));
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, ':itop-dashboard'));
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.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'
@@ -803,7 +802,7 @@ try
{
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property'));
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, ':itop-dashboard'));
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.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'

View File

@@ -567,42 +567,13 @@ EOF;
$aClassParams['display_template'] = "utils::GetAbsoluteUrlModulesRoot().'$sDisplayTemplate'";
}
$this->CompileFiles($oProperties, $sTargetDir.'/'.$sModuleRelativeDir, '');
if (($sIcon = $oProperties->GetChildText('icon')) && (strlen($sIcon) > 0))
{
$sIcon = $sModuleRelativeDir.'/'.$sIcon;
$aClassParams['icon'] = "utils::GetAbsoluteUrlModulesRoot().'$sIcon'";
}
else // si <fileref ref="nnn">
{
$oIcon = $oProperties->GetOptionalElement('icon');
if ($oIcon)
{
$oFileRef = $oIcon->GetOptionalElement('fileref');
if ($oFileRef)
{
$iFileId = $oFileRef->getAttribute('ref');
$sXPath = "/itop_design/files/file[@id='$iFileId']";
$oNodes = $this->oFactory->GetNodes($sXPath);
if ($oNodes->length == 0)
{
throw new DOMFormatException('Could not find the file with ref '.$iFileId);
}
$sName = $oNodes->item(0)->GetChildText('name');
$sData = base64_decode($oNodes->item(0)->GetChildText('data'));
$aPathInfo = pathinfo($sName);
$sFile = 'icon-file'.$iFileId.'.'.$aPathInfo['extension'];
$sFilePath = $sTargetDir.'/'.$sModuleRelativeDir.'/'.$sFile;
file_put_contents($sFilePath, $sData);
if (!file_exists($sFilePath))
{
throw new Exception('Could not write icon file '.$sFilePath);
}
$aClassParams['icon'] = "utils::GetAbsoluteUrlModulesRoot().'$sModuleRelativeDir/$sFile'";
}
}
}
$oOrder = $oProperties->GetOptionalElement('order');
if ($oOrder)
{
@@ -1028,6 +999,8 @@ EOF;
protected function CompileMenu($oMenu, $sTargetDir, $sModuleRelativeDir, $oP)
{
$this->CompileFiles($oMenu, $sTargetDir.'/'.$sModuleRelativeDir, $sModuleRelativeDir);
$sMenuId = $oMenu->getAttribute("id");
$sMenuClass = $oMenu->getAttribute("xsi:type");
@@ -1065,11 +1038,11 @@ EOF;
}
$sFileName = strtolower(str_replace(array(':', '/', '\\', '*'), '_', $sMenuId)).'_dashboard_menu.xml';
$sTemplateSpec = $this->PathToPHP($sFileName, $sModuleRelativeDir);
$oXMLDoc = new DOMDocument('1.0', 'UTF-8');
$oXMLDoc->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS)
$oXMLDoc->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
$oRootNode = $oXMLDoc->createElement('dashboard'); // make sure that the document is not empty
$oRootNode->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
$oXMLDoc->appendChild($oRootNode);
@@ -1325,6 +1298,11 @@ class ProfilesConfig
protected static \$aLINKTOCLASSES = $sLinkToClasses;
public static function GetLinkClasses()
{
return self::\$aLINKTOCLASSES;
}
public static function GetProfileActionGrant(\$iProfileId, \$sClass, \$sAction)
{
// Search for a grant, starting from the most explicit declaration,
@@ -1462,6 +1440,42 @@ EOF;
$sDictFile = $sTargetDir.'/dictionaries/'.$sSafeLang.'.dict.php';
file_put_contents($sDictFile, $sPHPDict);
}
// Transform the file references into the corresponding filename (and create the file in the relevant directory)
//
protected function CompileFiles($oNode, $sTargetDir, $sRelativePath)
{
$oFileRefs = $oNode->GetNodes(".//fileref");
foreach ($oFileRefs as $oFileRef)
{
$iFileId = $oFileRef->getAttribute('ref');
if ($iFileId > 0)
{
$oNodes = $this->oFactory->GetNodes("/itop_design/files/file[@id='$iFileId']");
if ($oNodes->length == 0)
{
throw new DOMFormatException('Could not find the file with ref '.$iFileId);
}
$sName = $oNodes->item(0)->GetChildText('name');
$sData = base64_decode($oNodes->item(0)->GetChildText('data'));
$aPathInfo = pathinfo($sName);
$sFile = 'icon-file'.$iFileId.'.'.$aPathInfo['extension'];
$sFilePath = $sTargetDir.'/images/'.$sFile;
@mkdir($sTargetDir.'/images');
file_put_contents($sFilePath, $sData);
if (!file_exists($sFilePath))
{
throw new Exception('Could not write icon file '.$sFilePath);
}
$oParentNode = $oFileRef->parentNode;
$oParentNode->removeChild($oFileRef);
$oTextNode = $oParentNode->ownerDocument->createTextNode($sRelativePath.'/images/'.$sFile);
$oParentNode->appendChild($oTextNode);
}
}
}
}
?>

View File

@@ -1440,12 +1440,26 @@ class MFElement extends DOMElement
return $this->ownerDocument->GetNodes($sXPath, $this);
}
/**
* Extracts some nodes from the DOM (active nodes only !!!)
* @param string $sXPath A XPath expression
* @return DOMNodeList
*/
public function GetNodeById($sXPath, $sId)
{
return $this->ownerDocument->GetNodeById($sXPath, $sId, $this);
}
/**
* For debugging purposes - but this is currently buggy: the whole document is rendered
*/
public function Dump($bReturnRes = false)
{
$sXml = $this->ownerDocument->saveXML($this);
$oMFDoc = new MFDocument();
$oClone = $oMFDoc->importNode($this->cloneNode(true), true);
$oMFDoc->appendChild($oClone);
$sXml = $oMFDoc->saveXML($oClone);
if ($bReturnRes)
{
return $sXml;
@@ -1713,6 +1727,9 @@ class MFElement extends DOMElement
*/
public function AddChildNode(MFElement $oNode)
{
// First: cleanup any flag behind the new node
$oNode->ApplyChanges();
$oExisting = $this->FindExistingChildNode($oNode);
if ($oExisting)
{
@@ -1740,6 +1757,9 @@ class MFElement extends DOMElement
*/
public function RedefineChildNode(MFElement $oNode, $sSearchId = null)
{
// First: cleanup any flag behind the new node
$oNode->ApplyChanges();
$oExisting = $this->FindExistingChildNode($oNode, $sSearchId);
if (!$oExisting)
{
@@ -1906,7 +1926,45 @@ class MFElement extends DOMElement
$aCurrentRules = $oRulesNode->GetNodeAsArrayOfItems();
}
return $aCurrentRules;
}
}
/**
* List changes below a given node (see also MFDocument::ListChanges)
*/
public function ListChanges()
{
// Note: omitting the dot will make the query be global to the whole document!!!
return $this->GetNodes('.//*[@_alteration or @_old_id]');
}
/**
* List changes below a given node (see also MFDocument::ApplyChanges)
*/
public function ApplyChanges()
{
$oNodes = $this->ListChanges();
foreach($oNodes as $oNode)
{
$sOperation = $oNode->GetAttribute('_alteration');
switch($sOperation)
{
case 'added':
case 'replaced':
// marked as added or modified, just reset the flag
$oNode->removeAttribute('_alteration');
break;
case 'removed':
// marked as deleted, let's remove the node from the tree
$oNode->parentNode->removeChild($oNode);
break;
}
if ($oNode->hasAttribute('_old_id'))
{
$oNode->removeAttribute('_old_id');
}
}
}
}
/**