Ver código fonte

- Implementing the creation of objects on the fly when editing an extrernal key... still to be fully tested.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@955 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 14 anos atrás
pai
commit
3a53809b5c

+ 126 - 2
application/ajaxwebpage.class.inc.php

@@ -33,6 +33,9 @@ class ajax_page extends WebPage
      * @var Hash     
      */	  
 	protected $m_sReadyScript;
+	protected $m_sCurrentTab;
+	protected $m_sCurrentTabContainer;
+	protected $m_aTabs;
 	
     /**
      * constructor for the web page
@@ -44,8 +47,44 @@ class ajax_page extends WebPage
         $this->m_sReadyScript = "";
 		$this->add_header("Content-type: text/html; charset=utf-8");
 		$this->add_header("Cache-control: no-cache");
+		$this->m_sCurrentTabContainer = '';
+        $this->m_sCurrentTab = '';
+		$this->m_aTabs = array();
     }	
 
+	public function AddTabContainer($sTabContainer, $sPrefix = '')
+	{
+		$this->m_aTabs[$sTabContainer] = array('content' =>'', 'prefix' => $sPrefix);
+		$this->add("\$Tabs:$sTabContainer\$");
+	}
+	
+	public function AddToTab($sTabContainer, $sTabLabel, $sHtml)
+	{
+		if (!isset($this->m_aTabs[$sTabContainer]['content'][$sTabLabel]))
+		{
+			// Set the content of the tab
+			$this->m_aTabs[$sTabContainer]['content'][$sTabLabel] = $sHtml;
+		}
+		else
+		{
+			// Append to the content of the tab
+			$this->m_aTabs[$sTabContainer]['content'][$sTabLabel] .= $sHtml;
+		}
+	}
+
+	public function SetCurrentTabContainer($sTabContainer = '')
+	{
+		$sPreviousTabContainer = $this->m_sCurrentTabContainer;
+		$this->m_sCurrentTabContainer = $sTabContainer;
+		return $sPreviousTabContainer;
+	}
+
+	public function SetCurrentTab($sTabLabel = '')
+	{
+		$sPreviousTab = $this->m_sCurrentTab;
+		$this->m_sCurrentTab = $sTabLabel;
+		return $sPreviousTab;
+	}
 	
     /**
      * Echoes the content of the whole page
@@ -57,15 +96,88 @@ class ajax_page extends WebPage
         {
             header($s_header);
         }
+
+		if (count($this->m_aTabs) > 0)
+		{
+					$this->add_ready_script(
+<<<EOF
+			// The "tab widgets" to handle.
+			var tabs = $('div[id^=tabbedContent]');
+			    
+			// This selector will be reused when selecting actual tab widget A elements.
+			var tab_a_selector = 'ul.ui-tabs-nav a';
+			  
+			// Enable tabs on all tab widgets. The `event` property must be overridden so
+			// that the tabs aren't changed on click, and any custom event name can be
+			// specified. Note that if you define a callback for the 'select' event, it
+			// will be executed for the selected tab whenever the hash changes.
+			tabs.tabs({ event: 'change' });
+			  
+			// Define our own click handler for the tabs, overriding the default.
+			tabs.find( tab_a_selector ).click(function()
+			{
+				var state = {};
+						  
+				// Get the id of this tab widget.
+				var id = $(this).closest( 'div[id^=tabbedContent]' ).attr( 'id' );
+				  
+				// Get the index of this tab.
+				var idx = $(this).parent().prevAll().length;
+				
+				// Set the state!
+				state[ id ] = idx;
+				$.bbq.pushState( state );
+			});
+EOF
+);
+		}
+		// Render the tabs in the page (if any)
+		foreach($this->m_aTabs as $sTabContainerName => $aTabContainer)
+		{
+			$sTabs = '';
+			$m_aTabs = $aTabContainer['content'];
+			$sPrefix = $aTabContainer['prefix'];
+			$container_index = 0;
+			if (count($m_aTabs) > 0)
+			{
+			  $sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
+			  $sTabs .= "<ul>\n";
+			  // Display the unordered list that will be rendered as the tabs
+	          $i = 0;
+			  foreach($m_aTabs as $sTabName => $sTabContent)
+			  {
+			      $sTabs .= "<li><a href=\"#tab_{$sPrefix}$i\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
+			      $i++;
+	          }
+			  $sTabs .= "</ul>\n";
+			  // Now add the content of the tabs themselves
+			  $i = 0;
+			  foreach($m_aTabs as $sTabName => $sTabContent)
+			  {
+			      $sTabs .= "<div id=\"tab_{$sPrefix}$i\">".$sTabContent."</div>\n";
+			      $i++;
+	          }
+			  $sTabs .= "</div>\n<!-- end of tabs-->\n";
+	        }
+			$this->s_content = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $this->s_content);
+			$container_index++;
+		}
+	
         $s_captured_output = ob_get_contents();
         ob_end_clean();
         echo $this->s_content;
         echo $this->s_deferred_content;
+        if (count($this->a_scripts) > 0)
+        {
+            echo "<script type=\"text/javascript\">\n";
+            echo implode("\n", $this->a_scripts);
+            echo "\n</script>\n";
+        }
         if (!empty($this->m_sReadyScript))
         {
-	        echo "<script>\n";
+	        echo "<script type=\"text/javascript\">\n";
 	        echo $this->m_sReadyScript; // Ready Scripts are output as simple scripts
-	        echo "</script>\n";
+	        echo "\n</script>\n";
         }
 		if (trim($s_captured_output) != "")
         {
@@ -82,6 +194,18 @@ class ajax_page extends WebPage
     public function small_p($sText)
     {
 	}
+
+    public function add($sHtml)
+    {
+        if (!empty($this->m_sCurrentTabContainer) && !empty($this->m_sCurrentTab))
+        {
+            $this->AddToTab($this->m_sCurrentTabContainer, $this->m_sCurrentTab, $sHtml);
+        }
+        else
+        {
+            parent::add($sHtml);
+        }
+    }
 	
 	/**
 	 * Adds a script to be executed when the DOM is ready (typical JQuery use)

+ 130 - 21
application/cmdbabstract.class.inc.php

@@ -1032,10 +1032,9 @@ EOF
 	{
 		static $iInputId = 0;
 		$sFieldPrefix = '';
-		if (isset($aArgs['prefix']))
-		{
-			$sFieldPrefix = $aArgs['prefix'];
-		}
+		$sFormPrefix = isset($aArgs['formPrefix']) ? $aArgs['formPrefix'] : '';
+		$sFieldPrefix = isset($aArgs['prefix']) ? $sFormPrefix.$aArgs['prefix'] : $sFormPrefix;
+
 		if (isset($aArgs[$sAttCode]) && empty($value))
 		{
 			// default value passed by the context (either the app context of the operation)
@@ -1136,7 +1135,7 @@ EOF
 					if (count($aAllowedValues) >= $iMaxComboLength)
 					{
 						// too many choices, use an autocomplete
-						$oWidget = new UIAutoCompleteWidget($sAttCode, $sClass, $oAttDef->GetLabel(), $aAllowedValues, $value, $iId, $sNameSuffix, $sFieldPrefix);
+						$oWidget = new UIAutoCompleteWidget($sAttCode, $sClass, $oAttDef->GetLabel(), $aAllowedValues, $value, $iId, $sNameSuffix, $sFieldPrefix, $sFormPrefix);
 						$sHTMLValue = $oWidget->Display($oPage, $aArgs);
 						
 					}
@@ -1206,12 +1205,12 @@ EOF
 				{
 					$sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number
 				}
-				$oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $sMandatory, sFormId, $sNullValue) } );"); // Bind to a custom event: validate
+				$oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $sMandatory, sFormId, $sNullValue) } );\n"); // Bind to a custom event: validate
 			}
 			$aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that depend on the current one
 			if (count($aDependencies) > 0)
 			{
-				$oPage->add_ready_script("$('#$iId').bind('change', function(evt, sFormId) { return UpdateDependentFields(['".implode("','", $aDependencies)."']) } );"); // Bind to a custom event: validate
+				$oPage->add_ready_script("$('#$iId').bind('change', function(evt, sFormId) { return oWizardHelper{$sFormPrefix}.UpdateDependentFields(['".implode("','", $aDependencies)."']) } );\n"); // Bind to a custom event: validate
 			}
 		}
 		return "<div>{$sHTMLValue}</div>";
@@ -1219,9 +1218,14 @@ EOF
 	
 	public function DisplayModifyForm(WebPage $oPage, $aExtraParams = array())
 	{
-		static $iGlobalFormId = 0;
+		static $iGlobalFormId = 1;
 		$iGlobalFormId++;
-		$this->m_iFormId = $iGlobalFormId;
+		$sPrefix = '';
+		if (isset($aExtraParams['formPrefix']))
+		{
+			$sPrefix = $aExtraParams['formPrefix'];
+		}
+		$this->m_iFormId = $sPrefix.$iGlobalFormId;
 		$sClass = get_class($this);
 		$oAppContext = new ApplicationContext();
 		$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
@@ -1238,7 +1242,7 @@ EOF
 		}
 		$oPage->add("<form action=\"$sFormAction\" id=\"form_{$this->m_iFormId}\" enctype=\"multipart/form-data\" method=\"post\" onSubmit=\"return CheckFields('form_{$this->m_iFormId}', true)\">\n");
 
-		$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB);
+		$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, $sPrefix);
 		$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
 		$oPage->SetCurrentTab(Dict::S('UI:PropertiesTab'));
 //		$aDetailsList = $this->FLattenZList(MetaModel::GetZListItems($sClass, 'details'));
@@ -1310,7 +1314,7 @@ EOF
 										{
 											$sValue = $this->Get($sAttCode);
 											$sDisplayValue = $this->GetEditValue($sAttCode);
-											$aArgs = array('this' => $this);
+											$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
 											$sInputId = $this->m_iFormId.'_'.$sAttCode;
 											$sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
 											$aFieldsMap[$sAttCode] = $sInputId;
@@ -1343,8 +1347,10 @@ EOF
 		}
 
 		// Now display the relations, one tab per relation
-
-		$this->DisplayBareRelations($oPage, true); // Edit mode
+		if (!isset($aExtraParams['noRelations']))
+		{
+			$this->DisplayBareRelations($oPage, true); // Edit mode
+		}
 
 		$oPage->SetCurrentTab('');
 		$oPage->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n");
@@ -1359,16 +1365,20 @@ EOF
 			// The object already exists in the database, it's modification
 			$oPage->add("<input type=\"hidden\" name=\"id\" value=\"$iKey\">\n");
 			$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"apply_modify\">\n");			
-			$oPage->add("<button type=\"button\" class=\"action\" onClick=\"BackToDetails('$sClass', $iKey)\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
+//			$oPage->add("<button type=\"button\" id=\"btn_cancel_{$sPrefix}\" class=\"action\" onClick=\"BackToDetails('$sClass', $iKey)\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
+			$oPage->add("<button type=\"button\" class=\"action cancel\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
 			$oPage->add("<button type=\"submit\" class=\"action\"><span>".Dict::S('UI:Button:Apply')."</span></button>\n");
 		}
 		else
 		{
 			// The object does not exist in the database it's a creation
 			$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"apply_new\">\n");			
-			$oPage->add("<button type=\"button\" class=\"action\" onClick=\"BackToList('$sClass')\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
+//			$oPage->add("<button type=\"button\" id=\"btn_cancel_{$sPrefix}\" class=\"action\" onClick=\"BackToDetails('$sClass', $iKey)\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
+			$oPage->add("<button type=\"button\" class=\"action cancel\">".Dict::S('UI:Button:Cancel')."</button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
 			$oPage->add("<button type=\"submit\" class=\"action\"><span>".Dict::S('UI:Button:Create')."</span></button>\n");
 		}
+		// Hook the cancel button via jQuery so that it can be unhooked easily as well if needed
+		$oPage->add_ready_script("$('#form_{$this->m_iFormId} button.cancel').click( function() { BackToDetails('$sClass', $iKey)} );");
 		$oPage->add("</form>\n");
 		
 		$iFieldsCount = count($aFieldsMap);
@@ -1377,15 +1387,16 @@ EOF
 		$oPage->add_script(
 <<<EOF
 		// Create the object once at the beginning of the page...
-		var oWizardHelper = new WizardHelper('$sClass');
-		oWizardHelper.SetFieldsMap($sJsonFieldsMap);
-		oWizardHelper.SetFieldsCount($iFieldsCount);
+		var oWizardHelper$sPrefix = new WizardHelper('$sClass', '$sPrefix');
+		oWizardHelper$sPrefix.SetFieldsMap($sJsonFieldsMap);
+		oWizardHelper$sPrefix.SetFieldsCount($iFieldsCount);
 EOF
 );
 		$oPage->add_ready_script(
 <<<EOF
 		// Starts the validation when the page is ready
 		CheckFields('form_{$this->m_iFormId}', false);
+
 EOF
 );
 	}
@@ -1399,9 +1410,12 @@ EOF
 		
 		if ($oObjectToClone == null)
 		{
-			$sTargetState = MetaModel::GetDefaultState($sClass);
 			$oObj = MetaModel::NewObject($sClass);
-			$oObj->Set($sStateAttCode, $sTargetState);
+			if (!empty($sStateAttCode))
+			{
+				$sTargetState = MetaModel::GetDefaultState($sClass);
+				$oObj->Set($sStateAttCode, $sTargetState);
+			}
 		}
 		else
 		{
@@ -1439,7 +1453,7 @@ EOF
 				}
 			}
 		}
-		return $oObj->DisplayModifyForm( $oPage, $aExtraParams = array());
+		return $oObj->DisplayModifyForm( $oPage, $aExtraParams);
 	}
 
 	protected static function ProcessZlist($aList, $aDetails, $sCurrentTab, $sCurrentCol, $sCurrentSet)
@@ -1663,5 +1677,100 @@ EOF
 			return $sContextParam;
 		}
 	}
+	
+	/**
+	 * Updates the object from the POSTed parameters
+	 */
+	function UpdateObject($sFormPrefix = '')
+	{
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
+			{
+				$aLinks = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", '');
+				$sLinkedClass = $oAttDef->GetLinkedClass();
+				$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
+				$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
+				$oLinkedSet = DBObjectSet::FromScratch($sLinkedClass);
+				if (is_array($aLinks))
+				{
+					foreach($aLinks as $id => $aData)
+					{
+						if (is_numeric($id))
+						{
+							if ($id < 0)
+							{
+								// New link to be created, the opposite of the id (-$id) is the ID of the remote object
+								$oLink = MetaModel::NewObject($sLinkedClass);
+								$oLink->Set($sExtKeyToRemote, -$id);
+								$oLink->Set($sExtKeyToMe, $this->GetKey());
+							}
+							else
+							{
+								// Existing link, potentially to be updated...
+								$oLink = MetaModel::GetObject($sLinkedClass, $id);
+							}
+							// Now populate the attributes
+							foreach($aData as $sName => $value)
+							{
+								if (MetaModel::IsValidAttCode($sLinkedClass, $sName))
+								{
+									$oLinkAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sName);
+									if ($oLinkAttDef->IsWritable())
+									{
+										$oLink->Set($sName, $value);
+									}
+								}
+							}
+							$oLinkedSet->AddObject($oLink);
+						}
+					}
+				}
+				$this->Set($sAttCode, $oLinkedSet);
+			}
+			else if ($oAttDef->IsWritable())
+			{
+				$iFlags = $this->GetAttributeFlags($sAttCode);
+				if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY))
+				{
+					// Non-visible, or read-only attribute, do nothing
+				}
+				elseif ($oAttDef->GetEditClass() == 'Document')
+				{
+					// There should be an uploaded file with the named attr_<attCode>
+					$oDocument = utils::ReadPostedDocument("file_{$sFormPrefix}{$sAttCode}");
+					if (!$oDocument->IsEmpty())
+					{
+						// A new file has been uploaded
+						$this->Set($sAttCode, $oDocument);
+					}
+				}
+				elseif ($oAttDef->GetEditClass() == 'One Way Password')
+				{
+					// Check if the password was typed/changed
+					$bChanged = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_changed", false);
+					if ($bChanged)
+					{
+						// The password has been changed or set
+						$rawValue = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null);
+						$this->Set($sAttCode, $rawValue);
+					}
+				}
+				else
+				{
+					$rawValue = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null);
+					if (!is_null($rawValue))
+					{
+						$aAttributes[$sAttCode] = trim($rawValue);
+						$previousValue = $this->Get($sAttCode);
+						if ($previousValue !== $aAttributes[$sAttCode])
+						{
+							$this->Set($sAttCode, $aAttributes[$sAttCode]);
+						}
+					}
+				}
+			}
+		}
+	}
 }
 ?>

