瀏覽代碼

- New implementation of the n-n link edition widget... in progress.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@652 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 15 年之前
父節點
當前提交
ccdf7ab3da

+ 24 - 30
application/cmdbabstract.class.inc.php

@@ -115,7 +115,7 @@ abstract class cmdbAbstractObject extends CMDBObject
 		$oSingletonFilter->AddCondition('id', $this->GetKey(), '=');
 		$oBlock = new MenuBlock($oSingletonFilter, 'popup', false);
 		$oBlock->Display($oPage, -1);
-		$oPage->add("<div class=\"page_header\"><h1><img src=\"".$this->GetIcon()."\" style=\"margin-right:10px;margin-top: -16px;vertical-align:middle;\">\n");
+		$oPage->add("<div class=\"page_header\"><h1>".$this->GetIcon()."&nbsp;\n");
 		$oPage->add(MetaModel::GetName(get_class($this)).": <span class=\"hilite\">".$this->GetName()."</span></h1>\n");
 		$oPage->add("</div>\n");
 	}
@@ -174,7 +174,7 @@ abstract class cmdbAbstractObject extends CMDBObject
 					$sLinkedClass = $oAttDef->GetLinkedClass();
 					$oLinkingAttDef = 	MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote());
 					$sTargetClass = $oLinkingAttDef->GetTargetClass();
-					$oPage->p("<img src=\"".MetaModel::GetClassIcon($sTargetClass)."\" style=\"vertical-align:middle;\">&nbsp;".$oAttDef->GetDescription());
+					$oPage->p(MetaModel::GetClassIcon($sTargetClass)."&nbsp;".$oAttDef->GetDescription());
 
 					$sValue = $this->Get($sAttCode);
 					$sDisplayValue = $this->GetEditValue($sAttCode);
@@ -211,10 +211,10 @@ abstract class cmdbAbstractObject extends CMDBObject
 							'object_id' => $this->GetKey(),
 							'target_attr' => $oAttDef->GetExtKeyToRemote(),
 							'view_link' => false,
-							'menu' => $bMenu,
+							'menu' => false,
 						);
 				}
-				$oPage->p("<img src=\"".MetaModel::GetClassIcon($sTargetClass)."\" style=\"vertical-align:middle;\">&nbsp;".$oAttDef->GetDescription());
+				$oPage->p(MetaModel::GetClassIcon($sTargetClass)."&nbsp;".$oAttDef->GetDescription());
 				$oBlock = new DisplayBlock($this->Get($sAttCode)->GetFilter(), 'list', false);
 				$oBlock->Display($oPage, 'rel_'.$sAttCode, $aParams);
 			}
@@ -782,7 +782,7 @@ abstract class cmdbAbstractObject extends CMDBObject
 		$sHtml .= "<form id=\"form{$iSearchFormId}\">\n";
 		$sHtml .= "<h2>".Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo)."</h2>\n";
 		$index = 0;
-		$sHtml .= "<table>\n";
+		$sHtml .= "<p>\n";
 		$aFilterCriteria = $oSet->GetFilter()->GetCriteria();
 		$aMapCriteria = array();
 		foreach($aFilterCriteria as $aCriteria)
@@ -793,14 +793,7 @@ abstract class cmdbAbstractObject extends CMDBObject
 		foreach($aList as $sFilterCode)
 		{
 			$oAppContext->Reset($sFilterCode); // Make sure the same parameter will not be passed twice
-			if (($index % $numCols) == 0)
-			{
-				if ($index != 0)
-				{
-					$sHtml .= "</tr>\n";
-				}
-				$sHtml .= "<tr>\n";
-			}
+			$sHtml .= '<span style="white-space: nowrap;padding:5px;display:inline-block;">';
 			$sFilterValue = '';
 			$sFilterValue = utils::ReadParam($sFilterCode, '');
 			$sFilterOpCode = null; // Use the default 'loose' OpCode
@@ -842,22 +835,18 @@ abstract class cmdbAbstractObject extends CMDBObject
 					$sValue .= "<option value=\"$key\"$sSelected>$value</option>\n";
 				}
 				$sValue .= "</select>\n";
-				$sHtml .= "<td><label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label></td><td>$sValue</td>\n";
+				$sHtml .= "<label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label>&nbsp;$sValue\n";
 			}
 			else
 			{
 				// Any value is possible, display an input box
-				$sHtml .= "<td><label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label></td><td><input class=\"textSearch\" name=\"$sFilterCode\" value=\"$sFilterValue\"/></td>\n";
+				$sHtml .= "<label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label>&nbsp;<input class=\"textSearch\" name=\"$sFilterCode\" value=\"$sFilterValue\"/>\n";
 			}
 			$index++;
+			$sHtml .= '</span> ';
 		}
