Jelajahi Sumber

User editable dashboards... implementation in progress

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@2030 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 13 tahun lalu
induk
melakukan
c0372f38d1

+ 131 - 43
application/dashboard.class.inc.php

@@ -28,11 +28,12 @@ abstract class Dashboard
 	protected $aWidgetsData;
 	protected $oDOMNode;
 	protected $sId;
+	protected $aCells;
 	
 	public function __construct($sId)
 	{
 		$this->sLayoutClass = null;
-		$this->aDashlets = array();
+		$this->aCells = array();
 		$this->oDOMNode = null;
 		$this->sId = $sId;
 	}
@@ -49,15 +50,21 @@ abstract class Dashboard
 		$oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0);
 		$this->sTitle = $oTitleNode->textContent;
 		
-		$oDashletsNode = $this->oDOMNode->getElementsByTagName('dashlets')->item(0);
-		$oDashletList = $oDashletsNode->getElementsByTagName('dashlet');
-		foreach($oDashletList as $oDomNode)
+		$oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0);
+		$oCellsList = $oCellsNode->getElementsByTagName('cell');
+		foreach($oCellsList as $oCellNode)
 		{
-			$sDashletClass = $oDomNode->getAttribute('xsi:type');
-			$sId = $oDomNode->getAttribute('id');
-			$oNewDashlet = new $sDashletClass($sId);
-			$oNewDashlet->FromDOMNode($oDomNode);
-			$this->aDashlets[] = $oNewDashlet;
+			$aDashletList = array();
+			$oDashletList = $oCellNode->getElementsByTagName('dashlet');
+			foreach($oDashletList as $oDomNode)
+			{
+				$sDashletClass = $oDomNode->getAttribute('xsi:type');
+				$sId = $oDomNode->getAttribute('id');
+				$oNewDashlet = new $sDashletClass($sId);
+				$oNewDashlet->FromDOMNode($oDomNode);
+				$aDashletList[] = $oNewDashlet;
+			}
+			$this->aCells[] = $aDashletList;
 		}
 	}
 	
@@ -77,16 +84,21 @@ abstract class Dashboard
 		$oNode = $oDoc->createElement('title', $this->sTitle);
 		$oMainNode->appendChild($oNode);
 
-		$oDashletsNode = $oDoc->createElement('dashlets');
-		$oMainNode->appendChild($oDashletsNode);
+		$oCellsNode = $oDoc->createElement('cells');
+		$oMainNode->appendChild($oCellsNode);
 
-		foreach ($this->aDashlets as $oDashlet)
+		foreach ($this->aCells as $aCell)
 		{
-			$oNode = $oDoc->createElement('dashlet');
-			$oDashletsNode->appendChild($oNode);
-			$oNode->setAttribute('id', $oDashlet->GetID());
-			$oNode->setAttribute('xsi:type', get_class($oDashlet));
-			$oDashlet->ToDOMNode($oNode);
+			$oCellNode = $oDoc->createElement('cell');
+			$oCellsNode->appendChild($oCellNode);
+			foreach ($aCell as $oDashlet)
+			{
+				$oNode = $oDoc->createElement('dashlet');
+				$oCellNode->appendChild($oNode);
+				$oNode->setAttribute('id', $oDashlet->GetID());
+				$oNode->setAttribute('xsi:type', get_class($oDashlet));
+				$oDashlet->ToDOMNode($oNode);
+			}
 		}
 
 		$sXml = $oDoc->saveXML();
@@ -98,18 +110,23 @@ abstract class Dashboard
 		$this->sLayoutClass = $aParams['layout_class'];
 		$this->sTitle = $aParams['title'];
 		
-		foreach($aParams['dashlets'] as $aDashletParams)
+		foreach($aParams['cells'] as $aCell)
 		{
-			$sDashletClass = $aDashletParams['dashlet_class'];
-			$sId = $aDashletParams['dashlet_id'];
-			$oNewDashlet = new $sDashletClass($sId);
-			
-			$oForm = $oNewDashlet->GetForm();
-			$oForm->SetParamsContainer($sId);
-			$oForm->SetPrefix('');
-			$aValues = $oForm->ReadParams();
-			$oNewDashlet->FromParams($aValues);
-			$this->aDashlets[] = $oNewDashlet;
+			$aCellDashlets = array();
+			foreach($aCell as $aDashletParams)
+			{
+				$sDashletClass = $aDashletParams['dashlet_class'];
+				$sId = $aDashletParams['dashlet_id'];
+				$oNewDashlet = new $sDashletClass($sId);
+				
+				$oForm = $oNewDashlet->GetForm();
+				$oForm->SetParamsContainer($sId);
+				$oForm->SetPrefix('');
+				$aValues = $oForm->ReadParams();
+				$oNewDashlet->FromParams($aValues);
+				$aCellDashlets[] = $oNewDashlet;
+			}
+			$this->aCells[] = $aCellDashlets;
 		}
 		
 	}
