فهرست منبع

Dashboards - Finalized the implementation (inc. translation in french) - still some known issues

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@2195 a333f486-631f-4898-b8df-5754b55c2be0
romainq 12 سال پیش
والد
کامیت
0865ab481b

+ 42 - 21
application/dashboard.class.inc.php

@@ -178,10 +178,10 @@ abstract class Dashboard
 	public function RenderProperties($oPage)
 	{
 		// menu to pick a layout and edit other properties of the dashboard
-		$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">Dashboard Properties</div>');
+		$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">'.Dict::S('UI:DashboardEdit:Properties').'</div>');
 		$sUrl = utils::GetAbsoluteUrlAppRoot();
 		
-		$oPage->add('<div style="text-align:center">Layout:</div>');
+		$oPage->add('<div style="text-align:center">'.Dict::S('UI:DashboardEdit:Layout').'</div>');
 		$oPage->add('<div id="select_layout" style="text-align:center">');
 		foreach( get_declared_classes() as $sLayoutClass)
 		{
@@ -200,7 +200,7 @@ abstract class Dashboard
 		$oPage->add('</div>');
 		
 		$oForm = new DesignerForm();
-		$oField = new DesignerLongTextField('dashboard_title', 'Title', $this->sTitle);
+		$oField = new DesignerLongTextField('dashboard_title', Dict::S('UI:DashboardEdit:DashboardTitle'), $this->sTitle);
 		$oForm->AddField($oField);
 		$this->SetFormParams($oForm);
 		$oForm->RenderAsPropertySheet($oPage);	
@@ -226,7 +226,7 @@ EOF
 	public function RenderDashletsSelection($oPage)
 	{
 		// Toolbox/palette to drag and drop dashlets
-		$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">Available Dashlets</div>');
+		$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">'.Dict::S('UI:DashboardEdit:Dashlets').'</div>');
 		$sUrl = utils::GetAbsoluteUrlAppRoot();
 
 		$oPage->add('<div id="select_dashlet" style="text-align:center">');
@@ -258,7 +258,7 @@ EOF
 	public function RenderDashletsProperties($oPage)
 	{
 		// Toolbox/palette to edit the properties of each dashlet
-		$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 class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">'.Dict::S('UI:DashboardEdit:DashletProperties').'</div>');
 
 		$oPage->add('<div id="dashlet_properties" style="text-align:center">');
 		foreach($this->aCells as $aCell)
@@ -284,7 +284,15 @@ EOF
 	
 	protected function GetNewDashletId()
 	{
-		return 999;
+		$iNewId = 0;
+		foreach($this->aCells as $aDashlets)
+		{
+			foreach($aDashlets as $oDashlet)
+			{
+				$iNewId = max($iNewId, (int)$oDashlet->GetID());
+			}
+		}
+		return $iNewId + 1;
 	}
 	
 	abstract protected function SetFormParams($oForm);
@@ -357,10 +365,11 @@ class RuntimeDashboard extends Dashboard
 		if (!$bEditMode)
 		{
 			$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>";
+
+			$sEditMenu .= "<li><a href=\"#\" onclick=\"return EditDashboard('{$this->sId}')\">".Dict::S('UI:Dashboard:Edit')."</a></li>";
 			if ($this->bCustomized)
 			{
-				$sEditMenu .= "<li><a href=\"#\" onclick=\"return RevertDashboard('{$this->sId}')\">Revert To Original Version</a></li>";
+				$sEditMenu .= "<li><a href=\"#\" onclick=\"if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}'); else return false;\">".Dict::S('UI:Dashboard:Revert')."</a></li>";
 			}
 			$sEditMenu .= "</ul></li></ul></span></td>";
 			$sEditMenu = addslashes($sEditMenu);
@@ -413,8 +422,8 @@ EOF
 		$oPage->add('<div id="event_bus"/>'); // For exchanging messages between the panes, same as in the designer
 		$oPage->add('</div>');
 		
-		$sDialogTitle = 'Dashboard Editor';
-		$sOkButtonLabel = Dict::S('UI:Button:Ok');
+		$sDialogTitle = Dict::S('UI:DashboardEdit:Title');
+		$sOkButtonLabel = Dict::S('UI:Button:Save');
 		$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
 		
 		$sId = addslashes($this->sId);
@@ -472,11 +481,13 @@ $('#event_bus').bind('dashlet-selected', function(event, data){
 
 	});
 	
-dashboard_prop_size = GetUserPreference('dashboard_prop_size', 300);
+dashboard_prop_size = GetUserPreference('dashboard_prop_size', 350);
 $('#dashboard_editor').layout({
 	east: {
-		minSize: 150,
+		minSize: 200,
 		size: dashboard_prop_size,
+		togglerLength_open: 0,
+		togglerLength_closed: 0, 
 		onresize_end: function(name, elt, state, options, layout)
 		{
 			if (state.isSliding == false)
@@ -486,13 +497,12 @@ $('#dashboard_editor').layout({
 		},
 	}
 });
-	
 EOF
 		);
 		$oPage->add_ready_script("");
 	}
 	
-	public static function GetDashletCreationForm($sOQL)
+	public static function GetDashletCreationForm($sOQL = null)
 	{
 		$oForm = new DesignerForm();
 	
@@ -502,15 +512,26 @@ EOF
 		foreach($aAllMenus as $idx => $aMenu)
 		{
 			$oMenu = $aMenu['node'];
+			$sParentId = $aMenu['parent'];
 			if ($oMenu instanceof DashboardMenuNode)
 			{
-				$aAllowedDashboards[$oMenu->GetMenuId()] = Dict::S($oMenu->GetMenuId());
+				$sMenuLabel = $oMenu->GetTitle();
+				$sParentLabel = Dict::S('Menu:'.$sParentId);
+				if ($sParentLabel != $sMenuLabel)
+				{
+					$aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel;
+				}
+				else
+				{
+					$aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel;
+				}
 			}
 		}
+		asort($aAllowedDashboards);
 		
 		$aKeys = array_keys($aAllowedDashboards); // Select the first one by default
 		$sDefaultDashboard = $aKeys[0];
-		$oField = new DesignerComboField('menu_id', 'Dashboard', $sDefaultDashboard);
+		$oField = new DesignerComboField('menu_id', Dict::S('UI:DashletCreation:Dashboard'), $sDefaultDashboard);
 		$oField->SetAllowedValues($aAllowedDashboards);
 		$oField->SetMandatory(true);
 		$oForm->AddField($oField);
@@ -518,7 +539,7 @@ EOF
 		// Get the list of possible dashlets that support a creation from
 		// an OQL
 		$aDashlets = array();
-		foreach( get_declared_classes() as $sDashletClass)
+		foreach(get_declared_classes() as $sDashletClass)
 		{
 			if (is_subclass_of($sDashletClass, 'Dashlet'))
 			{
@@ -537,7 +558,7 @@ EOF
 			}
 		}
 		
-		$oSelectorField = new DesignerFormSelectorField('dashlet_class', 'Dashlet Type', '');
+		$oSelectorField = new DesignerFormSelectorField('dashlet_class', Dict::S('UI:DashletCreation:DashletType'), '');
 		$oForm->AddField($oSelectorField);
 		foreach($aDashlets as $sDashletClass => $aDashletInfo)
 		{
@@ -547,7 +568,7 @@ EOF
 			
 			$oSelectorField->AddSubForm($oSubForm, $aDashletInfo['label'], $aDashletInfo['class']);
 		}
-		$oField = new DesignerBooleanField('open_editor', 'Edit the Dashboard', true);
+		$oField = new DesignerBooleanField('open_editor', Dict::S('UI:DashletCreation:EditNow'), true);
 		$oForm->AddField($oField);
 		
 		return $oForm;
@@ -558,11 +579,11 @@ EOF
 		$oPage->add('<div id="dashlet_creation_dlg">');
 
 		$oForm = self::GetDashletCreationForm($sOQL);
-		
+
 		$oForm->Render($oPage);
 		$oPage->add('</div>');
 		
-		$sDialogTitle = 'Create a new Dashlet';
+		$sDialogTitle = Dict::S('UI:DashletCreation:Title');
 		$sOkButtonLabel = Dict::S('UI:Button:Ok');
 		$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
 		

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 11 - 3
application/dashlet.class.inc.php


+ 2 - 1
application/displayblock.class.inc.php

@@ -656,7 +656,8 @@ class DisplayBlock
 			$iCount = $this->m_oSet->Count();
 			$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($this->m_oFilter->serialize());
 			$sHtml .= '<p><a class="actions" href="'.$sHyperlink.'">';
-			$sHtml .= MetaModel::GetClassIcon($sClass, true, 'float;left;margin-right:10px;');
+			// Note: border set to 0 due to various browser interpretations (IE9 adding a 2px border)
+			$sHtml .= MetaModel::GetClassIcon($sClass, true, 'float;left;margin-right:10px;border:0;');
 			$sHtml .= MetaModel::GetName($sClass).': '.$iCount.'</a></p>';
 			$sParams = $oAppContext->GetForLink();
 			$sHtml .= '<p>';

+ 13 - 16
application/forms.class.inc.php

@@ -92,7 +92,9 @@ class DesignerForm
 				$aRow = $oField->Render($oP, $sFormId);
 				if ($oField->IsVisible())
 				{
-					$aDetails[] = array('label' => $aRow['label'], 'value' => $aRow['value']);
+					$sValidation = '&nbsp;<span class="prop_apply">'.$this->GetValidationArea($oField->GetCode()).'</span>';
+					$sField = $aRow['value'].$sValidation;
+					$aDetails[] = array('label' => $aRow['label'], 'value' => $sField);
 				}
 				else
 				{
@@ -152,7 +154,7 @@ class DesignerForm
 			$sFormId = $this->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>';
+			$sReturn .= '<thead><tr><th class="prop_header">'.Dict::S('UI:Form:Property').'</th><th class="prop_header">'.Dict::S('UI:Form:Value').'</th><th colspan="2" class="prop_header">&nbsp;</th></tr></thead><tbody>';
 		}
 		else
 		{
@@ -168,13 +170,14 @@ class DesignerForm
 			}
 
 
-			$sValidationFields = '</td><td class="prop_icon prop_apply"><span title="Apply" class="ui-icon ui-icon-circle-check"/></td><td  class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td></tr>';
 			foreach($aFields as $oField)
 			{
 				$aRow = $oField->Render($oP, $sFormId, 'property');
 				if ($oField->IsVisible())
 				{
 					$sFieldId = $this->GetFieldId($oField->GetCode());
+					$sValidation = $this->GetValidationArea($oField->GetCode(), '<span title="Apply" class="ui-icon ui-icon-circle-check"/>');
+					$sValidationFields = '</td><td class="prop_icon prop_apply">'.$sValidation.'</td><td  class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td></tr>';
 					$sReturn .= '<tr id="row_'.$sFieldId.'"><td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$aRow['value'];
 					if (!($oField instanceof DesignerFormSelectorField))
 					{
@@ -359,9 +362,9 @@ EOF
 		return 'attr_'.$sCode;
 	}
 	
-	public function GetValidationArea($sCode)
+	public function GetValidationArea($sCode, $sContent = '')
 	{
-		return "<span style=\"display:inline-block;width:20px;\" id=\"v_{$this->sFormPrefix}attr_$sCode\"></span>";
+		return "<span style=\"display:inline-block;width:20px;\" id=\"v_{$this->sFormPrefix}attr_$sCode\"><span class=\"ui-icon ui-icon-alert\"></span>$sContent</span>";
 	}
 	public function GetAsyncActionClass()
 	{
@@ -600,7 +603,6 @@ class DesignerTextField extends DesignerFormField
 	{
 		$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' :  '';
@@ -613,7 +615,7 @@ $('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId'
 }
 EOF
 );
-		return array('label' => $this->sLabel, 'value' => "<input type=\"text\" id=\"$sId\" $sReadOnly name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">".$sValidation);
+		return array('label' => $this->sLabel, 'value' => "<input type=\"text\" id=\"$sId\" $sReadOnly name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">");
 	}
 
 	public function ReadParam(&$aValues)
@@ -633,7 +635,6 @@ class DesignerLongTextField extends DesignerTextField
 	{
 		$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' :  '';
@@ -646,7 +647,7 @@ $('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId'
 }
 EOF
 );
-		return array('label' => $this->sLabel, 'value' => "<textarea id=\"$sId\" $sReadOnly name=\"$sName\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</textarea>".$sValidation);
+		return array('label' => $this->sLabel, 'value' => "<textarea id=\"$sId\" $sReadOnly name=\"$sName\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</textarea>");
 	}
 }
 
@@ -688,7 +689,6 @@ class DesignerComboField extends DesignerFormField
 		$sChecked = $this->defaultValue ? 'checked' : '';
 		$sMandatory = $this->bMandatory ? 'true' :  'false';
 		$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' :  '';
-		$sValidation = $this->oForm->GetValidationArea($this->sCode);
 		if ($this->bMultipleSelection)
 		{
 			$sHtml = "<select multiple size=\"8\"id=\"$sId\" name=\"$sName\" $sReadOnly>";
@@ -722,7 +722,7 @@ class DesignerComboField extends DesignerFormField
 $('#$sId').bind('change validate', function() { ValidateWithPattern('$sId', $sMandatory, '', '$sFormId'); } );
 EOF
 );
-		return array('label' => $this->sLabel, 'value' => $sHtml.$sValidation);
+		return array('label' => $this->sLabel, 'value' => $sHtml);
 	}
 
 	public function ReadParam(&$aValues)
@@ -746,11 +746,10 @@ class DesignerBooleanField extends DesignerFormField
 	{
 		$sId = $this->oForm->GetFieldId($this->sCode);
 		$sName = $this->oForm->GetFieldName($this->sCode);
-		$sValidation = $this->oForm->GetValidationArea($this->sCode);
 		$sChecked = $this->defaultValue ? 'checked' : '';
 		$sReadOnly = $this->IsReadOnly() ? 'disabled' :  ''; // readonly does not work as expected on checkboxes:
 															 // readonly prevents the user from changing the input's value not its state (checked/unchecked)
-		return array('label' => $this->sLabel, 'value' => "<input type=\"checkbox\" $sChecked $sReadOnly id=\"$sId\" name=\"$sName\" value=\"true\">".$sValidation);
+		return array('label' => $this->sLabel, 'value' => "<input type=\"checkbox\" $sChecked $sReadOnly id=\"$sId\" name=\"$sName\" value=\"true\">");
 	}
 	
 	public function ReadParam(&$aValues)
@@ -800,7 +799,6 @@ class DesignerHiddenField extends DesignerFormField
 	{
 		$sId = $this->oForm->GetFieldId($this->sCode);
 		$sName = $this->oForm->GetFieldName($this->sCode);
-		$sValidation = $this->oForm->GetValidationArea($this->sCode);
 		$sChecked = $this->defaultValue ? 'checked' : '';
 		return array('label' =>'', 'value' => "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">");
 	}
@@ -868,7 +866,6 @@ class DesignerSortableField extends DesignerFormField
 		$bOpen = false;
 		$sId = $this->oForm->GetFieldId($this->sCode);
 		$sName = $this->oForm->GetFieldName($this->sCode);
-		$sValidation = $this->oForm->GetValidationArea($this->sCode);
 		$sHtml = "<span class=\"sort_$sId fieldslist\" id=\"sortable_$sId\">";
 		foreach($this->defaultValue as $sValue)
 		{
@@ -893,7 +890,7 @@ class DesignerSortableField extends DesignerFormField
 	$('#sortable_$sId').disableSelection();
 EOF
 		);
-		return array('label' => $this->sLabel, 'value' => $sHtml.$sValidation);
+		return array('label' => $this->sLabel, 'value' => $sHtml);
 	}
 }
 

+ 0 - 11
application/itopwebpage.class.inc.php

@@ -397,17 +397,6 @@ EOF
 
 EOF
 		);
-
-		// Build menus from module handlers
-		//
-		foreach(get_declared_classes() as $sPHPClass)
-		{
-			if (is_subclass_of($sPHPClass, 'ModuleHandlerAPI'))
-			{
-				$aCallSpec = array($sPHPClass, 'OnMenuCreation');
-				call_user_func($aCallSpec);
-			}
-		}
 	}
 
 	public function AddToMenu($sHtml)

+ 21 - 0
application/menunode.class.inc.php

@@ -60,10 +60,29 @@ require_once(APPROOT."/application/user.dashboard.class.inc.php");
  
 class ApplicationMenu
 {
+	static $bAdditionalMenusLoaded = false;
 	static $aRootMenus = array();
 	static $aMenusIndex = array();
 	static $sFavoriteSiloQuery = 'SELECT Organization';
 	
+	static public function LoadAdditionalMenus()
+	{
+		if (!self::$bAdditionalMenusLoaded)
+		{
+			// Build menus from module handlers
+			//
+			foreach(get_declared_classes() as $sPHPClass)
+			{
+				if (is_subclass_of($sPHPClass, 'ModuleHandlerAPI'))
+				{
+					$aCallSpec = array($sPHPClass, 'OnMenuCreation');
+					call_user_func($aCallSpec);
+				}
+			}
+			self::$bAdditionalMenusLoaded = true;
+		}
+	}
+
 	/**
 	 * Set the query used to limit the list of displayed organizations in the drop-down menu
 	 * @param $sOQL string The OQL query returning a list of Organization objects
@@ -127,6 +146,7 @@ class ApplicationMenu
 	 */
 	static public function ReflectionMenuNodes()
 	{
+		self::LoadAdditionalMenus();
 		return self::$aMenusIndex;
 	}
 	
@@ -135,6 +155,7 @@ class ApplicationMenu
 	 */
 	static public function DisplayMenu(iTopWebPage $oPage, $aExtraParams)
 	{
+		self::LoadAdditionalMenus();
 		// Sort the root menu based on the rank
 		usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
 		$iAccordion = 0;

+ 14 - 9
core/expression.class.inc.php

@@ -847,15 +847,20 @@ class FunctionExpression extends Expression
 			$oFormatExpr = $this->m_aArgs[1];
 			if ($oFormatExpr->Render() == "'%w'")
 			{
-				static $aWeekDayToString = array(
-					0 => 'Sunday',
-					1 => 'Monday',
-					2 => 'Tuesday',
-					3 => 'Wednesday',
-					4 => 'Thursday',
-					5 => 'Friday',
-					6 => 'Saturday',
-				);
+				static $aWeekDayToString = null;
+				if (is_null($aWeekDayToString))
+				{
+					// Init the correspondance table
+					$aWeekDayToString = array(
+						0 => Dict::S('DayOfWeek-Sunday'),
+						1 => Dict::S('DayOfWeek-Monday'),
+						2 => Dict::S('DayOfWeek-Tuesday'),
+						3 => Dict::S('DayOfWeek-Wednesday'),
+						4 => Dict::S('DayOfWeek-Thursday'),
+						5 => Dict::S('DayOfWeek-Friday'),
+						6 => Dict::S('DayOfWeek-Saturday')
+					);
+				}
 				if (isset($aWeekDayToString[(int)$sValue]))
 				{
 					$sRes = $aWeekDayToString[(int)$sValue];

+ 6 - 0
core/oql/oqlexception.class.inc.php

@@ -50,6 +50,12 @@ class OQLException extends CoreException
 		parent::__construct($sMessage, 0);
 	}
 
+	public function GetUserFriendlyDescription()
+	{
+		// Todo - translate all errors!
+		return $this->getMessage();
+	}
+
 	public function getHtmlDesc($sHighlightHtmlBegin = '<span style="font-weight: bolder">', $sHighlightHtmlEnd = '</span>')
 	{
 		$sRet = htmlentities($this->m_MyIssue.", found '".$this->m_sUnexpected."' in: ", ENT_QUOTES, 'UTF-8');

+ 15 - 0
core/oql/oqlinterpreter.class.inc.php

@@ -37,6 +37,21 @@ class UnknownClassOqlException extends OqlNormalizeException
 	{
 		parent::__construct('Unknown class', $sInput, $oName, $aExpecting);
 	}
+
+	public function GetUserFriendlyDescription()
+	{
+		$sWrongClass = $this->GetWrongWord();
+		$sSuggest = self::FindClosestString($sWrongClass, $this->GetSuggestions());
+
+		if ($sSuggest != '')
+		{
+			return Dict::Format('UI:OQL:UnknownClassAndFix', $sWrongClass, $sSuggest);
+		}
+		else
+		{
+			return Dict::Format('UI:OQL:UnknownClassNoFix', $sWrongClass);
+		}
+	}
 }
 
 class OqlInterpreterException extends OQLException

+ 22 - 0
css/light-grey.css

@@ -1178,6 +1178,19 @@ table.prop_table {
 td.prop_value {
 	text-align: left;
 }
+tr.itop-property-field-modified td {
+	background: #fbb;
+}
+tr.itop-property-field-modified td.prop_value.hover {
+	background: #fbb;
+}
+td.prop_value textarea, td.prop_value input[type=text]{
+	width: 98%;
+}
+td.prop_icon {
+	width: 20px;
+}
+
 .dashlet {
 	text-align:left;
 }
@@ -1193,6 +1206,15 @@ td.prop_value {
 .dashlet-content .display_block {
 	text-align:left;
 }
+.prop_apply .ui-icon-alert {
+	display: none;
+}
+.prop_apply .ui-state-error .ui-icon-alert {
+	display: block;
+}
+.ui-state-error .ui-icon-circle-check {
+	display: none;
+}
 .summary-details {
     float: right;
     margin-top: 5px;

+ 83 - 0
dictionaries/dictionary.itop.ui.php

@@ -353,6 +353,7 @@ Dict::Add('EN US', 'English', 'English', array(
 	'UI:Button:Search' => ' Search ',
 	'UI:Button:Query' => ' Query ',
 	'UI:Button:Ok' => 'Ok',
+	'UI:Button:Save' => 'Save',
 	'UI:Button:Cancel' => 'Cancel',
 	'UI:Button:Apply' => 'Apply',
 	'UI:Button:Back' => ' << Back ',
@@ -979,5 +980,87 @@ When associated with a trigger, each action is given an "order" number, specifyi
 	'UI:Button:MoveUp' => 'Move Up',
 	'UI:Button:MoveDown' => 'Move Down',
 
+	'UI:OQL:UnknownClassAndFix' => 'Unknown class "%1$s". You may try "%2$s" instead.',
+	'UI:OQL:UnknownClassNoFix' => 'Unknown class "%1$s"',
+
+	'UI:Dashboard:Edit' => 'Edit This Page...',
+	'UI:Dashboard:Revert' => 'Revert To Original Version...',
+	'UI:Dashboard:RevertConfirm' => 'Every changes made to the original version will be lost. Please confirm that you want to do this.',
+
+	'UI:DashletCreation:Title' => 'Create a new Dashlet',
+	'UI:DashletCreation:Dashboard' => 'Dashboard',
+	'UI:DashletCreation:DashletType' => 'Dashlet Type',
+	'UI:DashletCreation:EditNow' => 'Edit the Dashboard',
+
+	'UI:DashboardEdit:Title' => 'Dashboard Editor',
+	'UI:DashboardEdit:DashboardTitle' => 'Title',
+	'UI:DashboardEdit:Layout' => 'Layout',
+	'UI:DashboardEdit:Properties' => 'Dashboard Properties',
+	'UI:DashboardEdit:Dashlets' => 'Available Dashlets',	
+	'UI:DashboardEdit:DashletProperties' => 'Dashlet Properties',	
+
+	'UI:Form:Property' => 'Property',
+	'UI:Form:Value' => 'Value',
+
+	'UI:DashletPlainText:Label' => 'Text',
+	'UI:DashletPlainText:Description' => 'Plain text (no formatting)',
+	'UI:DashletPlainText:Prop-Text' => 'Text',
+	'UI:DashletPlainText:Prop-Text:Default' => 'Please enter some text here...',
+
+	'UI:DashletObjectList:Label' => 'Object list',
+	'UI:DashletObjectList:Description' => 'Object list dashlet',
+	'UI:DashletObjectList:Prop-Title' => 'Title',
+	'UI:DashletObjectList:Prop-Query' => 'Query',
+	'UI:DashletObjectList:Prop-Menu' => 'Menu',
+
+	'UI:DashletGroupBy:Prop-Title' => 'Title',
+	'UI:DashletGroupBy:Prop-Query' => 'Query',
+	'UI:DashletGroupBy:Prop-Style' => 'Style',
+	'UI:DashletGroupBy:Prop-GroupBy' => 'Group by...',
+	'UI:DashletGroupBy:Prop-GroupBy:Hour' => 'Hour of %1$s (0-23)',
+	'UI:DashletGroupBy:Prop-GroupBy:Month' => 'Month of %1$s (1 - 12)',
+	'UI:DashletGroupBy:Prop-GroupBy:DayOfWeek' => 'Day of week for %1$s',
+	'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Day of month for %1$s',
+	'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (hour)',
+	'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (month)',
+	'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (day of week)',
+	'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (day of month)',
+	'UI:DashletGroupBy:MissingGroupBy' => 'Please select the field on which the objects will be grouped together',
+
+	'UI:DashletGroupByPie:Label' => 'Pie Chart',
+	'UI:DashletGroupByPie:Description' => 'Pie Chart',
+	'UI:DashletGroupByBars:Label' => 'Bar Chart',
+	'UI:DashletGroupByBars:Description' => 'Bar Chart',
+	'UI:DashletGroupByTable:Label' => 'Group By (table)',
+	'UI:DashletGroupByTable:Description' => 'List (Grouped by a field)',
+
+	'UI:DashletHeaderStatic:Label' => 'Header',
+	'UI:DashletHeaderStatic:Description' => 'Displays an horizontal separator',
+	'UI:DashletHeaderStatic:Prop-Title' => 'Title',
+	'UI:DashletHeaderStatic:Prop-Title:Default' => 'Contacts',
+	'UI:DashletHeaderStatic:Prop-Icon' => 'Icon',
+
+	'UI:DashletHeaderDynamic:Label' => 'Header with statistics',
+	'UI:DashletHeaderDynamic:Description' => 'Header with stats (grouped by...)',
+	'UI:DashletHeaderDynamic:Prop-Title' => 'Title',
+	'UI:DashletHeaderDynamic:Prop-Title:Default' => 'Contacts',
+	'UI:DashletHeaderDynamic:Prop-Icon' => 'Icon',
+	'UI:DashletHeaderDynamic:Prop-Subtitle' => 'Subtitle',
+	'UI:DashletHeaderDynamic:Prop-Subtitle:Default' => 'Contacts',
+	'UI:DashletHeaderDynamic:Prop-Query' => 'Query',
+	'UI:DashletHeaderDynamic:Prop-GroupBy' => 'Group by',
+	'UI:DashletHeaderDynamic:Prop-Values' => 'Values',
+
+	'UI:DashletBadge:Label' => 'Badge',
+	'UI:DashletBadge:Description' => 'Object Icon with new/search',
+	'UI:DashletBadge:Prop-Class' => 'Class',
+
+	'DayOfWeek-Sunday' => 'Sunday',
+	'DayOfWeek-Monday' => 'Monday',
+	'DayOfWeek-Tuesday' => 'Tuesday',
+	'DayOfWeek-Wednesday' => 'Wednesday',
+	'DayOfWeek-Thursday' => 'Thursday',
+	'DayOfWeek-Friday' => 'Friday',
+	'DayOfWeek-Saturday' => 'Saturday',
 ));
 ?>

+ 83 - 0
dictionaries/fr.dictionary.itop.ui.php

@@ -235,6 +235,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'UI:Button:GlobalSearch' => 'Rechercher',
 	'UI:Button:Search' => 'Rechercher',
 	'UI:Button:Query' => ' Lancer la requête ',
+	'UI:Button:Save' => 'Sauver',
 	'UI:Button:Ok' => 'Ok',
 	'UI:Button:Cancel' => 'Annuler',
 	'UI:Button:Apply' => 'Appliquer',
@@ -823,5 +824,87 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
 	'UI:Button:MoveUp' => 'Monter',
 	'UI:Button:MoveDown' => 'Descendre',
 
+	'UI:OQL:UnknownClassAndFix' => 'La classe "%1$s" est inconnue. Essayez plutôt "%2$s".',
+	'UI:OQL:UnknownClassNoFix' => 'La classe "%1$s" est inconnue',
+
+	'UI:Dashboard:Edit' => 'Editer cette page...',
+	'UI:Dashboard:Revert' => 'Revenir à la version d\'origine...',
+	'UI:Dashboard:RevertConfirm' => 'Toutes modifications apportées à la version d\'origine seront perdues. Veuillez confirmer l\'opération.',
+
+	'UI:DashletCreation:Title' => 'Créer un Indicateur',
+	'UI:DashletCreation:Dashboard' => 'Tableau de bord',
+	'UI:DashletCreation:DashletType' => 'Type d\'Indicateur',
+	'UI:DashletCreation:EditNow' => 'Modifier le tableau de bord',
+
+	'UI:DashboardEdit:Title' => 'Editeur de tableau de bord',
+	'UI:DashboardEdit:DashboardTitle' => 'Titre',
+	'UI:DashboardEdit:Layout' => 'Mise en page',
+	'UI:DashboardEdit:Properties' => 'Propriétés du tableau de bord',
+	'UI:DashboardEdit:Dashlets' => 'Indicateurs',	
+	'UI:DashboardEdit:DashletProperties' => 'Propriétés de l\'Indicateur',	
+
+	'UI:Form:Property' => 'Propriété',
+	'UI:Form:Value' => 'Valeur',
+
+	'UI:DashletPlainText:Label' => 'Texte',
+	'UI:DashletPlainText:Description' => 'Text pur (pas de mise en forme)',
+	'UI:DashletPlainText:Prop-Text' => 'Texte',
+	'UI:DashletPlainText:Prop-Text:Default' => 'Veuillez saisir votre texte ici...',
+
+	'UI:DashletObjectList:Label' => 'Liste d\'objets',
+	'UI:DashletObjectList:Description' => 'Liste d\'objets',
+	'UI:DashletObjectList:Prop-Title' => 'Titre',
+	'UI:DashletObjectList:Prop-Query' => 'Requête OQL',
+	'UI:DashletObjectList:Prop-Menu' => 'Menu',
+
+	'UI:DashletGroupBy:Prop-Title' => 'Titre',
+	'UI:DashletGroupBy:Prop-Query' => 'Requête OQL',
+	'UI:DashletGroupBy:Prop-Style' => 'Style',
+	'UI:DashletGroupBy:Prop-GroupBy' => 'Grouper par',
+	'UI:DashletGroupBy:Prop-GroupBy:Hour' => 'Heure de %1$s (0-23)',
+	'UI:DashletGroupBy:Prop-GroupBy:Month' => 'Mois de %1$s (1 - 12)',
+	'UI:DashletGroupBy:Prop-GroupBy:DayOfWeek' => 'Jour de la semaine pour %1$s',
+	'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Jour du mois pour %1$s',
+	'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (heure)',
+	'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (mois)',
+	'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (jour de la semaine)',
+	'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (jour du mois)',
+	'UI:DashletGroupBy:MissingGroupBy' => 'Veuillez sélectionner le champ sur lequel les objets seront groupés',
+
+	'UI:DashletGroupByPie:Label' => 'Secteurs',
+	'UI:DashletGroupByPie:Description' => 'Graphique à secteur',
+	'UI:DashletGroupByBars:Label' => 'Barres',
+	'UI:DashletGroupByBars:Description' => 'Graphique en Barres',
+	'UI:DashletGroupByTable:Label' => 'Table',
+	'UI:DashletGroupByTable:Description' => 'Table',
+
+	'UI:DashletHeaderStatic:Label' => 'En-tête',
+	'UI:DashletHeaderStatic:Description' => 'En-tête présenté comme une barre horizontale',
+	'UI:DashletHeaderStatic:Prop-Title' => 'Titre',
+	'UI:DashletHeaderStatic:Prop-Title:Default' => 'Contacts',
+	'UI:DashletHeaderStatic:Prop-Icon' => 'Icône',
+
+	'UI:DashletHeaderDynamic:Label' => 'En-tête dynamique',
+	'UI:DashletHeaderDynamic:Description' => 'En-tête avec statistiques (regroupements)',
+	'UI:DashletHeaderDynamic:Prop-Title' => 'Titre',
+	'UI:DashletHeaderDynamic:Prop-Title:Default' => 'Contacts',
+	'UI:DashletHeaderDynamic:Prop-Icon' => 'Icône',
+	'UI:DashletHeaderDynamic:Prop-Subtitle' => 'Sous-titre',
+	'UI:DashletHeaderDynamic:Prop-Subtitle:Default' => 'Contacts',
+	'UI:DashletHeaderDynamic:Prop-Query' => 'Requête OQL',
+	'UI:DashletHeaderDynamic:Prop-GroupBy' => 'Grouper par',
+	'UI:DashletHeaderDynamic:Prop-Values' => 'Valeurs',
+
+	'UI:DashletBadge:Label' => 'Badge',
+	'UI:DashletBadge:Description' => 'Icône représentant une classe d\'objets, ainsi que des liens pour créer/rechercher',
+	'UI:DashletBadge:Prop-Class' => 'Classe',
+
+	'DayOfWeek-Sunday' => 'Dimanche',
+	'DayOfWeek-Monday' => 'Lundi',
+	'DayOfWeek-Tuesday' => 'Mardi',
+	'DayOfWeek-Wednesday' => 'Mercredi',
+	'DayOfWeek-Thursday' => 'Jeudi',
+	'DayOfWeek-Friday' => 'Vendredi',
+	'DayOfWeek-Saturday' => 'Samedi',
 ));
 ?>

+ 9 - 4
js/property_field.js

@@ -39,11 +39,15 @@ $(function()
 		{
 			if (this.bModified)
 			{
-				this.element.find(".prop_icon span.ui-icon").css({visibility: ''});
+				this.element.addClass("itop-property-field-modified");
+				this.element.find(".prop_icon span.ui-icon-circle-check").css({visibility: ''});
+				this.element.find(".prop_icon span.ui-icon-circle-close").css({visibility: ''});
 			}
 			else
 			{
-				this.element.find(".prop_icon span.ui-icon").css({visibility: 'hidden'});				
+				this.element.removeClass("itop-property-field-modified");
+				this.element.find(".prop_icon span.ui-icon-circle-check").css({visibility: 'hidden'});
+				this.element.find(".prop_icon span.ui-icon-circle-close").css({visibility: 'hidden'});
 			}
 		},
 	
@@ -52,6 +56,7 @@ $(function()
 		_destroy: function()
 		{
 			this.element.removeClass( "itop-property-field" );
+			this.element.removeClass("itop-property-field-modified");
 		},
 		
 		// _setOptions is called with a hash of all options that are changing
@@ -211,13 +216,13 @@ function ValidateWithPattern(sFieldId, bMandatory, sPattern, sFormId)
 	}
 	if (!bValid)
 	{
-		$('#v_'+sFieldId).html('<img style="vertical-align:middle;" src="'+GetAbsoluteUrlAppRoot()+'images/validation_error.png">');
+		$('#v_'+sFieldId).addClass('ui-state-error');
 		if (oFormValidation[sFormId] == undefined) oFormValidation[sFormId] = [];
 		oFormValidation[sFormId].push(sFieldId);
 	}
 	else
 	{
-		$('#v_'+sFieldId).html('');
+		$('#v_'+sFieldId).removeClass('ui-state-error');
 	}
 }
 

+ 1 - 0
pages/UI.php

@@ -2159,6 +2159,7 @@ EOF
 		///////////////////////////////////////////////////////////////////////////////////////////
 
 		default: // Menu node rendering (templates)
+		ApplicationMenu::LoadAdditionalMenus();
 		$oMenuNode = ApplicationMenu::GetMenuNode(ApplicationMenu::GetMenuIndexById(ApplicationMenu::GetActiveNodeId()));
 		if (is_object($oMenuNode))
 		{

+ 3 - 13
pages/ajax.render.php

@@ -672,18 +672,7 @@ try
 		
 		case 'dashboard_editor':
 		$sId = utils::ReadParam('id', '', false, 'raw_data');
-		
-		// Before searching for the menus make sure that all of them exist
-		// Build menus from module handlers
-		//
-		foreach(get_declared_classes() as $sPHPClass)
-		{
-			if (is_subclass_of($sPHPClass, 'ModuleHandlerAPI'))
-			{
-				$aCallSpec = array($sPHPClass, 'OnMenuCreation');
-				call_user_func($aCallSpec);
-			}
-		}
+		ApplicationMenu::LoadAdditionalMenus();
 		$idx = ApplicationMenu::GetMenuIndexById($sId);
 		$oMenu = ApplicationMenu::GetMenuNode($idx);
 		$oMenu->RenderEditor($oPage);
@@ -808,7 +797,7 @@ try
 		break;
 		
 		case 'add_dashlet':
-		$oForm = RuntimeDashboard::GetDashletCreationForm('');
+		$oForm = RuntimeDashboard::GetDashletCreationForm();
 		$aValues = $oForm->ReadParams();
 		
 		$sDashletClass = $aValues['dashlet_class'];
@@ -819,6 +808,7 @@ try
 			$oDashlet = new $sDashletClass(0);
 			$oDashlet->FromParams($aValues);
 
+			ApplicationMenu::LoadAdditionalMenus();
 			$index = ApplicationMenu::GetMenuIndexById($sMenuId);
 			$oMenu = ApplicationMenu::GetMenuNode($index);
 			$oMenu->AddDashlet($oDashlet);

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است