+ 72 - 4
application/ui.autocompletewidget.class.inc.php

@@ -69,7 +69,7 @@ class UIAutoCompleteWidget
 	protected $iId;
 	protected $sTitle;
 	
-	public function __construct($sAttCode, $sClass, $sTitle, $aAllowedValues, $value, $iInputId, $sNameSuffix = '', $sFieldPrefix = '')
+	public function __construct($sAttCode, $sClass, $sTitle, $aAllowedValues, $value, $iInputId, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
 	{
 		self::$iWidgetIndex++;
 		$this->sAttCode = $sAttCode;
@@ -82,6 +82,7 @@ class UIAutoCompleteWidget
 		$this->sFieldPrefix = $sFieldPrefix;
 		$this->sTargetClass = $this->oAttDef->GetTargetClass();
 		$this->sTitle = $sTitle;
+		$this->sFormPrefix = $sFormPrefix;
 	}
 	
 	/**
@@ -100,17 +101,25 @@ class UIAutoCompleteWidget
 		{
 			$sDisplayValue = $this->GetObjectName($this->value);
 		}
+		$bCreate = UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY);
 		$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
+		$sFormPrefix = $this->sFormPrefix;
 		$oPage->add_ready_script(
 <<<EOF
-	oACWidget_{$this->iId} = new AutocompleteWidget('$this->iId', '$this->sClass', '$this->sAttCode', '$this->sNameSuffix');
+	oACWidget_{$this->iId} = new AutocompleteWidget('$this->iId', '$this->sClass', '$this->sAttCode', '$this->sNameSuffix', oWizardHelper$sFormPrefix);
 	oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
 EOF
 );
 		$iMinChars = $this->oAttDef->GetMinAutoCompleteChars();
 
 		// the input for the auto-complete
-		$sHTMLValue = "<input count=\"".count($this->aAllowedValues)."\" type=\"text\" id=\"label_$this->iId\" size=\"30\" maxlength=\"$iFieldSize\" value=\"$sDisplayValue\"/>&nbsp;<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.Search();\"><img style=\"border:0;vertical-align:middle;\" src=\"../images/mini_search.gif\" /></a>&nbsp;<span id=\"v_{$this->iId}\"></span>";
+		$sHTMLValue = "<input count=\"".count($this->aAllowedValues)."\" type=\"text\" id=\"label_$this->iId\" size=\"30\" maxlength=\"$iFieldSize\" value=\"$sDisplayValue\"/>&nbsp;";
+		$sHTMLValue .= "<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.Search();\"><img style=\"border:0;vertical-align:middle;\" src=\"../images/mini_search.gif\" /></a>&nbsp;";
+		if ($bCreate)
+		{
+			$sHTMLValue .= "<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.CreateObject();\"><img style=\"border:0;vertical-align:middle;\" src=\"../images/mini_add.gif\" /></a>&nbsp;";
+		}
+		$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span>";
 
 		// another hidden input to store & pass the object's Id
 		$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"attr_{$this->sFieldPrefix}{$this->sAttCode}{$this->sNameSuffix}\" value=\"$this->value\" />\n";
@@ -120,7 +129,12 @@ EOF
 		$oPage->add_ready_script("\$('#label_$this->iId').blur(function() { $(this).search(); } );");
 		$oPage->add_ready_script("\$('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('$this->iId', event, data, formatted); } );");
 		$oPage->add_ready_script("\$('#ac_dlg_$this->iId').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$this->sTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });\n");
+		$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$this->sTitle'});\n");
 		$oPage->add_at_the_end($this->GetSearchDialog($oPage)); // To prevent adding forms inside the main form
+		if ($bCreate)
+		{
+			$oPage->add_at_the_end($this->GetCreationDialog($oPage)); // To prevent adding forms inside the main form
+		}
 
 		return $sHTMLValue;
 	}
@@ -143,12 +157,29 @@ EOF
 		$sHTML .= "</form>\n";
 		$sHTML .= '</div></div></div>';
 
-		$oPage->add_ready_script("$('#fs_{$this->iId}').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoSearchObjects);");
+		$oPage->add_ready_script("$('#fs_{$this->iId}').bind('submit.uiAutocomplete', oACWidget_{$this->iId}.DoSearchObjects);");
 		$oPage->add_ready_script("$('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes);");
 
 		return $sHTML;
 	}
 
+	
+	protected function GetCreationDialog(WebPage $oPage)
+	{
+		$sHTML = '<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">';
+
+		//$sHTML .= "<form id=\"fcr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoCreate();\">\n";
+		//cmdbAbstractObject::DisplayCreationForm($oPage, 'Person', null, array());
+		//$sHTML .= "<input type=\"button\" id=\"btn_create_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ac_create_{$this->iId}').dialog('close');\">&nbsp;&nbsp;";
+		//$sHTML .= "<input type=\"button\" id=\"btn_create_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:New')."\"  onClick=\"oACWidget_{$this->iId}.DoCreate();\">";
+		//$sHTML .= "</form>\n";
+		$sHTML .= '</div></div></div>';
+
+		//$oPage->add_ready_script("$('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes);");
+
+		return $sHTML;
+	}
+
 	/**
 	 * Search for objects to be selected
 	 * @param WebPage $oP The page used for the output (usually an AjaxWebPage)
@@ -181,5 +212,42 @@ EOF
 		$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId);
 		return $oObj->GetName();
 	}
+	
+	/**
+	 * Get the form to create a new object of the 'target' class
+	 */
+	public function GetObjectCreationForm(WebPage $oPage)
+	{
+		$oPage->add("<h1>".MetaModel::GetClassIcon($this->sTargetClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."</h1>\n");
+	 	cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, null, array(), array('formPrefix' => $this->iId, 'noRelations' => true));	
+		$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
+		$oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject);");
+	}
+
+	/**
+	 * Get the form to create a new object of the 'target' class
+	 */
+	public function DoCreateObject($oPage)
+	{
+		$oObj = MetaModel::NewObject($this->sTargetClass);
+		$oObj->UpdateObject($this->sFormPrefix.$this->iId);
+		$oMyChange = MetaModel::NewObject("CMDBChange");
+		$oMyChange->Set("date", time());
+		if (UserRights::IsImpersonated())
+		{
+			$sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser());
+		}
+		else
+		{
+			$sUserString = UserRights::GetUser();
+		}
+		$oMyChange->Set("userinfo", $sUserString);
+		$iChangeId = $oMyChange->DBInsert();
+		$oObj->DBInsertTracked($oMyChange);
+	
+		return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
+		
+		//return array('name' => 'test', 'id' => '42');
+	}
 }
 ?>