@@ -138,7 +155,7 @@ abstract class Dashboard
 	{
 		$this->sTitle = $sTitle;
 	}
-	
+		
 	public function AddDashlet()
 	{
 	}
@@ -147,7 +164,7 @@ abstract class Dashboard
 	{
 		$oPage->add('<h1>'.Dict::S($this->sTitle).'</h1>');
 		$oLayout = new $this->sLayoutClass;
-		$oLayout->Render($oPage, $this->aDashlets, $bEditMode, $aExtraParams);
+		$oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams);
 		if (!$bEditMode)
 		{
 			$oPage->add_linked_script('../js/dashlet.js');
@@ -173,7 +190,8 @@ abstract class Dashboard
 				{
 					$aCallSpec = array($sLayoutClass, 'GetInfo');
 					$aInfo = call_user_func($aCallSpec);
-					$oPage->add('<input type="radio" name="layout_class" value="'.$sLayoutClass.'" id="layout_'.$sLayoutClass.'"><label for="layout_'.$sLayoutClass.'"><img src="'.$sUrl.$aInfo['icon'].'" /></label>'); // title="" on either the img or the label does nothing !
+					$sChecked = ($this->sLayoutClass == $sLayoutClass) ? 'checked' : '';
+					$oPage->add('<input type="radio" name="layout_class" '.$sChecked.' value="'.$sLayoutClass.'" id="layout_'.$sLayoutClass.'"><label for="layout_'.$sLayoutClass.'"><img src="'.$sUrl.$aInfo['icon'].'" /></label>'); // title="" on either the img or the label does nothing !
 				}
 			}
 		}
@@ -229,17 +247,20 @@ EOF
 		$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">Dashlet Properties</div>');
 
 		$oPage->add('<div id="dashlet_properties" style="text-align:center">');
-		foreach($this->aDashlets as $oDashlet)
+		foreach($this->aCells as $aCell)
 		{
-			$sId = $oDashlet->GetID();
-			$sClass = get_class($oDashlet);
-			if ($oDashlet->IsVisible())
+			foreach($aCell as $oDashlet)
 			{
-				$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$sId.'" style="display:none">');
-				$oForm = $oDashlet->GetForm();
-				$this->SetFormParams($oForm);
-				$oForm->RenderAsPropertySheet($oPage);		
-				$oPage->add('</div>');
+				$sId = $oDashlet->GetID();
+				$sClass = get_class($oDashlet);
+				if ($oDashlet->IsVisible())
+				{
+					$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$sId.'" style="display:none">');
+					$oForm = $oDashlet->GetForm();
+					$this->SetFormParams($oForm);
+					$oForm->RenderAsPropertySheet($oPage);		
+					$oPage->add('</div>');
+				}
 			}
 		}
 		$oPage->add('</div>');
@@ -252,6 +273,19 @@ EOF
 
 class RuntimeDashboard extends Dashboard
 {
+	protected $bCustomized;
+	
+	public function __construct($sId)
+	{
+		parent::__construct($sId);
+		$this->bCustomized = false;
+	}
+		
+	public function SetCustomFlag($bCustomized)
+	{
+		$this->bCustomized = $bCustomized;
+	}
+	
 	protected function SetFormParams($oForm)
 	{
 		$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property'));		
@@ -284,13 +318,41 @@ class RuntimeDashboard extends Dashboard
 		}		
 	}
 	
