diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 7dcab390c..3142b2e53 100644 --- a/application/cmdbabstract.class.inc.php +++ b/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("

GetIcon()."\" style=\"margin-right:10px;margin-top: -16px;vertical-align:middle;\">\n"); + $oPage->add("

".$this->GetIcon()." \n"); $oPage->add(MetaModel::GetName(get_class($this)).": ".$this->GetName()."

\n"); $oPage->add("
\n"); } @@ -174,7 +174,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sLinkedClass = $oAttDef->GetLinkedClass(); $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); $sTargetClass = $oLinkingAttDef->GetTargetClass(); - $oPage->p(" ".$oAttDef->GetDescription()); + $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$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(" ".$oAttDef->GetDescription()); + $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$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 .= "
\n"; $sHtml .= "

".Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo)."

\n"; $index = 0; - $sHtml .= "\n"; + $sHtml .= "

\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 .= "\n"; - } - $sHtml .= "

\n"; - } + $sHtml .= ''; $sFilterValue = ''; $sFilterValue = utils::ReadParam($sFilterCode, ''); $sFilterOpCode = null; // Use the default 'loose' OpCode @@ -842,22 +835,18 @@ abstract class cmdbAbstractObject extends CMDBObject $sValue .= "\n"; } $sValue .= "\n"; - $sHtml .= "\n"; + $sHtml .= " $sValue\n"; } else { // Any value is possible, display an input box - $sHtml .= "\n"; + $sHtml .= " \n"; } $index++; + $sHtml .= ' '; } - if (($index % $numCols) != 0) - { - $sHtml .= "\n"; - } - $sHtml .= "\n"; - $sHtml .= "\n"; - $sHtml .= "
$sValue
\n"; + $sHtml .= "

\n"; + $sHtml .= "

