diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 82ee6bd03..d7e6bb8cb 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1971,6 +1971,11 @@ HTML $sNullValue = $oAttDef->GetNullValue(); // used for the ValidateField() call in js/forms-json-utils.js $sFieldToValidateId = $iId; // can be different than the displayed field (for example in TagSet) + // List of attributes that depend on the current one + // Might be modified depending on the current field + $sWizardHelperJsVarName = "oWizardHelper{$sFormPrefix}"; + $aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); + switch ($oAttDef->GetEditClass()) { case 'Date': @@ -2261,6 +2266,7 @@ EOF break; case 'ExtKey': + /** @var \AttributeExternalKey $oAttDef */ $aEventsList[] = 'validate'; $aEventsList[] = 'change'; @@ -2279,6 +2285,19 @@ EOF $sHTMLValue = UIExtKeyWidget::DisplayFromAttCode($oPage, $sAttCode, $sClass, $oAttDef->GetLabel(), $oAllowedValues, $value, $iId, $bMandatory, $sFieldName, $sFormPrefix, $aExtKeyParams); $sHTMLValue .= "\n"; + + $bHasExtKeyUpdatingRemoteClassFields = ( + array_key_exists('replaceDependenciesByRemoteClassFields', $aArgs) + && ($aArgs['replaceDependenciesByRemoteClassFields']) + ); + if ($bHasExtKeyUpdatingRemoteClassFields) + { + // On this field update we need to update all the corresponding remote class fields + // Used when extkey widget is in a linkedset indirect + $sWizardHelperJsVarName = $aArgs['wizHelperRemote']; + $aDependencies = $aArgs['remoteCodes']; + } + break; case 'RedundancySetting': @@ -2476,15 +2495,39 @@ EOF $sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number } $sOriginalValue = ($iFlags & OPT_ATT_MUSTCHANGE) ? json_encode($value) : 'undefined'; - $oPage->add_ready_script("$('#$sFieldToValidateId').bind('".implode(' ', - $aEventsList)."', function(evt, sFormId) { return ValidateField('$sFieldToValidateId', '$sPattern', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );\n"); // Bind to a custom event: validate + $sEventList = implode(' ', $aEventsList); + $oPage->add_ready_script(<< 0) { - // Unbind first to avoid duplicate event handlers in case of reload of the whole (or part of the) form - $oPage->add_ready_script("$('#$iId').unbind('change.dependencies').bind('change.dependencies', function(evt, sFormId) { return oWizardHelper{$sFormPrefix}.UpdateDependentFields(['".implode("','", - $aDependencies)."']) } );\n"); // Bind to a custom event: validate + //--- Add an event handler to launch a custom event: validate + // * Unbind first to avoid duplicate event handlers in case of reload of the whole (or part of the) form + // * We were using off/on directly on the node before, but that was causing an issue when adding dynamically new nodes + // indeed the events weren't attached on the of the new nodes ! + // So we're adding the handler on a node above, and we're using a selector to catch only the event we're interested in ! + $sDependencies = implode("','", $aDependencies); + + $oPage->add_ready_script(<<add_dict_entry('UI:ValueMustBeSet'); diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 991dca5b8..12c7ea464 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -130,45 +130,39 @@ class UILinksWidget $aRow = array(); $aFieldsMap = array(); $iKey = 0; - if(is_object($linkObjOrId) && (!$linkObjOrId->IsNew())) + + if (is_object($linkObjOrId) && (!$linkObjOrId->IsNew())) { $iKey = $linkObjOrId->GetKey(); - $iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote); + $iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote); $sPrefix .= "[$iKey]["; $sNameSuffix = "]"; // To make a tabular form $aArgs['prefix'] = $sPrefix; $aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$iKey}"; $aArgs['this'] = $linkObjOrId; - if($bReadOnly) - { - $aRow['form::checkbox'] = ""; - foreach($this->m_aEditableFields as $sFieldCode) - { - $sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode); - $aRow[$sFieldCode] = $sDisplayValue; - } - } - else - { - $aRow['form::checkbox'] = "m_iInputId.".OnSelectChange();\" value=\"$iKey\">"; - foreach($this->m_aEditableFields as $sFieldCode) - { - $sRowFieldCode = ($sFieldCode === $this->m_sExtKeyToRemote) ? 'static::key' : $sFieldCode; - $sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']'; - $sSafeId = utils::GetSafeId($sFieldId); - $sValue = $linkObjOrId->Get($sFieldCode); - $sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode); - $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); - $aRow[$sRowFieldCode] = '
'. - cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, - $sDisplayValue, $sSafeId, $sNameSuffix, 0, $aArgs). - '
'; - $aFieldsMap[$sFieldCode] = $sSafeId; - } - } + if ($bReadOnly) + { + $aRow['form::checkbox'] = ""; + foreach ($this->m_aEditableFields as $sFieldCode) + { + $sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode); + $aRow[$sFieldCode] = $sDisplayValue; + } + } + else + { + $aRow['form::checkbox'] = "m_iInputId.".OnSelectChange();\" value=\"$iKey\">"; + foreach ($this->m_aEditableFields as $sFieldCode) + { + $sSafeFieldId = $this->GetFieldId($linkObjOrId->GetKey(), $sFieldCode); + $this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $linkObjOrId, $oP, $sNameSuffix, $sSafeFieldId); + $aFieldsMap[$sFieldCode] = $sSafeFieldId; + } + } $sState = $linkObjOrId->GetState(); + $sRemoteKeySafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $this->m_sExtKeyToRemote);; } else { @@ -178,15 +172,18 @@ class UILinksWidget // New link existing only in memory $oNewLinkObj = $linkObjOrId; $iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote); - $oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields + $oNewLinkObj->Set($this->m_sExtKeyToMe, + $oCurrentObj); // Setting the extkey with the object also fills the related external fields } else { $iRemoteObjKey = $linkObjOrId; $oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass); $oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey); - $oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields - $oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields + $oNewLinkObj->Set($this->m_sExtKeyToRemote, + $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields + $oNewLinkObj->Set($this->m_sExtKeyToMe, + $oCurrentObj); // Setting the extkey with the object also fills the related external fields } $sPrefix .= "[-$iUniqueId]["; $sNameSuffix = "]"; // To make a tabular form @@ -220,59 +217,133 @@ EOF foreach($this->m_aEditableFields as $sFieldCode) { - $sRowFieldCode = ($sFieldCode === $this->m_sExtKeyToRemote) ? 'static::key' : $sFieldCode; - $sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.-$iUniqueId.']'; - $sSafeId = utils::GetSafeId($sFieldId); + $sSafeFieldId = $this->GetFieldId($iUniqueId, $sFieldCode); + $this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $oNewLinkObj, $oP, $sNameSuffix, $sSafeFieldId); + $aFieldsMap[$sFieldCode] = $sSafeFieldId; + $sValue = $oNewLinkObj->Get($sFieldCode); - $sDisplayValue = $oNewLinkObj->GetEditValue($sFieldCode); - $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); - $aRow[$sRowFieldCode] = '
'. - cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, - $sSafeId /* id */, $sNameSuffix, 0, $aArgs). - '
'; - $aFieldsMap[$sFieldCode] = $sSafeId; - $oP->add_ready_script(<<add_ready_script( + <<m_iInputId}.OnValueChange($iKey, $iUniqueId, '$sFieldCode', '$sValue'); -EOF +JS ); } + $sState = ''; + $sRemoteKeySafeFieldId = $this->GetFieldId($iUniqueId, $this->m_sExtKeyToRemote); } - if(!$bReadOnly) - { - $sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe); - $aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId; - $aRow['form::checkbox'] .= "GetKey()."\">"; + if (!$bReadOnly) + { + $sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe); + $aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId; + $aRow['form::checkbox'] .= "GetKey()."\">"; - $sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote); - $aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId; - $aRow['form::checkbox'] .= ""; - } + $sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote); + $aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId; + $aRow['form::checkbox'] .= ""; + } + // Adding fields from remote class + // all fields are embedded in a span + added to $aFieldsMap array so that we can refresh them after extkey change + $aRemoteFieldsMap = []; + foreach (MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode) + { + $sSafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $sFieldCode); + $aRow['static::'.$sFieldCode] = "".$oLinkedObj->GetAsHTML($sFieldCode).''; + $aRemoteFieldsMap[$sFieldCode] = $sSafeFieldId; + } + // id field is needed so that remote object could be load server side + $aRemoteFieldsMap['id'] = $sRemoteKeySafeFieldId; + + // Generate WizardHelper to update dependant fields + $this->AddWizardHelperInit($oP, $aArgs['wizHelper'], $this->m_sLinkedClass, $sState, $aFieldsMap); + //instantiate specific WizarHelper instance for remote class fields refresh + $bHasExtKeyUpdatingRemoteClassFields = ( + array_key_exists('replaceDependenciesByRemoteClassFields', $aArgs) + && ($aArgs['replaceDependenciesByRemoteClassFields']) + ); + if ($bHasExtKeyUpdatingRemoteClassFields) + { + $this->AddWizardHelperInit($oP, $aArgs['wizHelperRemote'], $this->m_sRemoteClass, $sState, $aRemoteFieldsMap); + } + + return $aRow; + } + + private function AddRowForFieldCode(&$aRow, $sFieldCode, &$aArgs, $oLnk, $oP, $sNameSuffix, $sSafeFieldId): void + { + if (($sFieldCode === $this->m_sExtKeyToRemote)) + { + // current field is the lnk extkey to the remote class + $aArgs['replaceDependenciesByRemoteClassFields'] = true; + $sRowFieldCode = 'static::key'; + $aArgs['wizHelperRemote'] = $aArgs['wizHelper'].'_remote'; + $aRemoteAttDefs = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->m_sRemoteClass); + $aRemoteCodes = array_map( + function ($value) { + return $value->GetCode(); + }, + $aRemoteAttDefs + ); + $aArgs['remoteCodes'] = $aRemoteCodes; + } + else + { + $aArgs['replaceDependenciesByRemoteClassFields'] = false; + $sRowFieldCode = $sFieldCode; + } + $sValue = $oLnk->Get($sFieldCode); + $sDisplayValue = $oLnk->GetEditValue($sFieldCode); + $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); + + $aRow[$sRowFieldCode] = '
' + .cmdbAbstractObject::GetFormElementForField( + $oP, + $this->m_sLinkedClass, + $sFieldCode, + $oAttDef, + $sValue, + $sDisplayValue, + $sSafeFieldId, + $sNameSuffix, + 0, + $aArgs + ) + .'
'; + } + + private function GetFieldId($iLnkId, $sFieldCode, $bSafe = true) + { + $sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$iLnkId.']'; + + return ($bSafe) ? utils::GetSafeId($sFieldId) : $sFieldId; + } + + private function AddWizardHelperInit($oP, $sWizardHelperVarName, $sWizardHelperClass, $sState, $aFieldsMap): void + { $iFieldsCount = count($aFieldsMap); $sJsonFieldsMap = json_encode($aFieldsMap); - + $oP->add_script( -<<m_sLinkedClass}', '', '$sState'); -{$aArgs['wizHelper']}.SetFieldsMap($sJsonFieldsMap); -{$aArgs['wizHelper']}.SetFieldsCount($iFieldsCount); -EOF + <<m_sRemoteClass, 'list') as $sFieldCode) - { - $aRow['static::'.$sFieldCode] = $oLinkedObj->GetAsHTML($sFieldCode); - } - return $aRow; } /** * Display one row of the whole form + * * @param WebPage $oP * @param array $aConfig * @param array $aRow * @param int $iRowId + * * @return string */ protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId) diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php index dbcfe085a..bb3bcc578 100644 --- a/application/wizardhelper.class.inc.php +++ b/application/wizardhelper.class.inc.php @@ -335,20 +335,41 @@ class WizardHelper { $sResult = $this->m_aData['m_oFieldsMap'][$sFieldName]; } + return $sResult; } - + + public function GetReturnNotEditableFields() + { + return $this->m_aData['m_bReturnNotEditableFields'] ?? false; + } + + /** + * @return string JS code to be executed for fields update + * @since 2.8.0 N°3198 + */ + public function GetJsForUpdateFields() + { + $sWizardHelperJsVar = ($this->m_aData['m_sWizHelperJsVarName']) ?? 'oWizardHelper'.$this->GetFormPrefix(); + $sWizardHelperJson = $this->ToJSON(); + + return << $value) + foreach ($aLinkObj as $sAttCode => $value) { $oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode); - if (($oAttDef->IsExternalKey()) && ($value != '') && ($value > 0)) + if (($oAttDef->IsExternalKey()) && ($value != '') && ($value > 0)) { // For external keys: load the target object so that external fields // get filled too diff --git a/js/extkeywidget.js b/js/extkeywidget.js index b7c086e33..a64d14e5e 100644 --- a/js/extkeywidget.js +++ b/js/extkeywidget.js @@ -377,6 +377,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper $('#'+me.id).val(iObjectId); if (prevValue != iObjectId) { + // dependent fields will be updated using the WizardHelper JS object $('#'+me.id).trigger('validate'); $('#'+me.id).trigger('extkeychange'); $('#'+me.id).trigger('change'); diff --git a/js/wizardhelper.js b/js/wizardhelper.js index a29069371..f5f2b1b57 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -67,7 +67,9 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) { 'm_oAllowedValues': {}, 'm_iFieldsCount': 0, 'm_sFormPrefix': sFormPrefix, - 'm_sState': sState + 'm_sState': sState, + 'm_bReturnNotEditableFields': false, // if true then will return values and not editable fields + 'm_sWizHelperJsVarName': null // if set will use this name when server returns JS code in \WizardHelper::GetJsForUpdateFields }; this.m_oData.m_sClass = sClass; @@ -111,6 +113,14 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) { this.m_oData.m_oCurrentValues[sFieldName] = currentValue; }; + this.SetReturnNotEditableFields = function (bReturnNotEditableFields) { + this.m_oData.m_bReturnNotEditableFields = bReturnNotEditableFields; + }; + + this.SetWizHelperJsVarName = function (sWizHelperJsVarName) { + this.m_oData.m_sWizHelperJsVarName = sWizHelperJsVarName; + }; + this.ToJSON = function () { return JSON.stringify(this.m_oData); }; @@ -167,9 +177,9 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) { this.UpdateWizard = function () { //console.log('** UpdateWizard **') - for(sFieldCode in this.m_oData.m_oFieldsMap) + for (let sFieldCode in this.m_oData.m_oFieldsMap) { - sCleanFieldCode = sFieldCode.replace('"', ''); + let sCleanFieldCode = sFieldCode.replace('"', ''); //console.log(sFieldCode); this.UpdateCurrentValue(sCleanFieldCode); } @@ -202,16 +212,16 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) { }); }; - this.UpdateCurrentValue = function (sFieldCode) - { - $('#'+this.m_oData.m_oFieldsMap[sFieldCode]).trigger('update_value'); // Give the widget a chance to update its value (if it is aware of this event) - value = $('#'+this.m_oData.m_oFieldsMap[sFieldCode]).val(); + this.UpdateCurrentValue = function (sFieldCode) { + var $oField = $('#'+this.m_oData.m_oFieldsMap[sFieldCode]); + $oField.trigger('update_value'); // Give the widget a chance to update its value (if it is aware of this event) + var value = $oField.val(); if (value == '') { value = null; } this.m_oData.m_oCurrentValues[sFieldCode] = value; - return value; + return value; }; this.UpdateDependentFields = function (aFieldNames) { diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 1dc041b98..e7fa1fd3f 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -757,14 +757,15 @@ try $value = $oObj->Get($sAttCode); $displayValue = $oObj->GetEditValue($sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if (!$oAttDef->IsWritable()) + if (!$oAttDef->IsWritable() || ($oWizardHelper->GetReturnNotEditableFields())) { // Even non-writable fields (like AttributeExternal) can be refreshed $sHTMLValue = $oObj->GetAsHTML($sAttCode); } else { - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', $iFlags, array('this' => $oObj, 'formPrefix' => $sFormPrefix), false); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, + $displayValue, $sId, '', $iFlags, array('this' => $oObj, 'formPrefix' => $sFormPrefix), false); // Make sure that we immediately validate the field when we reload it $oPage->add_ready_script("$('#$sId').trigger('validate');"); } @@ -772,7 +773,7 @@ try } } } - $oPage->add_script("oWizardHelper{$sFormPrefix}.m_oData=".$oWizardHelper->ToJSON().";\noWizardHelper{$sFormPrefix}.UpdateFields();\n"); + $oPage->add_script($oWizardHelper->GetJsForUpdateFields()); break; case 'obj_creation_form':