diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index abe3116a2..d688ae436 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -2541,7 +2541,8 @@ EOF $this->Set($sAttCode, $iValue); } } - else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() && ($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE)) + else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() && + (($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE) || ($oAttDef->GetEditMode() == LINKSET_EDITMODE_ADDREMOVE))) { $oLinkset = $this->Get($sAttCode); $sLinkedClass = $oLinkset->GetClass(); @@ -2557,13 +2558,16 @@ EOF } else { - $aObjSet[] = $oLink; + if (!array_key_exists('to_be_removed', $value) || !in_array($oLink->GetKey(), $value['to_be_removed'])) + { + $aObjSet[] = $oLink; + } } } if (array_key_exists('to_be_created', $value) && (count($value['to_be_created']) > 0)) { - // Now handle the lniks to be created + // Now handle the links to be created foreach($value['to_be_created'] as $aData) { $sSubClass = $aData['class']; @@ -2578,7 +2582,35 @@ EOF } } } - + if (array_key_exists('to_be_added', $value) && (count($value['to_be_added']) > 0)) + { + // Now handle the links to be added by making the remote object point to self + foreach($value['to_be_added'] as $iObjKey) + { + $oLink = MetaModel::GetObject($sLinkedClass, $iObjKey, false); + if ($oLink) + { + $aObjSet[] = $oLink; + $bModified = true; + } + } + } + if (array_key_exists('to_be_removed', $value) && (count($value['to_be_removed']) > 0)) + { + // Now handle the links to be removed by making the remote object point to nothing + // Keep them in the set (modified), DBWriteLinks will handle them + foreach($value['to_be_removed'] as $iObjKey) + { + $oLink = MetaModel::GetObject($sLinkedClass, $iObjKey, false); + if ($oLink) + { + $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); + $oLink->Set($sExtKeyToMe, null); + $aObjSet[] = $oLink; + $bModified = true; + } + } + } if ($bModified) { $oNewSet = DBObjectSet::FromArray($oLinkset->GetClass(), $aObjSet); @@ -2617,7 +2649,8 @@ EOF { $value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents')); } - else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() && ($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE)) + else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() && + (($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE) || ($oAttDef->GetEditMode() == LINKSET_EDITMODE_ADDREMOVE)) ) { $aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}', 'raw_data'), true); $aToBeCreated = array(); @@ -2637,7 +2670,9 @@ EOF } $value = array('to_be_created' => $aToBeCreated, - 'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]', 'raw_data'), true) ); + 'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]', 'raw_data'), true), + 'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]', 'raw_data'), true), + 'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]', 'raw_data'), true) ); } else { diff --git a/application/ui.linksdirectwidget.class.inc.php b/application/ui.linksdirectwidget.class.inc.php index 94e537d04..f6b02ff96 100644 --- a/application/ui.linksdirectwidget.class.inc.php +++ b/application/ui.linksdirectwidget.class.inc.php @@ -54,6 +54,7 @@ class UILinksWidgetDirect default: $aZList = MetaModel::FlattenZList(MetaModel::GetZListItems($this->sLinkedClass, 'list')); + array_unshift($aZList, 'friendlyname'); } foreach($aZList as $sLinkedAttCode) { @@ -97,6 +98,18 @@ class UILinksWidgetDirect $this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj); break; + case LINKSET_EDITMODE_ADDREMOVE: // The whole linkset can be edited 'in-place' + $sTargetClass = $oLinksetDef->GetLinkedClass(); + $sExtKeyToMe = $oLinksetDef->GetExtKeyToMe(); + $oExtKeyDef = MetaModel::GetAttributeDef($sTargetClass, $sExtKeyToMe); + $aButtons = array('add'); + if ($oExtKeyDef->IsNullAllowed()) + { + $aButtons = array('add', 'remove'); + } + $this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons); + break; + case LINKSET_EDITMODE_ACTIONS: default: $this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, true /* bDisplayMenu*/); @@ -139,7 +152,7 @@ class UILinksWidgetDirect } } - protected function DisplayEditInPlace(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj) + protected function DisplayEditInPlace(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete')) { $aAttribs = $this->GetTableConfig(); @@ -165,10 +178,16 @@ class UILinksWidgetDirect // 'modify' => 'Modify...' , 'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)), 'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)), + 'remove' => Dict::S('UI:Button:Remove'), + 'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)), + 'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)), ); - $sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php'; + $oContext = new ApplicationContext(); + $sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink(); $sJSONLabels = json_encode($aLabels); - $oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, sumit_to: '$sSubmitUrl' });"); + $sJSONButtons = json_encode($aButtons); + $sWizHelper = 'oWizardHelper'.$sFormPrefix; + $oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper });"); } public function GetObjectCreationDlg(WebPage $oPage, $sProposedRealClass = '') @@ -221,6 +240,71 @@ class UILinksWidgetDirect $oPage->add(''); } + public function GetObjectsSelectionDlg($oPage, $oCurrentObj) + { + $sHtml = "
\n"; + $oFilter = new DBObjectSearch($this->sLinkedClass); + if ($oCurrentObj != null) + { + $this->SetSearchDefaultFromContext($oCurrentObj, $oFilter); + } + $oBlock = new DisplayBlock($oFilter, 'search', false); + $sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array('open' => true)); + $sHtml .= "
sInputid}\">\n"; + $sHtml .= "
sInputid}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n"; + $sHtml .= "

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