-		if (($index % $numCols) != 0)
-		{
-			$sHtml .= "<td colspan=\"".(2*($numCols - ($index % $numCols)))."\"></td>\n";
-		}
-		$sHtml .= "</tr>\n";
-		$sHtml .= "<tr><td colspan=\"".(2*$numCols)."\" align=\"right\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Search')."\"></td></tr>\n";
-		$sHtml .= "</table>\n";
+		$sHtml .= "</p>\n";
+		$sHtml .= "<p align=\"right\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Search')."\"></p>\n";
 		foreach($aExtraParams as $sName => $sValue)
 		{
 			$sHtml .= "<input type=\"hidden\" name=\"$sName\" value=\"$sValue\" />\n";
@@ -904,6 +893,11 @@ abstract class cmdbAbstractObject extends CMDBObject
 	public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array())
 	{
 		static $iInputId = 0;
+		$sFieldPrefix = '';
+		if (isset($aArgs['prefix']))
+		{
+			$sFieldPrefix = $aArgs['prefix'];
+		}
 		if (isset($aArgs[$sAttCode]) && empty($value))
 		{
 			// default value passed by the context (either the app context of the operation)
@@ -939,19 +933,19 @@ abstract class cmdbAbstractObject extends CMDBObject
 				case 'DateTime':
 				$aEventsList[] ='keyup';
 				$aEventsList[] ='change';
-				$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"20\" name=\"attr_{$sAttCode}{$sNameSuffix}\" value=\"$value\" id=\"$iId\"/>&nbsp;{$sValidationField}";
+				$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"20\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"$value\" id=\"$iId\"/>&nbsp;{$sValidationField}";
 				break;
 				
 				case 'Password':
 					$aEventsList[] ='keyup';
 					$aEventsList[] ='change';
-					$sHTMLValue = "<input title=\"$sHelpText\" type=\"password\" size=\"30\" name=\"attr_{$sAttCode}{$sNameSuffix}\" value=\"$value\" id=\"$iId\"/>&nbsp;{$sValidationField}";
+					$sHTMLValue = "<input title=\"$sHelpText\" type=\"password\" size=\"30\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"$value\" id=\"$iId\"/>&nbsp;{$sValidationField}";
 				break;
 				
 				case 'Text':
 					$aEventsList[] ='keypress';
 					$aEventsList[] ='change';
-					$sHTMLValue = "<textarea class=\"resizable\" title=\"$sHelpText\" name=\"attr_{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">$value</textarea>&nbsp;{$sValidationField}";
+					$sHTMLValue = "<textarea class=\"resizable\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">$value</textarea>&nbsp;{$sValidationField}";
 				break;
 	
 				case 'LinkedSet':
@@ -970,9 +964,9 @@ abstract class cmdbAbstractObject extends CMDBObject
 					}
 					$iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize'));
 					$sHTMLValue = "<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"$iMaxFileSize\" />\n";
-				    $sHTMLValue .= "<input name=\"attr_{$sAttCode}{$sNameSuffix}\" type=\"hidden\" id=\"$iId\" \" value=\"$sFileName\"/>\n";
+				    $sHTMLValue .= "<input name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" type=\"hidden\" id=\"$iId\" \" value=\"$sFileName\"/>\n";
 				    $sHTMLValue .= "<span id=\"name_$iInputId\">$sFileName</span><br/>\n";
-				    $sHTMLValue .= "<input title=\"$sHelpText\" name=\"file_{$sAttCode}{$sNameSuffix}\" type=\"file\" id=\"file_$iId\" onChange=\"UpdateFileName('$iId', this.value)\"/>&nbsp;{$sValidationField}\n";
+				    $sHTMLValue .= "<input title=\"$sHelpText\" name=\"file_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" type=\"file\" id=\"file_$iId\" onChange=\"UpdateFileName('$iId', this.value)\"/>&nbsp;{$sValidationField}\n";
 				break;
 				
 				case 'List':
@@ -998,7 +992,7 @@ abstract class cmdbAbstractObject extends CMDBObject
 							// The input for the auto complete
 							$sHTMLValue = "<input count=\"".count($aAllowedValues)."\" type=\"text\" id=\"label_$iId\" size=\"30\" value=\"$sDisplayValue\"{$sCSSClasses}/>&nbsp;{$sValidationField}";
 							// another hidden input to store & pass the object's Id
-							$sHTMLValue .= "<input type=\"hidden\" id=\"$iId\" name=\"attr_{$sAttCode}{$sNameSuffix}\" value=\"$value\" />\n";
+							$sHTMLValue .= "<input type=\"hidden\" id=\"$iId\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"$value\" />\n";
 							$oPage->add_ready_script("\$('#label_$iId').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});");
 							$oPage->add_ready_script("\$('#label_$iId').result( function(event, data, formatted) { if (data) { $('#{$iId}').val(data[1]); } } );");
 							$aEventsList[] ='change';
@@ -1007,7 +1001,7 @@ abstract class cmdbAbstractObject extends CMDBObject
 						{
 							// Few choices, use a normal 'select'
 							// In case there are no valid values, the select will be empty, thus blocking the user from validating the form
-							$sHTMLValue = "<select title=\"$sHelpText\" name=\"attr_{$sAttCode}{$sNameSuffix}\" id=\"$iId\">\n";
+							$sHTMLValue = "<select title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" id=\"$iId\">\n";
 							$sHTMLValue .= "<option value=\"0\">".Dict::S('UI:SelectOne')."</option>\n";
 							foreach($aAllowedValues as $key => $display_value)
 							{
@@ -1028,7 +1022,7 @@ abstract class cmdbAbstractObject extends CMDBObject
 					}
 					else
 					{
-						$sHTMLValue = "<input title=\"$sHelpText\" type=\"text\" size=\"30\" name=\"attr_{$sAttCode}{$sNameSuffix}\" value=\"$value\" id=\"$iId\"/>&nbsp;{$sValidationField}";
+						$sHTMLValue = "<input title=\"$sHelpText\" type=\"text\" size=\"30\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"$value\" id=\"$iId\"/>&nbsp;{$sValidationField}";
 						$aEventsList[] ='keyup';
 						$aEventsList[] ='change';
 					}

+ 15 - 3
application/displayblock.class.inc.php

@@ -568,14 +568,17 @@ class DisplayBlock
 			break;
 			
 			case 'search':
-			$iSearchSectionId = 1;
+			static $iSearchSectionId = 1;
 			$sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed';
 			$sHtml .= "<div id=\"Search_$iSearchSectionId\" class=\"$sStyle\">\n";
-			$oPage->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});");
+			$oPage->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\n" .
+					"	\$(\"#Search_$iSearchSectionId\").slideToggle('normal');\n" .
+					"	$(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});");
 			$sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams);
 	 		$sHtml .= "</div>\n";
 	 		$sHtml .= "<div class=\"HRDrawer\"></div>\n";
 	 		$sHtml .= "<div id=\"LnkSearch_$iSearchSectionId\" class=\"DrawerHandle\">".Dict::S('UI:SearchToggle')."</div>\n";