+	public function Revert()
+	{
+		$oUDSearch = new DBObjectSearch('UserDashboard');
+		$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
+		$oUDSearch->AddCondition('menu_code', $this->sId, '=');
+		$oUDSet = new DBObjectSet($oUDSearch);
+		if ($oUDSet->Count() > 0)
+		{
+			// Assuming there is at most one couple {user, menu}!
+			$oUserDashboard = $oUDSet->Fetch();
+			$oUserDashboard->DBDelete();
+		}
+	}
+	
 	public function Render($oPage, $bEditMode = false, $aExtraParams = array())
 	{
 		parent::Render($oPage, $bEditMode, $aExtraParams);
 		if (!$bEditMode)
 		{
-			$sEditBtn = addslashes('<div style="display: inline-block; height: 55px; width:200px;vertical-align:center;line-height:60px;text-align:left;"><button onclick="EditDashboard(\''.$this->sId.'\');">Edit This Page</button></div>');
-			$oPage->add_ready_script("$('#top-bar').prepend('$sEditBtn');");
+			$sEditMenu = "<td><span id=\"DashboardMenu\"><ul><li><img src=\"../images/edit.png\"><ul>";
+			$sEditMenu .= "<li><a href=\"#\" onclick=\"return EditDashboard('{$this->sId}')\">Edit This Page</a></li>";
+			if ($this->bCustomized)
+			{
+				$sEditMenu .= "<li><a href=\"#\" onclick=\"return RevertDashboard('{$this->sId}')\">Revert To Original Version</a></li>";
+			}
+			$sEditMenu .= "</ul></li></ul></span></td>";
+			$sEditMenu = addslashes($sEditMenu);
+			//$sEditBtn = addslashes('<div style="display: inline-block; height: 55px; width:200px;vertical-align:center;line-height:60px;text-align:left;"><button onclick="EditDashboard(\''.$this->sId.'\');">Edit This Page</button></div>');
+			$oPage->add_ready_script(
+<<<EOF
+	$('#logOffBtn').parent().before('$sEditMenu');
+	$('#DashboardMenu>ul').popupmenu();
+	
+EOF
+			);
 			$oPage->add_script(
 <<<EOF
 function EditDashboard(sId)
@@ -303,6 +365,16 @@ function EditDashboard(sId)
 	);
 	return false;
 }
+function RevertDashboard(sId)
+{
+	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'revert_dashboard', dashboard_id: sId},
+		function(data)
+		{
+			$('body').append(data);
+		}
+	);
+	return false;
+}
 EOF
 			);
 		}
@@ -380,8 +452,24 @@ $('#event_bus').bind('dashlet-selected', function(event, data){
 		});
 
 	});
+	
+dashboard_prop_size = GetUserPreference('dashboard_prop_size', 300);
+$('#dashboard_editor').layout({
+	east: {
+		minSize: 150,
+		size: dashboard_prop_size,
+		onresize_end: function(name, elt, state, options, layout)
+		{
+			if (state.isSliding == false)
+			{
+				SetUserPreference('dashboard_prop_size', state.size, true);
+			}
+		},
+	}
+});
+	
 EOF
 		);
-		$oPage->add_ready_script("$('#dashboard_editor').layout({ east__size: 300 });");
+		$oPage->add_ready_script("");
 	}
 }

+ 48 - 10
application/dashboardlayout.class.inc.php

@@ -27,9 +27,8 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
 		$this->iNbCols = 1;
 	}
 	
-	public function Render($oPage, $aDashlets, $bEditMode = false, $aExtraParams = array())
+	protected function TrimCell($aDashlets)
 	{
-		// Trim the list of dashlets to remove the invisible ones at the end of the array
 		$aKeys = array_reverse(array_keys($aDashlets));
 		$idx = 0;
 		$bNoVisibleFound = true;
@@ -46,24 +45,63 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
 			}
 			$idx++;
 		}