+ 5 - 0
application/wizardhelper.class.inc.php

@@ -227,6 +227,11 @@ class WizardHelper
 		return $this->m_aData['m_sClass'];
 	}
 	
+	public function GetFormPrefix()
+	{
+		return $this->m_aData['m_sFormPrefix'];
+	}
+	
 	public function GetIdForField($sFieldName)
 	{
 		$sResult = '';

BIN
images/mini_add.gif


+ 114 - 19
js/autocompletewidget.js

@@ -13,7 +13,7 @@
 //   along with this program; if not, write to the Free Software
 //   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
+function AutocompleteWidget(id, sClass, sAttCode, sSuffix, oWizHelper)
 {
 	this.id = id;
 	this.sClass = sClass;
@@ -21,7 +21,10 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
 	this.sSuffix = sSuffix;
 	this.emptyHtml = ''; // content to be displayed when the search results are empty (when opening the dialog) 
 	this.emptyOnClose = true; // Workaround for the JQuery dialog being very slow when opening and closing if the content contains many INPUT tags
+	this.oWizardHelper = oWizHelper;
+	this.ajax_request = null;
 	var me = this;
+	
 	this.Init = function()
 	{
 		// make sure that the form is clean
@@ -30,6 +33,15 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
 		$('#'+this.id+'_linksToRemove').val('');
 	}
 	
+	this.StopPendingRequest = function()
+	{
+		if (this.ajax_request)
+		{
+			this.ajax_request.Abort();
+			this.ajax_request = null;
+		}
+	}
+	
 	this.Search = function()
 	{
 		$('#ac_dlg_'+me.id).dialog('open');
@@ -66,8 +78,6 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
 		}
 	}
 	
-	var ajax_request = null;
-	
 	this.DoSearchObjects = function(id)
 	{
 		var theMap = { sAttCode: me.sAttCode,
@@ -86,8 +96,8 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
 			}
 		);
 		
-		oWizardHelper.UpdateWizard();
-		theMap['json'] = oWizardHelper.ToJSON();
+		me.oWizardHelper.UpdateWizard();
+		theMap['json'] = me.oWizardHelper.ToJSON();
 		
 		theMap['sRemoteClass'] = theMap['class'];  // swap 'class' (defined in the form) and 'remoteClass'
 		theMap['class'] = me.sClass;
@@ -100,14 +110,10 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
 
 		// Make sure that we cancel any pending request before issuing another
 		// since responses may arrive in arbitrary order
-		if (ajax_request != null)
-		{
-			ajax_request.abort();
-			ajax_request = null;
-		}
+		this.StopPendingRequest();
 		
 		// Run the query and display the results
-		ajax_request = $.post( 'ajax.render.php', theMap, 
+		this.ajax_request = $.post( 'ajax.render.php', theMap, 
 			function(data)
 			{
 				$(sSearchAreaId).html(data);
@@ -115,7 +121,7 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
 				$(sSearchAreaId+' .listResults').tablesorter( { headers: {0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
 				$('#fr_'+me.id+' input:radio').click(function() { me.UpdateButtons(); });
 				me.UpdateButtons();
-				ajax_request = null;
+				me.ajax_request = null;
 			},
 			'html'
 		);
@@ -140,21 +146,18 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
 	
 		// Make sure that we cancel any pending request before issuing another
 		// since responses may arrive in arbitrary order
-		if (ajax_request != null)
-		{
-			ajax_request.abort();
-			ajax_request = null;
-		}
+		this.StopPendingRequest();
 		
 		// Run the query and get the result back directly in JSON
-		ajax_request = $.post( 'ajax.render.php', theMap, 
+		this.ajax_request = $.post( 'ajax.render.php', theMap, 
 			function(data)
 			{
 				$('#label_'+me.id).val(data.name);
 				$('#label_'+me.id).removeClass('ac_loading');
 				$('#'+me.id).val(iObjectId);
 				$('#'+me.id).trigger('validate');
-				ajax_request = null;
+				$('#label_'+me.id).focus();
+				me.ajax_request = null;
 			},
 			'json'
 		);
@@ -174,4 +177,96 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
 		}
 		$('#label_'+me.id).focus();
 	}
+	
+	this.CreateObject = function(oWizHelper)
+	{
+		// Query the server to get the form to create a target object
+		$('#label_'+me.id).addClass('ac_loading');
+		me.oWizardHelper.UpdateWizard();
+		var theMap = { sAttCode: me.sAttCode,
+				   iInputId: me.id,
+				   sSuffix: me.sSuffix,
+				   'class': me.sClass,
+				   'json': me.oWizardHelper.ToJSON(),
+				   operation: 'objectCreationForm'
+				 }
+	
+		// Make sure that we cancel any pending request before issuing another
+		// since responses may arrive in arbitrary order
+		this.StopPendingRequest();
+		
+		// Run the query and get the result back directly in JSON
+		this.ajax_request = $.post( 'ajax.render.php', theMap, 
+			function(data)
+			{
+				$('#dcr_'+me.id).html(data);
+				// Adjust the height of the dialog
+				$('#ac_create_'+me.id).dialog('open');
+				var h = $('#ac_create_'+me.id+' .wizContainer').outerHeight();
+				$('#ac_create_'+me.id).dialog( "option", "height", h+55 ); // space for dialog title and padding...				
+				$('#ac_create_'+me.id).dialog( "option", "close", function() { $('#label_'+me.id).removeClass('ac_loading'); $('#label_'+me.id).focus(); } );			
+				// Modify the action of the cancel button
+				$('#ac_create_'+me.id+' button.cancel').unbind('click').click( me.CloseCreateObject );
+				me.ajax_request = null;
+			},
+			'html'
+		);
+	}
+	
+	this.CloseCreateObject = function()
+	{
+		$('#ac_create_'+me.id).dialog( "close" );
+		$('#dcr_'+me.id).html('');
+	}
+	
+	this.DoCreateObject = function()
+	{
+		var sFormId = $('#dcr_'+me.id+' form').attr('id');
+		if (CheckFields(sFormId, true))
+		{
+			$('#'+sFormId).block();
+			var theMap = { sAttCode: me.sAttCode,
+					   iInputId: me.id,
+					   sSuffix: me.sSuffix,
+					   'class': me.sClass,
+					   'json': me.oWizardHelper.ToJSON()
+					 }
+
+			// Gather the values from the form
+			// Gather the parameters from the search form
+			$('#'+sFormId+' :input').each(
+				function(i)
+				{
+					if (this.name != '')
+					{
+						theMap[this.name] = this.value;
+					}
+				}
+			);
+			// Override the 'operation' code
+			theMap['operation'] = 'doCreateObject';
+			theMap['class'] = me.sClass;
+
+			$('#ac_create_'+me.id).dialog('close');
+			
+			// Make sure that we cancel any pending request before issuing another
+			// since responses may arrive in arbitrary order
+			this.StopPendingRequest();
+			
+			// Run the query and get the result back directly in JSON
+			this.ajax_request = $.post( 'ajax.render.php', theMap, 
+				function(data)
+				{
+					$('#label_'+me.id).val(data.name);
+					$('#'+me.id).val(data.id);
+					$('#'+me.id).trigger('validate');
+					$('#label_'+me.id).removeClass('ac_loading');
+					$('#label_'+me.id).focus();
+					me.ajax_request = null;
+				},
+				'json'
+			);
+		}
+		return false; // do NOT submit the form
+	}
 }

+ 2 - 0
js/forms-json-utils.js

@@ -231,6 +231,7 @@ function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue)
 	setTimeout(function(){ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue);}, 500);
 }
 