+	 		$iSearchSectionId++;
 			break;
 			
 			case 'open_flash_chart':
@@ -784,6 +787,15 @@ class HistoryBlock extends DisplayBlock
 
 class MenuBlock extends DisplayBlock
 {
+	/**
+	 * Renders the "Actions" popup menu for the given set of objects
+	 * 
+	 * Note that the menu links containing (or ending) with a hash (#) will have their fragment
+	 * part (whatever is after the hash) dynamically replaced (by javascript) when the menu is
+	 * displayed, to correspond to the current hash/fragment in the page. This allows modifying
+	 * an object in with the same tab active by default as the tab that was active when selecting
+	 * the "Modify..." action.
+	 */
 	public function GetRenderContent(WebPage $oPage, $aExtraParams = array())
 	{
 		$sHtml = '';
@@ -841,7 +853,7 @@ class MenuBlock extends DisplayBlock
 				//$aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger');
 				if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); }
 				//if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Clone...', 'url' => "../pages/$sUIPage?operation=clone&class=$sClass&id=$id&$sContext"); }
-				if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext"); }
+				if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext#"); }
 				if ($bIsDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Delete'), 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); }
 				$aRelations = MetaModel::EnumRelations($sClass);
 				foreach($aRelations as $sRelationCode)

+ 65 - 41
application/itopwebpage.class.inc.php

@@ -54,6 +54,7 @@ class iTopWebPage extends NiceWebPage
 		$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
 //		$this->add_linked_stylesheet("../css/date.picker.css");
 		$this->add_linked_script('../js/jquery.layout.min.js');
+		$this->add_linked_script('../js/jquery.history.js');
 //		$this->add_linked_script("../js/jquery.dimensions.js");
 		$this->add_linked_script("../js/jquery.tablehover.js");
 		$this->add_linked_script("../js/jquery.treeview.js");
@@ -69,41 +70,6 @@ class iTopWebPage extends NiceWebPage
 		$this->add_linked_script("../js/swfobject.js");
 		$this->add_ready_script(
 <<<EOF
-	//add new widget called TruncatedList to properly display truncated lists when they are sorted
-	$.tablesorter.addWidget({ 
-	    // give the widget a id 
-	    id: "truncatedList", 
-	    // format is called when the on init and when a sorting has finished 
-	    format: function(table)
-	    { 
-			// Check if there is a "truncated" line
-			this.truncatedList = false;  
-			if ($("tr td.truncated",table).length > 0)
-			{
-				this.truncatedList = true;
-			}
-			if (this.truncatedList)
-			{
-				$("tr td",table).removeClass('truncated');
-				$("tr:last td",table).addClass('truncated');
-			}
-	    } 
-	});
-	
-	$.tablesorter.addWidget({ 
-	    // give the widget a id 
-	    id: "myZebra", 
-	    // format is called when the on init and when a sorting has finished 
-	    format: function(table)
-	    {
-	    	// Replace the 'red even' lines by 'red_even' since most browser do not support 2 classes selector in CSS, etc..
-			$("tbody tr:even",table).addClass('even');
-			$("tbody tr.red:even",table).removeClass('red').removeClass('even').addClass('red_even');
-			$("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even');
-			$("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even');
-	    } 
-	});
-
 	try
 	{
 	var myLayout; // a var is required because this page utilizes: myLayout.allowOverflow() method
@@ -149,8 +115,46 @@ class iTopWebPage extends NiceWebPage
 		// Accordion Menu
 		$("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false, collapsible: false });
  	});
+	//add new widget called TruncatedList to properly display truncated lists when they are sorted
+	$.tablesorter.addWidget({ 
+	    // give the widget a id 
+	    id: "truncatedList", 
+	    // format is called when the on init and when a sorting has finished 
+	    format: function(table)
+	    { 
+			// Check if there is a "truncated" line
+			this.truncatedList = false;  
+			if ($("tr td.truncated",table).length > 0)
+			{
+				this.truncatedList = true;
+			}
+			if (this.truncatedList)
+			{
+				$("tr td",table).removeClass('truncated');
+				$("tr:last td",table).addClass('truncated');
+			}
+	    } 
+	});
+		
+	
+	$.tablesorter.addWidget({ 
+	    // give the widget a id 
+	    id: "myZebra", 
+	    // format is called when the on init and when a sorting has finished 
+	    format: function(table)
+	    {
+	    	// Replace the 'red even' lines by 'red_even' since most browser do not support 2 classes selector in CSS, etc..
+			$("tbody tr:even",table).addClass('even');
+			$("tbody tr.red:even",table).removeClass('red').removeClass('even').addClass('red_even');
+			$("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even');
+			$("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even');
+	    } 
+		});
 		
-	$("div[id^=tabbedContent]").tabs(); // tabs
+	// tabs
+	$("div[id^=tabbedContent]").tabs( { show: function(event, ui) {
+			window.location.href = ui.tab.href; // So that history can keep track of the tabs
+	} });
 	$("table.listResults").tableHover(); // hover tables
 	$(".listResults").tablesorter( { headers: { 0:{sorter: false }}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
 	$(".date-pick").datepicker({
@@ -167,6 +171,12 @@ class iTopWebPage extends NiceWebPage
 	$('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth }); // JQuery UI dialogs
 	ShowDebug();
 	$('#logOffBtn>ul').popupmenu();
+	$.history.init(history_callback);
+	$("a[rel='history']").click(function()
+	{
+		$.history.load(this.href.replace(/^.*#/, ''));
+		return false;
+	});
 	}
 	catch(err)
 	{
@@ -178,10 +188,23 @@ class iTopWebPage extends NiceWebPage
 EOF
 );
 		$sUserPrefs = appUserPreferences::GetAsJSON();
-		$this->add_script("
+		$this->add_script(
+<<<EOF
+		// for JQuery history
+		function history_callback(hash)
+		{
+			// do stuff that loads page content based on hash variable
+			var aMatches = /^tab_(.*)$/.exec(hash);
+			if (aMatches != null)
+			{
+				var tab = $('#'+hash);
+				tab.parents('div[id^=tabbedContent]:first').tabs('select', aMatches[1]);
+			}
+		}
+
 		// For automplete
 		function findValue(li) {
-			if( li == null ) return alert(\"No match!\");
+			if( li == null ) return alert("No match!");
 			
 			// if coming from an AJAX call, let's use the CityId as the value
 			if( !!li.extra ) var sValue = li.extra[0];
@@ -220,7 +243,8 @@ EOF
 		}
 		
 		var oUserPreferences = $sUserPrefs;
-		");
+EOF
+);
 		
 		// Add the standard menus
 		/*
@@ -440,7 +464,7 @@ EOF
 	          $i = 0;
 			  foreach($m_aTabs as $sTabName => $sTabContent)
 			  {
-			      $sTabs .= "<li><a href=\"#fragment_$i\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
+			      $sTabs .= "<li><a href=\"#tab_$i\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
 			      $i++;
 	          }
 			  $sTabs .= "</ul>\n";
@@ -448,7 +472,7 @@ EOF
 			  $i = 0;
 			  foreach($m_aTabs as $sTabName => $sTabContent)
 			  {
-			      $sTabs .= "<div id=\"fragment_$i\">".$sTabContent."</div>\n";
+			      $sTabs .= "<div id=\"tab_$i\">".$sTabContent."</div>\n";
 			      $i++;
 	          }
 			  $sTabs .= "</div>\n<!-- end of tabs-->\n";

+ 1 - 1
application/menunode.class.inc.php

@@ -423,7 +423,7 @@ class OQLMenuNode extends MenuNode
 		// The standard template used for all such pages: a (closed) search form at the top and a list of results at the bottom
 		$sTemplate = <<<EOF
 <itopblock BlockClass="DisplayBlock" type="search" asynchronous="false" encoding="text/oql">$this->sOQL</itopblock>
-<p class="page-header"><img src="$sIcon"><itopstring>$this->sPageTitle</itopstring></p>
+<p class="page-header">$sIcon<itopstring>$this->sPageTitle</itopstring></p>
 <itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">$this->sOQL</itopblock>
 EOF;
 		$oTemplate = new DisplayTemplate($sTemplate);

+ 1 - 1
application/nicewebpage.class.inc.php

@@ -36,7 +36,7 @@ class NiceWebPage extends WebPage
         parent::__construct($s_title);
 		$this->m_aReadyScripts = array();
 		$this->add_linked_script("../js/jquery-1.4.2.min.js");
-		$this->add_linked_script("../js/jquery.history_remote.pack.js");
+		//$this->add_linked_script("../js/jquery.history_remote.pack.js");
 		$this->add_linked_stylesheet('../css/ui-lightness/jquery-ui-1.8.2.custom.css');
 		$this->add_linked_script('../js/jquery-ui-1.8.2.custom.min.js');
 		//$this->add_linked_script("../js/ui.resizable.js");

+ 248 - 171
application/ui.linkswidget.class.inc.php

@@ -32,6 +32,11 @@ class UILinksWidget
 	protected $m_sAttCode;
 	protected $m_sNameSuffix;
 	protected $m_iInputId;
+	protected $m_aAttributes;
+	protected $m_sExtKeyToRemote;
+	protected $m_sLinkedClass;
+	protected $m_sRemoteClass;
+	protected static $iWidgetIndex = 0;
 	
 	public function __construct($sClass, $sAttCode, $iInputId, $sNameSuffix = '')
 	{
@@ -39,120 +44,197 @@ class UILinksWidget
 		$this->m_sAttCode = $sAttCode;
 		$this->m_sNameSuffix = $sNameSuffix;
 		$this->m_iInputId = $iInputId;
-	}
-	
-	public function Display(WebPage $oPage, $oCurrentValuesSet = null)
-	{
-		$sHTMLValue = '';
-		$sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode);
-		// #@# todo - add context information, otherwise any value will be authorized for external keys
-		$aAllowedValues = MetaModel::GetAllowedValues_att($this->m_sClass, $this->m_sAttCode, array(), '');
+		$this->m_aEditableFields = array();
+		self::$iWidgetIndex++;
+			
 		$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
-		$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
+		$this->m_sLinkedClass = $oAttDef->GetLinkedClass();
+		$this->m_sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
+		$oLinkingAttDef = 	MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote);
+		$this->m_sRemoteClass = $oLinkingAttDef->GetTargetClass();
 		$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
 		$sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass);
-		$sDefaultState = MetaModel::GetDefaultState($this->m_sClass);
+		$sDefaultState = MetaModel::GetDefaultState($this->m_sClass);		
 
-		$aAttributes = array();
-		$sLinkedClass = $oAttDef->GetLinkedClass();
-		foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef)
+		$this->m_aEditableFields = array();
+		$this->m_aTableConfig = array();
+		$this->m_aTableConfig['form::checkbox'] = array( 'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onChange=\"var value = this.checked; $('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection').each( function() { this.checked = value; } ); oWidget".self::$iWidgetIndex.".OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+'));
+
+		foreach(MetaModel::ListAttributeDefs($this->m_sLinkedClass) as $sAttCode=>$oAttDef)
 		{
 			if ($sStateAttCode == $sAttCode)
 			{
 				// State attribute is always hidden from the UI
 			}
-			else if (!$oAttDef->IsExternalField() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $sExtKeyToRemote))
+			else if (!$oAttDef->IsExternalField() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $this->m_sExtKeyToRemote) && ($sAttCode != 'finalclass'))
 			{
-				$iFlags = MetaModel::GetAttributeFlags($this->m_sClass, $sDefaultState, $sAttCode);				
+				$iFlags = MetaModel::GetAttributeFlags($this->m_sLinkedClass, $sDefaultState, $sAttCode);				
 				if ( !($iFlags & OPT_ATT_HIDDEN) && !($iFlags & OPT_ATT_READONLY) )
 				{
-					$aAttributes[] = $sAttCode;
+					$this->m_aEditableFields[] = $sAttCode;
+					$this->m_aTableConfig[$sAttCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());				
 				}
 			}
 		}
-		$sAttributes = '[]';
-		if (count($aAttributes) > 0)
+		foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode)
 		{
-			$sAttributes = "['".implode("','", $aAttributes)."']";
+			// TO DO: check the state of the attribute: hidden or visible ?
+			if ($sFieldCode != 'finalclass')
+			{
+				$oAttDef = MetaModel::GetAttributeDef($this->m_sRemoteClass, $sFieldCode);
+				$this->m_aTableConfig['static::'.$sFieldCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
+			}
 		}
-		if ($oCurrentValuesSet != null)
+	}
+	
+	/**
+	 * A one-row form for editing a link record
+	 * @param WebPage $oP Web page used for the ouput
+	 * @param DBObject $oLinkedObj The object to which all the elements of the linked set refer to
+	 * @param mixed $linkObjOrId Either the object linked or a unique number for new link records to add
+	 * @param Hash $aArgs Extra context arguments
+	 * @return string The HTML fragment of the one-row form
+	 */
+	protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId = null, $aArgs = array() )
+	{
+		$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
+		$aRow = array();
+		if(is_object($linkObjOrId))
 		{
-			// Serialize the link set into a JSon object
-			$aCurrentValues = array();
-			$oCurrentValuesSet->Rewind(); // Make sure we can iterate through this set...
-			while($oLinkObj = $oCurrentValuesSet->Fetch())
+			$key = $linkObjOrId->GetKey();
+			$sPrefix .= "[$key][";
+			$sNameSuffix = "]"; // To make a tabular form
+			$aArgs['prefix'] = $sPrefix;
+			$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onChange=\"oWidget".self::$iWidgetIndex.".OnSelectChange();\" value=\"$key\">";
+			foreach($this->m_aEditableFields as $sFieldCode)
 			{
-				$sRow = '{';
-				foreach($aAttributes as $sLinkAttCode)
-				{
-					$sRow.= "\"$sLinkAttCode\": \"".addslashes($oLinkObj->Get($sLinkAttCode))."\", ";
-				}
-				$sRow .= "\"$sExtKeyToRemote\": ".$oLinkObj->Get($sExtKeyToRemote).'}';
-				$aCurrentValues[] = $sRow;
+				$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
+				$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $linkObjOrId->Get($sFieldCode), '' /* DisplayValue */, $key, $sNameSuffix, 0, $aArgs);
 			}
-			$sJSON = '['.implode(',', $aCurrentValues).']';
 		}
 		else
 		{
-			$sJSON = '[]'; // Empty array;
-//echo "JSON VA IECH<br/>\n";
+			// form for creating a new record
+			$sPrefix .= "[$linkObjOrId][";
+			$sNameSuffix = "]"; // To make a tabular form
+			$aArgs['prefix'] = $sPrefix;
+			$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onChange=\"oWidget".self::$iWidgetIndex.".OnSelectChange();\" value=\"$linkObjOrId\">";
+			$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_attr_{$sPrefix}id{$sNameSuffix}\" value=\"\">";
+			foreach($this->m_aEditableFields as $sFieldCode)
+			{
+				$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
+				$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, '' /* id */, $sNameSuffix, 0, $aArgs);
+			}
 		}
-//echo "JASON: $sJSON<br/>\n";;
 
-		// Many values (or even a unknown list) display an autocomplete
-		if ( (count($aAllowedValues) == 0) || (count($aAllowedValues) > 50) )
+		foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode)
 		{
-			// too many choices, use an autocomplete
-			// The input for the auto complete
-			$sTitle = $oAttDef->GetDescription();
-			$sHTMLValue .= "<script type=\"text/javascript\">\n";
-			$sHTMLValue .= "oLinkWidget{$this->m_iInputId} = new LinksWidget('{$this->m_iInputId}', '$sLinkedClass', '$sExtKeyToMe', '$sExtKeyToRemote', $sAttributes);\n";
-			$sHTMLValue .= "</script>\n";
-			$oPage->add_at_the_end($this->GetObjectPickerDialog($oPage, $sTargetClass, 'oLinkWidget'.$this->m_iInputId.'.OnOk')); // Forms should not be inside forms
-			$oPage->add_at_the_end($this->GetLinkObjectDialog($oPage, $this->m_iInputId)); // Forms should not be inside forms
-			$sHTMLValue .= "<input type=\"text\" id=\"ac_{$this->m_iInputId}\" size=\"35\" value=\"\" title=\"".Dict::S('UI:LinksWidget:Autocomplete+')."\"/>";
-			$sHTMLValue .= "<input type=\"button\" id=\"ac_add_{$this->m_iInputId}\" value=\"".Dict::S('UI:Button:AddObject')."\"  class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>";
-			$sHTMLValue .= "&nbsp;<input type=\"button\" value=\"".Dict::S('UI:Button:BrowseObjects')."\"  class=\"action\" onClick=\"return ManageObjects('$sTitle', '$sTargetClass', '$this->m_iInputId', '$sExtKeyToRemote');\"/>&nbsp<span id=\"v_{$this->m_iInputId}\"></span>\n";
-			// another hidden input to store & pass the object's Id
-			$sHTMLValue .= "<input type=\"hidden\" id=\"id_ac_{$this->m_iInputId}\" onChange=\"EnableAddButton('{$this->m_iInputId}');\"/>\n";
-			$sHTMLValue .= "<input type=\"hidden\" id=\"{$this->m_iInputId}\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"/>\n";
-			$oPage->add_ready_script("\$('#{$this->m_iInputId}').val('$sJSON');\noLinkWidget{$this->m_iInputId}.Init();\n\$('#ac_{$this->m_iInputId}').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_{$this->m_iInputId}', extraParams:{operation:'ui.linkswidget', sclass:'{$this->m_sClass}', attCode:'{$this->m_sAttCode}', max:30}});");
-			$oPage->add_ready_script("\$('#ac_add_{$this->m_iInputId}').attr('disabled', 'disabled');");
-			$oPage->add_ready_script("\$('#ac_{$this->m_iInputId}').result( function(event, data, formatted) { if (data) { $('#id_ac_{$this->m_iInputId}').val(data[1]); $('#ac_add_{$this->m_iInputId}').attr('disabled', ''); } else { $('#ac_add_{$this->m_iInputId}').attr('disabled', 'disabled'); } } );");
+			$aRow['static::'.$sFieldCode] = $oLinkedObj->GetAsHTML($sFieldCode);
+		}
+		return $aRow;
+	}
+
+	/**
+	 * Display one row of the whole form
+	 * @return none
+	 */
+	protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId)
+	{
+		$sHtml = '';
+		$sHtml .= "<tr id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_row_$iRowId\">\n";
+		foreach($aConfig as $sName=>$void)
+		{
+			$sHtml .= "<td>".$aRow[$sName]."</td>\n";
+		}
+		$sHtml .= "</tr>\n";
+		
+		return $sHtml;
+	}
+	
+	/**
+	 * Display the table with the form for editing all the links at once
+	 * @param WebPage $oP The web page used for the output
+	 * @param Hash $aConfig The table's header configuration
+	 * @param Hash $aData The tabular data to be displayed
+	 * @return string Html fragment representing the form table
+	 */
+	protected function DisplayFormTable(WebPage $oP, $aConfig, $aData)
+	{
+		$sHtml = '';
+		$sHtml .= "<table class=\"listResults\">\n";
+		// Header
+		$sHtml .= "<thead>\n";
+		$sHtml .= "<tr>\n";
+		foreach($aConfig as $sName=>$aDef)
+		{
+			$sHtml .= "<th title=\"".$aDef['description']."\">".$aDef['label']."</th>\n";
+		}
+		$sHtml .= "</tr>\n";
+		$sHtml .= "</thead>\n";
+		
+		// Content
+		$sHtml .= "</tbody>\n";
+		if (count($aData) == 0)
+		{
+			$sHtml .= "<tr id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_empty_row\"><td colspan=\"".count($aConfig)."\" style=\"text-align:center;\">".Dict::S('UI:Message:EmptyList:UseAdd')."<input type=\"hidden\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"></td></td>";
 		}
 		else
 		{
-			// Few choices, use a normal 'select'
-			$sHTMLValue = "<select name=\"attr_{$this->m_sAttCode}\"  id=\"{$this->m_iInputId}\">\n";
-			$sHTMLValue .= "<option value=\"0\">".Dict::S('UI:Combo:SelectValue')."</option>\n";
-			if (count($aAllowedValues) > 0)
+			foreach($aData as $iRowId => $aRow)
 			{
-				foreach($aAllowedValues as $key => $value)
-				{
-					$sHTMLValue .= "<option value=\"$key\"$sSelected>$value</option>\n";
-				}
-			}
-			$sHTMLValue .= "</select>\n";
+				$sHtml .= $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId);
+			}		
 		}
-		$sHTMLValue .= "<div id=\"{$this->m_iInputId}_values\">\n";
-		if ($oCurrentValuesSet != null)
+		$sHtml .= "</tbody>\n";
+		
+		// Footer
+		$sHtml .= "</table>\n";
+		
+		return $sHtml;
+	}
+	
+
+	/**
+	 * Get the HTML fragment corresponding to the linkset editing widget
+	 * @param WebPage $oP The web page used for all the output
+	 * @param DBObjectSet The initial value of the linked set
+	 * @param Hash $aArgs Extra context arguments
+	 * @return string The HTML fragment to be inserted into the page
+	 */
+	public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array())
+	{
+		$iWidgetIndex = self::$iWidgetIndex;
+		$sHtmlValue = '';
+		$sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode);
+		$sHtmlValue .= "<div id=\"linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix}\">\n";
+		$oValue->Rewind();
+		$aForm = array();
+		$oContext = new UserContext();
+		while($oCurrentLink = $oValue->Fetch())
 		{
-		 	// transform the DBObjectSet into a CMDBObjectSet !!!
-			$aLinkedObjects = $oCurrentValuesSet->ToArray(false);
-			// Actual values will be displayed asynchronously, no need to display them here
-			//if (count($aLinkedObjects) > 0)
-			//{
-			//	$oSet = CMDBObjectSet::FromArray($sLinkedClass, $aLinkedObjects);
-			//	$oDisplayBlock = DisplayBlock::FromObjectSet($oSet, 'list');
-			//	$sHTMLValue .= $oDisplayBlock->GetDisplay($oPage, $this->m_iInputId.'_current', array('linkage' => $sExtKeyToMe, 'menu' => false));
-			//}
+			$aRow = array();
+			$key = $oCurrentLink->GetKey();
+			$oLinkedObj = $oContext->GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote));
+
+			$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs);
 		}
-		$sHTMLValue .= "</div>\n";
-		return $sHTMLValue;
+		$sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm);
+		$oPage->add_ready_script(<<<EOF
+		oWidget$iWidgetIndex = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}');
+		oWidget$iWidgetIndex.Init();
+EOF
+);
+		$sHtmlValue .= "<span style=\"float:left;\">&nbsp;&nbsp;&nbsp;<img src=\"../images/tv-item-last.gif\">&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget$iWidgetIndex.RemoveSelected();\" >";
+		$sHtmlValue .= "&nbsp;&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget$iWidgetIndex.AddObjects();\"></span>\n";
+		$sHtmlValue .= "<span style=\"clear:both;\"><p>&nbsp;</p></span>\n";
+		$sHtmlValue .= "</div>\n";
+		$oPage->add_at_the_end($this->GetObjectPickerDialog($oPage)); // To prevent adding forms inside the main form
+		return $sHtmlValue;
 	}