\n"; foreach($aExtraParams as $sName => $sValue) { $sHtml .= "\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 = " {$sValidationField}"; + $sHTMLValue = " {$sValidationField}"; break; case 'Password': $aEventsList[] ='keyup'; $aEventsList[] ='change'; - $sHTMLValue = " {$sValidationField}"; + $sHTMLValue = " {$sValidationField}"; break; case 'Text': $aEventsList[] ='keypress'; $aEventsList[] ='change'; - $sHTMLValue = " {$sValidationField}"; + $sHTMLValue = " {$sValidationField}"; break; case 'LinkedSet': @@ -970,9 +964,9 @@ abstract class cmdbAbstractObject extends CMDBObject } $iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize')); $sHTMLValue = "\n"; - $sHTMLValue .= "\n"; + $sHTMLValue .= "\n"; $sHTMLValue .= "$sFileName
\n"; - $sHTMLValue .= " {$sValidationField}\n"; + $sHTMLValue .= " {$sValidationField}\n"; break; case 'List': @@ -998,7 +992,7 @@ abstract class cmdbAbstractObject extends CMDBObject // The input for the auto complete $sHTMLValue = " {$sValidationField}"; // another hidden input to store & pass the object's Id - $sHTMLValue .= "\n"; + $sHTMLValue .= "\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 = "\n"; $sHTMLValue .= "\n"; foreach($aAllowedValues as $key => $display_value) { @@ -1028,7 +1022,7 @@ abstract class cmdbAbstractObject extends CMDBObject } else { - $sHTMLValue = " {$sValidationField}"; + $sHTMLValue = " {$sValidationField}"; $aEventsList[] ='keyup'; $aEventsList[] ='change'; } diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 164922893..636acf1ab 100644 --- a/application/displayblock.class.inc.php +++ b/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 .= "
\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 .= "
\n"; $sHtml .= "
\n"; $sHtml .= "
".Dict::S('UI:SearchToggle')."
\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) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index a22e71327..30c5f1912 100644 --- a/application/itopwebpage.class.inc.php +++ b/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( << 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'); + } + } + }); - $("div[id^=tabbedContent]").tabs(); // tabs + + $.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'); + } + }); + + // 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( +<< $sTabContent) { - $sTabs .= "
  • ".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."
  • \n"; + $sTabs .= "
  • ".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."
  • \n"; $i++; } $sTabs .= "\n"; @@ -448,7 +472,7 @@ EOF $i = 0; foreach($m_aTabs as $sTabName => $sTabContent) { - $sTabs .= "
    ".$sTabContent."
    \n"; + $sTabs .= "
    ".$sTabContent."
    \n"; $i++; } $sTabs .= "

    \n\n"; diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 4ad4413ee..4897817be 100644 --- a/application/menunode.class.inc.php +++ b/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 = <<$this->sOQL - + $this->sOQL EOF; $oTemplate = new DisplayTemplate($sTemplate); diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php index 8b59d7167..6343279db 100644 --- a/application/nicewebpage.class.inc.php +++ b/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"); diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 931a5e3c4..6ee62d8e2 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/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' => "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)."']"; - } - if ($oCurrentValuesSet != null) - { - // Serialize the link set into a JSon object - $aCurrentValues = array(); - $oCurrentValuesSet->Rewind(); // Make sure we can iterate through this set... - while($oLinkObj = $oCurrentValuesSet->Fetch()) + // TO DO: check the state of the attribute: hidden or visible ? + if ($sFieldCode != 'finalclass') { - $sRow = '{'; - foreach($aAttributes as $sLinkAttCode) - { - $sRow.= "\"$sLinkAttCode\": \"".addslashes($oLinkObj->Get($sLinkAttCode))."\", "; - } - $sRow .= "\"$sExtKeyToRemote\": ".$oLinkObj->Get($sExtKeyToRemote).'}'; - $aCurrentValues[] = $sRow; + $oAttDef = MetaModel::GetAttributeDef($this->m_sRemoteClass, $sFieldCode); + $this->m_aTableConfig['static::'.$sFieldCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription()); } - $sJSON = '['.implode(',', $aCurrentValues).']'; } - else - { - $sJSON = '[]'; // Empty array; -//echo "JSON VA IECH
    \n"; - } -//echo "JASON: $sJSON
    \n";; - - // Many values (or even a unknown list) display an autocomplete - if ( (count($aAllowedValues) == 0) || (count($aAllowedValues) > 50) ) - { - // too many choices, use an autocomplete - // The input for the auto complete - $sTitle = $oAttDef->GetDescription(); - $sHTMLValue .= "\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 .= "m_iInputId}\" size=\"35\" value=\"\" title=\"".Dict::S('UI:LinksWidget:Autocomplete+')."\"/>"; - $sHTMLValue .= "m_iInputId}\" value=\"".Dict::S('UI:Button:AddObject')."\" class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>"; - $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/> m_iInputId}\">\n"; - // another hidden input to store & pass the object's Id - $sHTMLValue .= "m_iInputId}\" onChange=\"EnableAddButton('{$this->m_iInputId}');\"/>\n"; - $sHTMLValue .= "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'); } } );"); - } - else - { - // Few choices, use a normal 'select' - $sHTMLValue = "\n"; - } - $sHTMLValue .= "
    m_iInputId}_values\">\n"; - if ($oCurrentValuesSet != null) - { - // 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)); - //} - } - $sHTMLValue .= "
    \n"; - return $sHTMLValue; } + + /** + * 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)) + { + $key = $linkObjOrId->GetKey(); + $sPrefix .= "[$key]["; + $sNameSuffix = "]"; // To make a tabular form + $aArgs['prefix'] = $sPrefix; + $aRow['form::checkbox'] = ""; + foreach($this->m_aEditableFields as $sFieldCode) + { + $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); + } + } + else + { + // form for creating a new record + $sPrefix .= "[$linkObjOrId]["; + $sNameSuffix = "]"; // To make a tabular form + $aArgs['prefix'] = $sPrefix; + $aRow['form::checkbox'] = ""; + $aRow['form::checkbox'] .= ""; + 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); + } + } + + foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode) + { + $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 .= "m_sAttCode}{$this->m_sNameSuffix}_row_$iRowId\">\n"; + foreach($aConfig as $sName=>$void) + { + $sHtml .= "".$aRow[$sName]."\n"; + } + $sHtml .= "\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 .= "\n"; + // Header + $sHtml .= "\n"; + $sHtml .= "\n"; + foreach($aConfig as $sName=>$aDef) + { + $sHtml .= "\n"; + } + $sHtml .= "\n"; + $sHtml .= "\n"; + + // Content + $sHtml .= "\n"; + if (count($aData) == 0) + { + $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}_empty_row\">"; + } + else + { + foreach($aData as $iRowId => $aRow) + { + $sHtml .= $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId); + } + } + $sHtml .= "\n"; + + // Footer + $sHtml .= "
    ".$aDef['label']."
    ".Dict::S('UI:Message:EmptyList:UseAdd')."m_sAttCode}{$this->m_sNameSuffix}\" value=\"\">
    \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 .= "
    m_sAttCode}{$this->m_sNameSuffix}\">\n"; + $oValue->Rewind(); + $aForm = array(); + $oContext = new UserContext(); + while($oCurrentLink = $oValue->Fetch()) + { + $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 .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm); + $oPage->add_ready_script(<<m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}'); + oWidget$iWidgetIndex.Init(); +EOF +); + $sHtmlValue .= "     m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget$iWidgetIndex.RemoveSelected();\" >"; + $sHtmlValue .= "   m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget$iWidgetIndex.AddObjects();\">\n"; + $sHtmlValue .= "

     

    \n"; + $sHtmlValue .= "
    \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 - -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 = "
    m_sAttCode}{$this->m_sNameSuffix}\">"; + //$oTargetObj = $oContext->GetObject($sTargetClass, $this->m_iObjectId); + $sHtml .= "
    \n"; + //$sHtml .= "
    \n"); + //$sHtml .= "

    ".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "".$oTargetObj->GetHyperlink()."")."

    \n"); + //$sHtml .= "
    \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 .= "m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget$iWidgetIndex.DoAddObjects(this.id);\">\n"; + $sHtml .= "
    m_sAttCode}{$this->m_sNameSuffix}\">\n"; + $sHtml .= "

    ".Dict::S('UI:Message:EmptyList:UseSearchForm')."

    \n"; + $sHtml .= "
    \n"; + $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">  "; + $sHtml .= "
    \n"; + $sHtml .= "\n"; + $sHtml .= "
    \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), " ZZZZ ")."'});"); + $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 = "
    \n"; - $sHTML .= "
    \n"; - $sHTML .= "
    \n"; - $index = 0; - $aAttrsMap = array(); - $aDetails = array(); - foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef) + if ($sRemoteClass != '') { - if ($sStateAttCode == $sAttCode) + // 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) { - // 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)) - { - $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 .= "     \n"; - $sHTML .= "
    \n"; - $sHTML .= "
    \n"; - $sHTML .= "
    \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; } } ?>