+/*
 function UpdateDependentFields(aFieldNames)
 {
 	//console.log('UpdateDependentFields:');
@@ -248,6 +249,7 @@ function UpdateDependentFields(aFieldNames)
 	}
 	oWizardHelper.AjaxQueryServer();
 }
+*/
 
 function ResetPwd(id)
 {

+ 19 - 2
js/wizardhelper.js

@@ -1,5 +1,5 @@
 // Wizard Helper JavaScript class to communicate with the WizardHelper PHP class
-function WizardHelper(sClass)
+function WizardHelper(sClass, sFormPrefix)
 {
 	this.m_oData = { 'm_sClass' : '',
 					 'm_oFieldsMap': {},
@@ -8,7 +8,8 @@ function WizardHelper(sClass)
 					 'm_aAllowedValuesRequested': [],
 					 'm_oDefaultValue': {},
 					 'm_oAllowedValues': {},
-					 'm_iFieldsCount' : 0
+					 'm_iFieldsCount' : 0,
+					 'm_sFormPrefix' : sFormPrefix
 					};
 	this.m_oData.m_sClass = sClass;
 	
@@ -135,4 +136,20 @@ function WizardHelper(sClass)
 		this.m_oData.m_oCurrentValues[sFieldCode] = value;
 		return value;		
 	}
+
+	this.UpdateDependentFields = function(aFieldNames)
+	{
+		index = 0;
+		this.ResetQuery();
+		this.UpdateWizard();
+		while(index < aFieldNames.length )
+		{
+			sAttCode = aFieldNames[index];
+			sFieldId = this.GetFieldId(sAttCode);
+			$('#v_'+sFieldId).html('<img src="../images/indicator.gif" />');
+			this.RequestAllowedValues(sAttCode);
+			index++;
+		}
+		this.AjaxQueryServer();
+	}
 }