+	
 	/**
 	 * This static function is called by the Ajax Page when there is a need to fill an autocomplete combo
-	 * @param $oPage WebPage The ajax page used for the put^put (sent back to the browser
+	 * @param $oPage WebPage The ajax page used for the output (sent back to the browser)
 	 * @param $oContext UserContext The context of the user (for limiting the search)
 	 * @param $sClass string The name of the class of the current object being edited
 	 * @param $sAttCode string The name of the attribute being edited
@@ -257,111 +339,106 @@ class UILinksWidget
 		return $sTargetClass;
 	}
 	
-	protected function GetObjectPickerDialog($oPage, $sTargetClass, $sOkFunction)
+	protected function GetObjectPickerDialog($oPage)
 	{
-		$sDialogTitle = Dict::S('UI:ManageObjectsDlg');
-		$sOkBtnLabel = Dict::S('UI:Button:Ok');
-		$sCancelBtnLabel = Dict::S('UI:Button:Cancel');
-		$sAddBtnLabel = Dict::S('UI:Button:AddToList');
-		$sRemoveBtnLabel = Dict::S('UI:Button:RemoveFromList');
-		$sFilterBtnLabel = Dict::S('UI:Button:FilterList');
-		$sLabelSelectedObjects = Dict::S('UI:Label:SelectedObjects');
-		$sLabelAvailableObjects = Dict::S('UI:Label:AvailableObjects');
-		$sHTML = <<< EOF
-		<div style="display:none" title="$sDialogTitle" id="ManageObjectsDlg_{$this->m_iInputId}">
-		<div class="wizContainer">
-		<table width="100%">
-			<tr>
-				<td>
-					<p>$sLabelSelectedObjects</p>
-					<button type="button" class="action" onClick="FilterLeft('$sTargetClass');"><span>$sFilterBtnLabel</span></button>
-					<p><select id="selected_objects_{$this->m_iInputId}" size="10" multiple onChange="Manage_UpdateButtons('$this->m_iInputId')" style="width:300px;">
-					</select></p>
-				</td>
-				<td style="text-align:center; valign:middle;">
-					<p><button type="button" id="btn_add_objects_{$this->m_iInputId}" onClick="Manage_AddObjects('$this->m_iInputId');">$sAddBtnLabel</button></p>
-					<p><button type="button" id="btn_remove_objects_{$this->m_iInputId}" onClick="Manage_RemoveObjects('$this->m_iInputId');">$sRemoveBtnLabel</button></p>
-				</td>
-				<td>
-					<p>$sLabelAvailableObjects</p>
-					<button type="button" class="action" onClick="FilterRight('$sTargetClass');"><span>$sFilterBtnLabel</span></button>
-					<p><select id="available_objects_{$this->m_iInputId}" size="10" multiple onChange="Manage_UpdateButtons('$this->m_iInputId')" style="width:300px;">
-					</select></p>
-				</td>
-			</tr>
-			<tr>
-				<td colspan="3">
-				<input type="submit" onClick="$('#ManageObjectsDlg_{$this->m_iInputId}').dialog('close'); $sOkFunction('$sTargetClass', 'selected_objects'); return false;" value="$sOkBtnLabel" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<button type="button" onClick="$('#ManageObjectsDlg_{$this->m_iInputId}').dialog('close');">$sCancelBtnLabel</button>
-				</td>
-			</tr>
-		</table>
-		</div>
-		</div>
-EOF;
-		$oPage->add_ready_script("$('#ManageObjectsDlg_$this->m_iInputId').dialog( {autoOpen: false, modal: true, width: 750, height: 350} );"); // JQuery UI dialog
-		//$oPage->add_ready_script("UpdateObjectList('$sClass');");
-		return $sHTML;
+		$sHtml = "<div id=\"dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}\">";
+		//$oTargetObj = $oContext->GetObject($sTargetClass, $this->m_iObjectId);
+		$sHtml .= "<div class=\"wizContainer\">\n";
+		//$sHtml .= "<div class=\"page_header\">\n");
+		//$sHtml .= "<h1>".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."</h1>\n");
+		//$sHtml .= "</div>\n");
+
+		$oContext = new UserContext();
+		$iWidgetIndex = self::$iWidgetIndex;
+		$oFilter = $oContext->NewFilter($this->m_sRemoteClass);
+		$oSet = new CMDBObjectSet($oFilter);
+		$oBlock = new DisplayBlock($oFilter, 'search', false);
+		$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true));
+		$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget$iWidgetIndex.DoAddObjects(this.id);\">\n";
+		$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}\">\n";
+		$sHtml .= "<div style=\"height: 100px; background: #fff;border-color:#F6F6F1 #E6E6E1 #E6E6E1 #F6F6F1; border-style:solid; border-width:3px; text-align: center; vertical-align: center;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
+		$sHtml .= "</div>\n";
+		$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">&nbsp;&nbsp;<input type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">";
+		$sHtml .= "</div>\n";
+		$sHtml .= "</form>\n";
+		$sHtml .= "</div>\n";
+		$oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ autoOpen: false, modal: true });");
+		$oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass), "<span class=\"hilite\"> ZZZZ </span>")."'});");
+		$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget$iWidgetIndex.SearchObjectsToAdd);");
+		return $sHtml;
 	}
-	
-	protected function GetLinkObjectDialog($oPage, $sId)
+
+	/**
+	 * Search for objects to be linked to the current object (i.e "remote" objects)
+	 * @param WebPage $oP The page used for the output (usually an AjaxWebPage)
+	 * @param UserContext $oContext User context to limit the search...
+	 * @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
+	 * @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search
+	 */
+	public function SearchObjectsToAdd(WebPage $oP, UserContext $oContext, $sRemoteClass = '', $aAlreadyLinkedIds = array())
 	{
-		$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
-		$sLinkedClass = $oAttDef->GetLinkedClass();
-		$sStateAttCode = MetaModel::GetStateAttributeCode($sLinkedClass);
-		$sDefaultState = MetaModel::GetDefaultState($sLinkedClass);
-		$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
-		$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
-		$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
-		
-		$sHTML = "<div style=\"display:none\" title=\"".Dict::Format('UI:Link_Class_Attributes', MetaModel::GetName($sLinkedClass))."\" id=\"LinkDlg_$sId\">\n";
-		$sHTML .= "<div class=\"wizContainer\">\n";
-		$sHTML .= "<form action=\"./UI.php\" onSubmit=\"return oLinkWidget$sId.OnLinkOk();\">\n";
-		$index = 0;
-		$aAttrsMap = array();
-		$aDetails = array();
-		foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef)
+		if ($sRemoteClass != '')
 		{
-			if ($sStateAttCode == $sAttCode)
-			{
-				// State attribute is always hidden from the UI
-				//$sHTMLValue = $this->GetStateLabel();
-				//$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue);
-			}
-			else if (!$oAttDef->IsExternalField() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $sExtKeyToRemote))
+			// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
+			$oFilter = $oContext->NewFilter($sRemoteClass);
+		}
+		else
+		{
+			// No remote class specified use the one defined in the linkedset
+			$oFilter = $oContext->NewFilter($this->m_sRemoteClass);		
+		}
+		if (count($aAlreadyLinkedIds) > 0)
+		{
+			// Positive IDs correspond to existing link records
+			// negative IDs correspond to "remote" objects to be linked
+			$aLinkIds = array();
+			$aRemoteObjIds = array();
+			foreach($aAlreadyLinkedIds as $iId)
 			{
-				$iFlags = MetaModel::GetAttributeFlags($sLinkedClass, $sDefaultState, $sAttCode);				
-				if ($iFlags & OPT_ATT_HIDDEN)
+				if ($iId > 0)
 				{
-					// Attribute is hidden, do nothing
+					$aLinkIds[] = $iId;
 				}
 				else
 				{
-					if ($iFlags & OPT_ATT_READONLY)
-					{
-						// Attribute is read-only
-						$sHTMLValue = $this->GetAsHTML($sAttCode);
-					}
-					else
-					{
-						$sValue = ""; //$this->Get($sAttCode);
-						$sDisplayValue = ""; //$this->GetEditValue($sAttCode);
-						$sSubId = $sId.'_'.$index;
-						$aAttrsMap[$sAttCode] = $sSubId;
-						$index++;
-						$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sLinkedClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sSubId, $this->m_sAttCode);
-					}
-					$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue);
+					$aRemoteObjIds[] = -$iId;
+				}
+			}
+			
+			if (count($aLinkIds) >0)
+			{
+				// Search for the links to find to which "remote" object they are linked
+				$oLinkFilter = $oContext->NewFilter($this->m_sLinkedClass);
+				$oLinkFilter->AddCondition('id', $aLinkIds, 'IN');
+				$oLinkSet = new CMDBObjectSet($oLinkFilter);
+				while($oLink = $oLinkSet->Fetch())
+				{
+					$aRemoteObjIds[] = $oLink->Get($this->m_sExtKeyToRemote);
 				}
 			}