\n"; + $sHtml .= "
\n"; + $sHtml .= "sInputid}\" value=\"0\"/>"; + $sHtml .= "  "; + $sHtml .= "
\n"; + $sHtml .= "\n"; + $oPage->add($sHtml); + //$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->sInputId}.SearchObjectsToAdd);"); + //$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix}').resize(oWidget{$this->siInputId}.UpdateSizes);"); + } + + /** + * 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 string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of $this->sLinkedClass + * @param array $aAlreadyLinked Array of indentifiers of objects which are already linke to the current object (or about to be linked) + * @param DBObject $oCurrentObj The object currently being edited... if known... + */ + public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinked = array(), $oCurrentObj = null) + { + if ($sRemoteClass == '') + { + $sRemoteClass = $this->sLinkedClass; + } + $oFilter = new DBObjectSearch($sRemoteClass); + if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($sRemoteClass, $this->sClass)) + { + // Prevent linking to self if the linked object is of the same family + // and laready present in the database + if (!$oCurrentObj->IsNew()) + { + $oFilter->AddCondition('id', $oCurrentObj->GetKey(), '!='); + } + } + if (count($aAlreadyLinked) > 0) + { + $oFilter->AddCondition('id', $aAlreadyLinked, 'NOTIN'); + } + $oSet = new CMDBObjectSet($oFilter); + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oP, "ResultsToAdd_{$this->sInputid}", array('menu' => false, 'cssCount'=> '#count_'.$this->sInputid , 'selection_mode' => true, 'table_id' => 'add_'.$this->sInputid)); // Don't display the 'Actions' menu on the results + } + + public function DoAddObjects(WebPage $oP, $oFullSetFilter) + { + $aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter); + foreach($aLinkedObjectIds as $iObjectId) + { + $oLinkObj = MetaModel::GetObject($this->sLinkedClass, $iObjectId); + $oP->add($this->GetObjectRow($oP, $oLinkObj, $oLinkObj->GetKey())); + } + } + public function GetObjectModificationDlg() { @@ -238,9 +322,9 @@ class UILinksWidgetDirect } return $aAttribs; } + public function GetRow($oPage, $sRealClass, $aValues, $iTempId) { - $aAttribs = $this->GetTableConfig(); if ($sRealClass == '') { $sRealClass = $this->sLinkedClass; @@ -248,17 +332,60 @@ class UILinksWidgetDirect $oLinkObj = new $sRealClass(); $oLinkObj->UpdateObjectFromPostedForm($this->sInputid); + return $this->GetObjectRow($oPage, $oLinkObj, $iTempId); + } + + protected function GetObjectRow($oPage, $oLinkObj, $iTempId) + { + $aAttribs = $this->GetTableConfig(); $aRow = array(); - $aRow['form::select'] = ''; + $aRow['form::select'] = ''; foreach($this->aZlist as $sLinkedAttCode) { $aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode); } - return $oPage->GetTableRow($aRow, $aAttribs); + return $oPage->GetTableRow($aRow, $aAttribs); } - public function UpdateFromArray($oObj, $aData) + /** + * Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context + * @param DBObject $oSourceObj + * @param DBObjectSearch $oSearch + */ + protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch) { - + $oAppContext = new ApplicationContext(); + $sSrcClass = get_class($oSourceObj); + $sDestClass = $oSearch->GetClass(); + foreach($oAppContext->GetNames() as $key) + { + // Find the value of the object corresponding to each 'context' parameter + $aCallSpec = array($sSrcClass, 'MapContextParam'); + $sAttCode = ''; + if (is_callable($aCallSpec)) + { + $sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter + } + + if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode)) + { + $oAttDef = MetaModel::GetAttributeDef($sSrcClass, $sAttCode); + $defaultValue = $oSourceObj->Get($sAttCode); + + // Find the attcode for the same 'context' parameter in the destination class + // and sets its value as the default value for the search condition + $aCallSpec = array($sDestClass, 'MapContextParam'); + $sAttCode = ''; + if (is_callable($aCallSpec)) + { + $sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter + } + + if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue)) + { + $oSearch->AddCondition($sAttCode, $defaultValue); + } + } + } } } diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 3c55690a7..ff5cfa7eb 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -92,6 +92,7 @@ define('LINKSET_EDITMODE_NONE', 0); // The linkset cannot be edited at all from define('LINKSET_EDITMODE_ADDONLY', 1); // The only possible action is to open a new window to create a new object define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place +define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place /** diff --git a/core/dbobject.class.php b/core/dbobject.class.php index ed61f0b84..8bdcc6521 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1201,24 +1201,39 @@ abstract class DBObject foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) { if (!$oAttDef->IsLinkSet()) continue; - + + $oOriginalSet = $this->m_aOrigValues[$sAttCode]; + if ($oOriginalSet != null) + { + $aOriginalList = $oOriginalSet->ToArray(); + } + else + { + $aOriginalList = array(); + } + $oLinks = $this->Get($sAttCode); $oLinks->Rewind(); while ($oLinkedObject = $oLinks->Fetch()) { - $oLinkedObject->Set($oAttDef->GetExtKeyToMe(), $this->m_iKey); + if (!array_key_exists($oLinkedObject->GetKey(), $aOriginalList)) + { + // New object added to the set, make it point properly + $oLinkedObject->Set($oAttDef->GetExtKeyToMe(), $this->m_iKey); + } if ($oLinkedObject->IsModified()) { + // Objects can be modified because: + // 1) They've just been added into the set, so their ExtKey is modified + // 2) They are about to be removed from the set BUT NOT deleted, their ExtKey has been reset $oLinkedObject->DBWrite(); } } // Delete the objects that were initialy present and disappeared from the list // (if any) - $oOriginalSet = $this->m_aOrigValues[$sAttCode]; - if ($oOriginalSet != null) + if (count($aOriginalList) > 0) { - $aOriginalList = $oOriginalSet->ToArray(); $aNewSet = $oLinks->ToArray(); foreach($aOriginalList as $iId => $oObject) diff --git a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml index ac2e42cb8..4d27791e4 100755 --- a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml +++ b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml @@ -108,28 +108,28 @@ UserRequest parent_change_id - none + add_remove 0 0 Incident parent_change_id - none + add_remove 0 0 Problem related_change_id - none + add_remove 0 0 Change parent_id - none + add_remove 0 0 diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 661179c51..4f40e1366 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -1189,6 +1189,9 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:CSVImportCreated_items' => 'Created: %1$d', 'UI:CSVImportModified_items' => 'Modified: %1$d', 'UI:CSVImportUnchanged_items' => 'Unchanged: %1$d', - + + 'UI:Button:Remove' => 'Remove', + 'UI:AddAnExisting_Class' => 'Add objects of type %1$s...', + 'UI:SelectionOf_Class' => 'Selection of objects of type %1$s', )); ?> diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 467dcbb05..cef4540dd 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -1025,5 +1025,9 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:CSVImportCreated_items' => 'Créations: %1$d', 'UI:CSVImportModified_items' => 'Modifications: %1$d', 'UI:CSVImportUnchanged_items' => 'Inchangés: %1$d', + + 'UI:Button:Remove' => 'Enlever', + 'UI:AddAnExisting_Class' => 'Ajouter des objets de type %1$s...', + 'UI:SelectionOf_Class' => 'Sélection d\'objets de type %1$s', )); ?> diff --git a/js/linksdirectwidget.js b/js/linksdirectwidget.js index 05c4bc2b9..ac5e190ae 100644 --- a/js/linksdirectwidget.js +++ b/js/linksdirectwidget.js @@ -16,8 +16,13 @@ $(function() labels: { 'delete': 'Delete', modify: 'Modify...' , creation_title: 'Creation of a new object...' , - create: 'Create...' - } + create: 'Create...', + add: 'Add...', + remove: 'Remove', + selection_title: 'Objects selection' + }, + buttons: ['create', 'delete'], + oWizardHelper: null }, // the constructor @@ -31,34 +36,49 @@ $(function() this.datatable = this.element.find('table.listResults'); - this.deleteBtn = $(''); - this.modifyBtn = $(''); - this.createBtn = $(''); + var aButtonsTypes = ['delete', 'remove', 'modify', 'add', 'create']; + this.oButtons = {}; + for(k in aButtonsTypes) + { + this.oButtons[aButtonsTypes[k]] = $(''); + } this.indicator = $(''); this.inputToBeCreated = $(''); this.toBeCreated = {}; this.inputToBeDeleted = $(''); this.toBeDeleted = []; + this.inputToBeAdded = $(''); + this.toBeAdded = []; + this.inputToBeRemoved = $(''); + this.toBeRemoved = []; this.element .after(this.inputToBeCreated) .after(this.inputToBeDeleted) + .after(this.inputToBeAdded) + .after(this.inputToBeRemoved) .after('      ') - .after(this.indicator).after(this.createBtn).after('   ') - .after(this.modifyBtn).after('   ') - .after(this.deleteBtn); + .after(this.indicator); + for(k in this.options.buttons) + { + this.element.after(this.oButtons[this.options.buttons[k]]).after('   '); + } this.element.find('.selectList'+this.id).bind('change', function() { me._updateButtons(); }); - this.deleteBtn.click(function() { + this.oButtons['delete'].click(function() { $('.selectList'+me.id+':checked', me.element).each( function() { me._deleteRow($(this)); }); }); - this.createBtn.click(function() { + this.oButtons['create'].click(function() { me._createRow(); }); - - this.modifyBtn.hide(); //hidden for now since it's not yet implemented - + this.oButtons['remove'].click(function() { + $('.selectList'+me.id+':checked', me.element).each( function() { me._removeRow($(this)); }); + }); + this.oButtons['add'].click(function() { + me._selectToAdd(); + }); + this._updateButtons(); }, @@ -94,18 +114,21 @@ $(function() switch(oChecked.length) { case 0: - this.deleteBtn.attr('disabled', 'disabled'); - this.modifyBtn.attr('disabled', 'disabled'); + this.oButtons['delete'].attr('disabled', 'disabled'); + this.oButtons['remove'].attr('disabled', 'disabled'); + this.oButtons['modify'].attr('disabled', 'disabled'); break; case 1: - this.deleteBtn.removeAttr('disabled'); - this.modifyBtn.removeAttr('disabled'); + this.oButtons['delete'].removeAttr('disabled'); + this.oButtons['remove'].removeAttr('disabled'); + this.oButtons['modify'].removeAttr('disabled'); break; default: - this.deleteBtn.removeAttr('disabled'); - this.modifyBtn.attr('disabled', 'disabled'); + this.oButtons['delete'].removeAttr('disabled'); + this.oButtons['remove'].removeAttr('disabled'); + this.oButtons['modify'].attr('disabled', 'disabled'); break; } }, @@ -122,7 +145,7 @@ $(function() }, _createRow: function() { - this.createBtn.attr('disabled', 'disabled'); + this.oButtons['create'].attr('disabled', 'disabled'); this.indicator.html(''); oParams = this.options.submit_parameters; oParams.operation = 'createObject'; @@ -147,10 +170,184 @@ $(function() close: function() { me._onDlgClose(); } }); me.indicator.html(''); - me.createBtn.removeAttr('disabled'); + me.oButtons['create'].removeAttr('disabled'); me._updateDlgSize(); }); }, + _selectToAdd: function() + { + this.oButtons['add'].attr('disabled', 'disabled'); + this.indicator.html(''); + oParams = this.options.submit_parameters; + oParams.operation = 'selectObjectsToAdd'; + oParams['class'] = this.options.class_name; + oParams.real_class = ''; + oParams.att_code = this.options.att_code; + oParams.iInputId = this.id; + if (this.options.oWizardHelper) + { + this.options.oWizardHelper.UpdateWizard(); + oParams.json = this.options.oWizardHelper.ToJSON(); + } + var me = this; + $.post(this.options.submit_to, oParams, function(data){ + me.oDlg = $('
'); + $('body').append(me.oDlg); + me.oDlg.html(data); + me.oDlg.find('form').removeAttr('onsubmit').bind('submit', function() { me._onSearchToAdd(); return false; } ); + me.oDlg.find('button.cancel').unbind('click').click( function() { me.oDlg.dialog('close'); } ); + me.oDlg.find('button.ok').unbind('click').click( function() { me._onDoAdd(); } ); + + me.oDlg.dialog({ + title: me.options.labels['selection_title'], + modal: true, + width: 'auto', + height: 'auto', + position: { my: "center", at: "center", of: window }, + close: function() { me._onDlgClose(); } + }); + me.indicator.html(''); + me.oButtons['add'].removeAttr('disabled'); + me._onSearchToAdd(); + me._updateDlgSize(); + }); + }, + _onSearchToAdd: function() + { + var oParams = {}; + // Gather the parameters from the search form + $('#SearchFormToAdd_'+this.id+' :input').each( function() { + if (this.name != '') + { + var val = $(this).val(); // supports multiselect as well + if (val !== null) + { + oParams[this.name] = val; + } + } + }); + // Gather the already linked target objects + oParams.aAlreadyLinked = new Array(); + $('#'+this.id+' .listResults td input:checkbox').each(function(){ + iKey = parseInt(this.value, 10); // Numbers are in base 10 + oParams.aAlreadyLinked.push(iKey); + } + ); oParams.operation = 'searchObjectsToAdd2'; + oParams['class'] = this.options.class_name; + oParams.real_class = ''; + oParams.att_code = this.options.att_code; + oParams.iInputId = this.id; + if (this.options.oWizardHelper) + { + this.options.oWizardHelper.UpdateWizard(); + oParams.json = this.options.oWizardHelper.ToJSON(); + } + var me = this; + $('#SearchResultsToAdd_'+me.id).block(); + $.post(this.options.submit_to, oParams, function(data) { + + $('#SearchResultsToAdd_'+me.id).html(data); + $('#SearchResultsToAdd_'+me.id+' .listResults').tableHover(); + $('#count_'+me.id).change(function() { + var c = this.value; + me._onUpdateDlgButtons(c); + }); + $('#SearchResultsToAdd_'+me.id).unblock(); + }); + //alert("C'est parti mon kiki !"); + return false; // Stay on the page, no submit + }, + _getSelection: function(sName) + { + // Gather the parameters from the search form + var oMap = {}; + var oContext = $('#SearchResultsToAdd_'+this.id); + var selectionMode = $(':input[name=selectionMode]', oContext); + if (selectionMode.length > 0) + { + // Paginated table retrieve the mode and the exceptions + var sMode = selectionMode.val(); + oMap['selectionMode'] = sMode; + $('#fs_SearchFormToAdd_'+this.id+' :input').each( + function(i) + { + oMap[this.name] = this.value; + } + ); + $(':input[name^=storedSelection]', oContext).each(function() { + if (oMap[this.name] == undefined) + { + oMap[this.name] = new Array(); + } + oMap[this.name].push(this.value); + }); + // Retrieve the 'filter' definition + var table = $('#ResultsToAdd_'+this.id).find('table.listResults')[0]; + oMap['filter'] = table.config.filter; + oMap['extra_params'] = table.config.extra_params; + } + // Normal table, retrieve all the checked check-boxes + $(':checked[name^=selectObject]', oContext).each( + function(i) + { + if ( (this.name != '') && ((this.type != 'checkbox') || (this.checked)) ) + { + arrayExpr = /\[\]$/; + if (arrayExpr.test(this.name)) + { + // Array + if (oMap[this.name] == undefined) + { + oMap[this.name] = new Array(); + } + oMap[this.name].push(this.value); + } + else + { + oMap[this.name] = this.value; + } + } + } + ); + return oMap; + }, + _onUpdateDlgButtons: function(iCount) + { + if (iCount > 0) + { + this.oDlg.find('button.ok').removeAttr('disabled'); + } + else + { + this.oDlg.find('button.ok').attr('disabled', 'disabled'); + } + }, + _onDoAdd:function() + { + var oParams = this._getSelection('selectObject'); + this.oDlg.dialog('close'); + oParams.operation = 'doAddObjects2'; + oParams['class'] = this.options.class_name; + oParams.att_code = this.options.att_code; + oParams.iInputId = this.id; + var me = this; + $.post(this.options.submit_to, oParams, function(data) { + var oInserted = $(data); + oInserted.find('input:checkbox').each(function() { + var iKey = parseInt($(this).val(), 10); // Number in base 10 + me.toBeAdded.push(iKey); + me.toBeRemoved = me._ArrayRemove(me.toBeRemoved, iKey); + me.toBeDeleted = me._ArrayRemove(me.toBeDeleted, iKey); + }); + me.inputToBeAdded.val(JSON.stringify(me.toBeAdded)); + me.inputToBeRemoved.val(JSON.stringify(me.toBeRemoved)); + me.inputToBeDeleted.val(JSON.stringify(me.toBeDeleted)); + me.datatable.find('tbody').append(data); + me._updateTable(); + me.indicator.html(''); + me.oButtons['add'].removeAttr('disabled'); + }); + }, subclassSelected: function() { var sRealClass = this.oDlg.find('select[name="class"]').val(); @@ -204,14 +401,14 @@ $(function() oParams.tempId = nextIdx; var me = this; - this.createBtn.attr('disabled', 'disabled'); + this.oButtons['create'].attr('disabled', 'disabled'); this.indicator.html(''); $.post(this.options.submit_to, oParams, function(data){ me.datatable.find('tbody').append(data); me._updateTable(); me.indicator.html(''); - me.createBtn.removeAttr('disabled'); + me.oButtons['create'].removeAttr('disabled'); }); } }, @@ -227,13 +424,22 @@ $(function() if (iObjKey > 0) { // Existing objet: add it to the "to be deleted" list - this.toBeDeleted.push(iObjKey); - this.inputToBeDeleted.val(JSON.stringify(this.toBeDeleted)); + // if it has not just been added now + if (this._InArray(this.toBeAdded, iObjKey)) + { + this.toBeAdded = this._ArrayRemove(this.toBeAdded, iObjKey); + this.inputToBeAdded.val(JSON.stringify(this.toBeAdded)); + } + else + { + this.toBeDeleted.push(iObjKey); + this.inputToBeDeleted.val(JSON.stringify(this.toBeDeleted)); + } } else { // Object to be created, just remove it from the "to be created" list - this.toBeCreated[-iObjKey] = undefined; + this.toBeCreated = this._ArrayRemove(this.toBeCreated, iObjKey); this.inputToBeCreated.val(JSON.stringify(this.toBeCreated)); } // Now remove the row from the table @@ -241,6 +447,61 @@ $(function() oRow.remove(); this._updateButtons(); this._updateTable(); + }, + _removeRow: function(oCheckbox) + { + var iObjKey = parseInt(oCheckbox.val(), 10); // Number in base 10 + + if (iObjKey > 0) + { + // Existing objet: add it to the "to be removed" list + // if it has not just been added now + if (this._InArray(this.toBeAdded, iObjKey)) + { + this.toBeAdded = this._ArrayRemove(this.toBeAdded, iObjKey); + this.inputToBeAdded.val(JSON.stringify(this.toBeAdded)); + } + else + { + this.toBeRemoved.push(iObjKey); + this.inputToBeRemoved.val(JSON.stringify(this.toBeRemoved)); + } + } + else + { + // Object to be created, just remove it from the "to be created" list + this.toBeCreated = this._ArrayRemove(this.toBeCreated, iObjKey); + this.inputToBeCreated.val(JSON.stringify(this.toBeCreated)); + } + // Now remove the row from the table + oRow = oCheckbox.closest('tr'); + oRow.remove(); + this._updateButtons(); + this._updateTable(); + }, + _InArray: function(aArrayToSearch, needle) + { + aRes = []; + for(k in aArrayToSearch) + { + if (aArrayToSearch[k] == needle) + { + return true; + } + } + return false; + }, + _ArrayRemove: function(aArrayToFilter, needle) + { + aRes = []; + for(k in aArrayToFilter) + { + if (aArrayToFilter[k] != needle) + { + aRes.push(aArrayToFilter[k]); + } + } + return aRes; } }); }); \ No newline at end of file diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 534a06af3..8b1e189eb 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -285,7 +285,68 @@ try $aValues = utils::ReadParam('values', array(), false, 'raw_data'); $oPage->SetContentType('text/html'); $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); - $oPage->add($oWidget->GetRow($oPage, $sRealClass, $aValues, $iTempId)); + $oPage->add($oWidget->GetRow($oPage, $sRealClass, $aValues, -$iTempId)); + break; + + // ui.linksdirectwidget + case 'selectObjectsToAdd': + $oPage->SetContentType('text/html'); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + $oObj = null; + if ($sJson != '') + { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + } + $sRealClass = utils::ReadParam('real_class', '', false, 'class'); + $sAttCode = utils::ReadParam('att_code', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $iCurrObjectId = utils::ReadParam('iObjId', 0); + $oPage->SetContentType('text/html'); + $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); + $oWidget->GetObjectsSelectionDlg($oPage, $oObj); + break; + + // ui.linksdirectwidget + case 'searchObjectsToAdd2': + $oPage->SetContentType('text/html'); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sRealClass = utils::ReadParam('real_class', '', false, 'class'); + $sAttCode = utils::ReadParam('att_code', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + $oObj = null; + if ($sJson != '') + { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + } + $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); + $oWidget->SearchObjectsToAdd($oPage, $sRealClass, $aAlreadyLinked, $oObj); + break; + + // ui.linksdirectwidget + case 'doAddObjects2': + $oPage->SetContentType('text/html'); + $oPage->SetContentType('text/html'); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sRealClass = utils::ReadParam('real_class', '', false, 'class'); + $sAttCode = utils::ReadParam('att_code', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $iCurrObjectId = utils::ReadParam('iObjId', 0); + $sFilter = utils::ReadParam('filter', ''); + if ($sFilter != '') + { + $oFullSetFilter = DBObjectSearch::unserialize($sFilter); + } + else + { + $oFullSetFilter = new DBObjectSearch($sRemoteClass); + } + $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); + $oWidget->DoAddObjects($oPage, $oFullSetFilter); break; //////////////////////////////////////////////////////////// @@ -456,6 +517,8 @@ try $oWidget->DoAddObjects($oPage, $oFullSetFilter, $oObj); break; + //////////////////////////////////////////////////////////// + case 'wizard_helper_preview': $oPage->SetContentType('text/html'); $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index dc7163fcd..42759fad7 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -425,6 +425,7 @@ EOF; 'add_only' => 'LINKSET_EDITMODE_ADDONLY', 'actions' => 'LINKSET_EDITMODE_ACTIONS', 'in_place' => 'LINKSET_EDITMODE_INPLACE', + 'add_remove' => 'LINKSET_EDITMODE_ADDREMOVE', ); if (!array_key_exists($sEditMode, $aXmlToPHP))