+ 33 - 3
pages/ajax.render.php

@@ -75,7 +75,6 @@ switch($operation)
 	$sAttCode = utils::ReadParam('sAttCode', '');
 	$iInputId = utils::ReadParam('iInputId', '');
 	$sSuffix = utils::ReadParam('sSuffix', '');
-	// To do: retrieve the object under construction & use it to filter the allowed values
 	$sJson = utils::ReadParam('json', '');
 	$oWizardHelper = WizardHelper::FromJSON($sJson);
 	$oObj = $oWizardHelper->GetTargetObject();
@@ -85,6 +84,36 @@ switch($operation)
 	break;
 
 	// ui.autocompletewidget
+	case 'objectCreationForm':
+	$sTargetClass = utils::ReadParam('sRemoteClass', '');
+	$sAttCode = utils::ReadParam('sAttCode', '');
+	$iInputId = utils::ReadParam('iInputId', '');
+	$sSuffix = utils::ReadParam('sSuffix', '');
+	$sJson = utils::ReadParam('json', '');
+	$oWizardHelper = WizardHelper::FromJSON($sJson);
+	$oObj = $oWizardHelper->GetTargetObject();
+	$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array('this' => $oObj));
+	$oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', $aAllowedValues, $oObj->Get($sAttCode), $iInputId, $sSuffix, '');
+	$oWidget->GetObjectCreationForm($oPage);
+	break;
+	
+	// ui.autocompletewidget
+	case 'doCreateObject':
+	$sTargetClass = utils::ReadParam('sRemoteClass', '');
+	$sAttCode = utils::ReadParam('sAttCode', '');
+	$iInputId = utils::ReadParam('iInputId', '');
+	$sSuffix = utils::ReadParam('sSuffix', '');
+	$sJson = utils::ReadParam('json', '');
+	$oWizardHelper = WizardHelper::FromJSON($sJson);
+	$oObj = $oWizardHelper->GetTargetObject();
+	$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array('this' => $oObj));
+	// The iInputId of the autocomplete is the prefix for the form used to create the target object
+	$oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', $aAllowedValues, null, $iInputId, $sSuffix, $oWizardHelper->GetFormPrefix());
+	$aResult = $oWidget->DoCreateObject($oPage);
+	echo json_encode($aResult);	
+	break;
+	
+	// ui.autocompletewidget
 	case 'getObjectName':
 	$sTargetClass = utils::ReadParam('sTargetClass', '');
 	$sAttCode = utils::ReadParam('sAttCode', '');