+			$oFilter->AddCondition('id', $aRemoteObjIds, 'NOTIN');
+		}
+		$oSet = new CMDBObjectSet($oFilter);
+		$oBlock = new DisplayBlock($oFilter, 'list', false);
+		$oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results
+	}
+	
+	public function DoAddObjects(WebPage $oP, UserContext $oContext, $aLinkedObjectIds = array())
+	{
+		$aTable = array();
+		foreach($aLinkedObjectIds as $iObjectId)
+		{
+			$oLinkedObj = $oContext->GetObject($this->m_sRemoteClass, $iObjectId);
+			if (is_object($oLinkedObj))
+			{
+				$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids
+				$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId)); 
+			}
+			else
+			{
+				$oP->p(Dict::Format('UI:Error:Object_Class_Id_NotFound', $this->m_sLinkedClass, $iObjectId));
+			}
 		}
-		$sHTML .= $oPage->GetDetails($aDetails);
-		$sHTML .= "<input type=\"submit\" onClick=\"oLinkWidget$sId.OnLinkOk(); return false;\" value=\"".Dict::S('UI:Button:Ok')."\" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<button type=\"button\" onClick=\"oLinkWidget$sId.OnLinkCancel()\">".Dict::S('UI:Button:Cancel')."</button>\n";
-		$sHTML .= "</form>\n";
-		$sHTML .= "</div>\n";
-		$sHTML .= "</div>\n";
-		$oPage->add_ready_script("$('#LinkDlg_$sId').dialog( {autoOpen: false, modal: true, width: 300 } );"); // jQuery UI dialog
-		//$oPage->add_ready_script("UpdateObjectList('$sClass');");
-		return $sHTML;
 	}
 }
 ?>