+		return $aDashlets;
+	}
+	
+	protected function TrimCellsArray($aCells)
+	{
+		foreach($aCells as $key => $aDashlets)
+		{
+			$aCells[$key] = $this->TrimCell($aDashlets);
+		}
+		$aKeys = array_reverse(array_keys($aCells));
+		$idx = 0;
+		$bNoVisibleFound = true;
+		while($idx < count($aKeys) && $bNoVisibleFound)
+		{
+			$aDashlets = $aCells[$aKeys[$idx]];
+			if (count($aDashlets) > 0)
+			{
+				$bNoVisibleFound = false;
+			}
+			else
+			{
+				unset($aCells[$aKeys[$idx]]);
+			}
+			$idx++;
+		}
+		return $aCells;		
+		
+	}
+	
+	public function Render($oPage, $aCells, $bEditMode = false, $aExtraParams = array())
+	{
+		// Trim the list of cells to remove the invisible/empty ones at the end of the array
+		$aCells = $this->TrimCellsArray($aCells);
 		
 		$oPage->add('<table style="width:100%"><tbody>');
-		$iDashletIdx = 0;
+		$iCellIdx = 0;
 		$fColSize = 100 / $this->iNbCols;
-		$sStyle = $bEditMode ? 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode"' : 'style="width: '.$fColSize.'%;  "';
-		$iNbRows = ceil(count($aDashlets) / $this->iNbCols);
+		$sStyle = $bEditMode ? 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode"' : 'style="width: '.$fColSize.'%;" class="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>");
-				if (array_key_exists($iDashletIdx, $aDashlets))
+				if (array_key_exists($iCellIdx, $aCells))
 				{
-					$oDashlet = $aDashlets[$iDashletIdx];
-					if ($oDashlet->IsVisible())
+					$aDashlets = $aCells[$iCellIdx];
+					if (count($aDashlets) > 0)
 					{
-						$oDashlet->DoRender($oPage, $bEditMode, $aExtraParams);
+						foreach($aDashlets as $oDashlet)
+						{
+							if ($oDashlet->IsVisible())
+							{
+								$oDashlet->DoRender($oPage, $bEditMode, $aExtraParams);
+							}
+						}
 					}
 					else
 					{
@@ -75,7 +113,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
 					$oPage->add('&nbsp;');
 				}
 				$oPage->add('</td>');
-				$iDashletIdx++;
+				$iCellIdx++;
 			}
 			$oPage->add('</tr>');
 		}

+ 49 - 4
application/dashlet.class.inc.php

@@ -26,6 +26,7 @@ abstract class Dashlet
 	protected $bRedrawNeeded;
 	protected $bFormRedrawNeeded;
 	protected $aProperties; // array of {property => value}
+	protected $aCSSClasses;
 	
 	public function __construct($sId)
 	{
@@ -33,6 +34,7 @@ abstract class Dashlet
 		$this->bRedrawNeeded = true; // By default: redraw each time a property changes
 		$this->bFormRedrawNeeded = false; // By default: no need to redraw the form (independent fields)
 		$this->aProperties = array(); // By default: there is no property
+		$this->aCSSClasses = array('dashlet');
 	}
 
 	// Assuming that a property has the type of its default value, set in the constructor
@@ -109,14 +111,15 @@ abstract class Dashlet
 	
 	public function DoRender($oPage, $bEditMode = false, $aExtraParams = array())
 	{
+		$sCSSClasses = implode(' ', $this->aCSSClasses);
 		if ($bEditMode)
 		{
 			$sId = $this->GetID();
-			$oPage->add('<div class="dashlet" id="dashlet_'.$sId.'">');
+			$oPage->add('<div class="'.$sCSSClasses.'" id="dashlet_'.$sId.'">');
 		}
 		else
 		{
-			$oPage->add('<div class="dashlet">');
+			$oPage->add('<div class="'.$sCSSClasses.'">');
 		}
 		$this->Render($oPage, $bEditMode, $aExtraParams);
 		$oPage->add('</div>');
@@ -293,7 +296,7 @@ class DashletObjectList extends Dashlet
 		$oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
 		$oForm->AddField($oField);
 
-		$oField = new DesignerTextField('query', 'Query', $this->aProperties['query']);
+		$oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']);
 		$oForm->AddField($oField);
 
 		$oField = new DesignerBooleanField('menu', 'Menu', $this->aProperties['menu']);
@@ -379,7 +382,7 @@ abstract class DashletGroupBy extends Dashlet
 		$oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
 		$oForm->AddField($oField);
 
-		$oField = new DesignerTextField('query', 'Query', $this->aProperties['query']);
+		$oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']);
 		$oForm->AddField($oField);
 
 		// Group by field: build the list of possible values (attribute codes + ...)
@@ -575,11 +578,13 @@ class DashletHeader extends Dashlet
 		}
 
 		$oPage->add('<div style="text-align:center" class="dashlet-content">');
+		$oPage->add('<div class="main_header">');
 		$aParams = array();
 		$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
 		$oBlock = DisplayBlock::FromTemplate($sXML);
 		$oBlock->Display($oPage, $sBlockId, $aParams);
 		$oPage->add('</div>');
+		$oPage->add('</div>');
 	}
 
 	public function GetPropertiesFields(DesignerForm $oForm)