@@ -126,6 +155,7 @@ switch($operation)
 		$oWizardHelper->SetDefaultValue($sAttCode, $defaultValue);
 		$oObj->Set($sAttCode, $defaultValue);
 	}
+	$sFormPrefix = $oWizardHelper->GetFormPrefix();
 	foreach($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode)
 	{
 		$sId = $oWizardHelper->GetIdForField($sAttCode);
@@ -138,13 +168,13 @@ switch($operation)
 			$displayValue = $oObj->GetEditValue($sAttCode);
 			$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
 			$iFlags = MetaModel::GetAttributeFlags($sClass, $oObj->GetState(), $sAttCode);
-			$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', $iFlags, array('this' => $oObj));
+			$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', $iFlags, array('this' => $oObj, 'formPrefix' => $sFormPrefix));
 			// Make sure that we immediatly validate the field when we reload it
 			$oPage->add_ready_script("$('#$sId').trigger('validate');");
 			$oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue);
 		}
 	}
-	$oPage->add("<script type=\"text/javascript\">\noWizardHelper.m_oData=".$oWizardHelper->ToJSON().";\noWizardHelper.UpdateFields();\n</script>\n");
+	$oPage->add_script("oWizardHelper{$sFormPrefix}.m_oData=".$oWizardHelper->ToJSON().";\noWizardHelper{$sFormPrefix}.UpdateFields();\n");
 	break;
 		
 	case 'ajax':