@@ -603,3 +608,43 @@ class DashletHeader extends Dashlet
 		);
 	}
 }
+
+
+class DashletBadge extends Dashlet
+{
+	public function __construct($sId)
+	{
+		parent::__construct($sId);
+		$this->aProperties['class'] = 'Contact';
+		$this->aCSSClasses[] = 'dashlet-inline';
+		$this->aCSSClasses[] = 'dashlet-badge';
+	}
+	
+	public function Render($oPage, $bEditMode = false, $aExtraParams = array())
+	{
+		$sClass = $this->aProperties['class'];
+
+		$oPage->add('<div style="text-align:center" class="dashlet-content">');
+		$sXml = "<itopblock BlockClass=\"DisplayBlock\" type=\"actions\" asynchronous=\"false\" encoding=\"text/oql\" parameters=\"context_filter:1\">SELECT $sClass</itopblock>";
+		$oBlock = DisplayBlock::FromTemplate($sXml);
+		$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
+		$oBlock->Display($oPage, $sBlockId, $aExtraParams);
+		$oPage->add('</div>');
+	}
+
+	public function GetPropertiesFields(DesignerForm $oForm)
+	{
+		$oField = new DesignerTextField('class', 'Class', $this->aProperties['class']);
+		$oForm->AddField($oField);
+	}
+	
+	static public function GetInfo()
+	{
+		return array(
+			'label' => 'Badge',
+			'icon' => 'images/dashlet-badge.png',
+			'description' => 'Object Icon with new/search',
+		);
+	}
+}
+

+ 24 - 1
application/forms.class.inc.php

@@ -150,7 +150,7 @@ class DesignerForm
 		if ($this->oParentForm == null)
 		{
 			$sFormId = $this->sFormId;
-			$sReturn = '<form id="'.$sFormId.'">';
+			$sReturn = '<form id="'.$sFormId.'" onsubmit="return false;">';
 			$sReturn .= '<table class="prop_table">';
 			$sReturn .= '<thead><tr><th class="prop_header">Property</th><th class="prop_header">Value</th><th colspan="2" class="prop_header">&nbsp;</th></tr></thead><tbody>';
 		}
@@ -627,6 +627,29 @@ EOF
 	}
 }
 
+class DesignerLongTextField extends DesignerTextField
+{
+	public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
+	{
+		$sId = $this->oForm->GetFieldId($this->sCode);
+		$sName = $this->oForm->GetFieldName($this->sCode);
+		$sValidation = $this->oForm->GetValidationArea($this->sCode);
+		$sPattern = addslashes($this->sValidationPattern);
+		$sMandatory = $this->bMandatory ? 'true' :  'false';
+		$sReadOnly = $this->IsReadOnly() ? 'readonly' :  '';
+		$oP->add_ready_script(
+<<<EOF
+$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', '$sFormId'); } );
+{
+	var myTimer = null;
+	$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
+}
+EOF
+);
+		return array('label' => $this->sLabel, 'value' => "<textarea id=\"$sId\" $sReadOnly name=\"$sName\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</textarea>".$sValidation);
+	}
+}
+
 class DesignerComboField extends DesignerFormField
 {
 	protected $aAllowedValues;

+ 6 - 2
application/menunode.class.inc.php

@@ -807,9 +807,11 @@ class DashboardMenuNode extends MenuNode
 
 	protected function GetDashboard()
 	{
-		$sDashboardDefinition = @file_get_contents($this->sDashboardFile);
+		$sDashboardDefinition = @file_get_contents($this->sDashboardFile);		
 		if ($sDashboardDefinition !== false)
 		{
+			$bCustomized = false;
+			
 			// Search for an eventual user defined dashboard, overloading the existing one
 			$oUDSearch = new DBObjectSearch('UserDashboard');
 			$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
@@ -820,10 +822,12 @@ class DashboardMenuNode extends MenuNode
 				// Assuming there is at most one couple {user, menu}!
 				$oUserDashboard = $oUDSet->Fetch();
 				$sDashboardDefinition = $oUserDashboard->Get('contents');
+				$bCustomized = true;
+				
 			}
-
 			$oDashboard = new RuntimeDashboard($this->sMenuId);
 			$oDashboard->FromXml($sDashboardDefinition);
+			$oDashboard->SetCustomFlag($bCustomized);
 		}
 		else
 		{