diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index 91abac45a..a1bd816de 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -828,25 +828,29 @@ abstract class cmdbAbstractObject extends CMDBObject
if (!$oAttDef->IsExternalField())
{
$aCSSClasses = array();
+ $bMandatory = 0;
if ( (!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY))
{
$aCSSClasses[] = 'mandatory';
+ $bMandatory = 1;
}
$sCSSClasses = self::GetCSSClasses($aCSSClasses);
+ $sValidationField = "";
switch($oAttDef->GetEditClass())
{
case 'Date':
+ case 'DateTime':
$aCSSClasses[] = 'date-pick';
$sCSSClasses = self::GetCSSClasses($aCSSClasses);
- $sHTMLValue = "";
+ $sHTMLValue = "$sValidationField";
break;
case 'Password':
- $sHTMLValue = "";
+ $sHTMLValue = "$sValidationField";
break;
case 'Text':
- $sHTMLValue = "";
+ $sHTMLValue = "$sValidationField";
break;
case 'List':
@@ -884,7 +888,7 @@ abstract class cmdbAbstractObject extends CMDBObject
{
// too many choices, use an autocomplete
// The input for the auto complete
- $sHTMLValue = "";
+ $sHTMLValue = "$sValidationField";
// another hidden input to store & pass the object's Id
$sHTMLValue .= "\n";
$oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});");
@@ -906,15 +910,22 @@ abstract class cmdbAbstractObject extends CMDBObject
$sSelected = ($value == $key) ? ' selected' : '';
$sHTMLValue .= "\n";
}
- $sHTMLValue .= "\n";
+ $sHTMLValue .= "$sValidationField\n";
}
}
else
{
- $sHTMLValue = "";
+ $sHTMLValue = "$sValidationField";
}
break;
}
+ $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$';
+ $oPage->add_ready_script("$('#$iInputId').bind('validate blur', function(evt, sFormId) { return ValidateField('$iInputId', '$sPattern', $bMandatory, sFormId) } );"); // Bind to a custom event: validate
+ $aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that depend on the current one
+ if (count($aDependencies) > 0)
+ {
+ $oPage->add_ready_script("$('#$iInputId').bind('change', function(evt, sFormId) { return UpdateDependentFields(['".implode("','", $aDependencies)."']) } );"); // Bind to a custom event: validate
+ }
}
return $sHTMLValue;
}
@@ -923,12 +934,14 @@ abstract class cmdbAbstractObject extends CMDBObject
{
static $iFormId = 0;
$iFormId++;
+ $sClass = get_class($this);
$oAppContext = new ApplicationContext();
- $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
+ $sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$iKey = $this->GetKey();
$aDetails = array();
- $oPage->add("
\n");
+
+ $iFieldsCount = count($aFieldsMap);
+ $sJsonFieldsMap = json_encode($aFieldsMap);
+
+ $oPage->add_script(
+<<add("\n");
@@ -201,35 +202,45 @@ $sJSHandlerCode
}
// Now use the dependencies between the fields to order them
- while(count($aFields) > 0)
+ // Start from the order of the 'details'
+ $aList = MetaModel::GetZListItems($this->m_sClass, 'details');
+ $index = 0;
+ $aOrder = array();
+ foreach($aFields as $sAttCode => $void)
{
- $aCurrentStep = array();
- foreach($aFields as $sAttCode => $aDependencies)
+ $aOrder[$sAttCode] = 999; // At the end of the list...
+ }
+ foreach($aList as $sAttCode)
+ {
+ if (array_key_exists($sAttCode, $aFields))
{
- // All fields with no remaining dependencies can be entered at this
- // step of the wizard
- if (count($aDependencies) == 0)
+ $aOrder[$sAttCode] = $index;
+ }
+ $index++;
+ }
+ foreach($aFields as $sAttCode => $aDependencies)
+ {
+ // All fields with no remaining dependencies can be entered at this
+ // step of the wizard
+ if (count($aDependencies) > 0)
+ {
+ $iMaxPos = 0;
+ // Remove this field from the dependencies of the other fields
+ foreach($aDependencies as $sDependentAttCode => $void)
{
- $aCurrentStep[] = $sAttCode;
- $aFieldsDone[$sAttCode] = '';
- unset($aFields[$sAttCode]);
- // Remove this field from the dependencies of the other fields
- foreach($aFields as $sUpdatedCode => $aDummy)
- {
- // remove the dependency
- unset($aFields[$sUpdatedCode][$sAttCode]);
- }
+ // position the current field after the ones it depends on
+ $iMaxPos = max($iMaxPos, 1+$aOrder[$sDependentAttCode]);
}
}
- if (count($aCurrentStep) == 0)
- {
- // This step of the wizard would contain NO field !
- echo "Error: Circular reference in the dependencies between the fields.";
- print_r($aFields);
- break;
- }
- $aWizardSteps['mandatory'][] = $aCurrentStep;
}
+ asort($aOrder);
+ $aCurrentStep = array();
+ foreach($aOrder as $sAttCode => $rank)
+ {
+ $aCurrentStep[] = $sAttCode;
+ $aFieldsDone[$sAttCode] = '';
+ }
+ $aWizardSteps['mandatory'][] = $aCurrentStep;
// Now computes the steps to fill the optional fields
diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php
index 656b74589..b6eb2927b 100644
--- a/application/wizardhelper.class.inc.php
+++ b/application/wizardhelper.class.inc.php
@@ -16,9 +16,8 @@ class WizardHelper
public function GetTargetObject($bReadUploadedFiles = false)
{
$oObj = MetaModel::NewObject($this->m_aData['m_sClass']);
- foreach($this->m_aData['m_aCurrentValues'] as $iIndex => $value)
+ foreach($this->m_aData['m_oCurrentValues'] as $sAttCode => $value)
{
- $sAttCode = array_search($iIndex, $this->m_aData['m_oFieldsMap']);
// Because this is stored in a Javascript array, unused indexes
// are filled with null values
if ( ($sAttCode !== false) && ($value !== null))
@@ -104,7 +103,6 @@ class WizardHelper
// Protect against a request for a non existing field
if (isset($this->m_aData['m_oFieldsMap'][$sAttCode]))
{
- $iIndex = $this->m_aData['m_oFieldsMap'][$sAttCode];
$oAttDef = MetaModel::GetAttributeDef($this->m_aData['m_sClass'], $sAttCode);
if ($oAttDef->GetEditClass() == 'List')
{
@@ -124,13 +122,13 @@ class WizardHelper
}
$aData[] = $aRow;
}
- $this->m_aData['m_aDefaultValue'][$iIndex] = json_encode($aData);
+ $this->m_aData['m_oDefaultValue'][$sAttCode] = json_encode($aData);
}
else
{
// Normal handling for all other scalar attributes
- $this->m_aData['m_aDefaultValue'][$iIndex] = $value;
+ $this->m_aData['m_oDefaultValue'][$sAttCode] = $value;
}
}
}
@@ -145,8 +143,7 @@ class WizardHelper
// Protect against a request for a non existing field
if (isset($this->m_aData['m_oFieldsMap'][$sAttCode]))
{
- $iIndex = $this->m_aData['m_oFieldsMap'][$sAttCode];
- $this->m_aData['m_aAllowedValues'][$iIndex] = $sHtml;
+ $this->m_aData['m_oAllowedValues'][$sAttCode] = $sHtml;
}
}
diff --git a/css/light-grey.css b/css/light-grey.css
index e48fa60fc..c1b8e1b9a 100644
--- a/css/light-grey.css
+++ b/css/light-grey.css
@@ -478,6 +478,9 @@ div.wizStep span {
padding: 5px;
}
+.wizContainer table tr td {
+ background: transparent;
+}
.alignRight {
text-align: right;
padding: 3px;
diff --git a/images/validation_error.png b/images/validation_error.png
new file mode 100644
index 000000000..3dd3b55db
Binary files /dev/null and b/images/validation_error.png differ
diff --git a/images/validation_ok.png b/images/validation_ok.png
new file mode 100644
index 000000000..a674561c1
Binary files /dev/null and b/images/validation_ok.png differ
diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js
index 2a0140fff..3fe52d826 100644
--- a/js/forms-json-utils.js
+++ b/js/forms-json-utils.js
@@ -60,7 +60,7 @@ function GoToStep(iCurrentStep, iNextStep)
if (iNextStep > iCurrentStep)
{
// Check the values when moving forward
- if (CheckMandatoryFields('wizStep'+iCurrentStep))
+ if (CheckFields('wizStep'+iCurrentStep))
{
oCurrentStep.style.display = 'none';
ActivateStep(iNextStep);
@@ -85,70 +85,115 @@ function ActivateStep(iTargetStep)
}
oNextStep.style.display = '';
G_iCurrentStep = iTargetStep;
- $('#wizStep'+(iTargetStep)).block({ message: null });
+ //$('#wizStep'+(iTargetStep)).block({ message: null });
}
-function AjaxGetValuesDef(oObj, sClass, sAttCode, iFieldId)
-{
- var oJSON = document.getElementById(sJsonFieldId);
- $.get('ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode,
- { operation: "allowed_values" },
- function(data){
- //$('#field_'+iFieldId).html(data);
- }
- );
-}
+//function AjaxGetValuesDef(oObj, sClass, sAttCode, iFieldId)
+//{
+// var oJSON = document.getElementById(sJsonFieldId);
+// $.get('ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode,
+// { operation: "allowed_values" },
+// function(data){
+// //$('#field_'+iFieldId).html(data);
+// }
+// );
+//}
+//
+//function AjaxGetDefaultValue(oObj, sClass, sAttCode, iFieldId)
+//{
+// // Asynchronously call the server to provide a default value if the field is
+// // empty
+// if (oObj['m_aCurrValues'][sAttCode] == '')
+// {
+// var oJSON = document.getElementById(sJsonFieldId);
+// $.get('ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode,
+// { operation: "default_value" },
+// function(json_data){
+// var oObj = ReloadObjectFromServer(json_data);
+// UpdateFieldFromObject(iFieldId, aFieldsMap, oObj)
+// }
+// );
+// }
+//}
-function AjaxGetDefaultValue(oObj, sClass, sAttCode, iFieldId)
-{
- // Asynchronously call the server to provide a default value if the field is
- // empty
- if (oObj['m_aCurrValues'][sAttCode] == '')
- {
- var oJSON = document.getElementById(sJsonFieldId);
- $.get('ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode,
- { operation: "default_value" },
- function(json_data){
- var oObj = ReloadObjectFromServer(json_data);
- UpdateFieldFromObject(iFieldId, aFieldsMap, oObj)
- }
- );
- }
-}
+// Store the result of the form validation... there may be several forms per page, beware
+var oFormErrors = { err_form0: 0 };
-function CheckMandatoryFields(sFormId)
+function CheckFields(sFormId)
{
$('#'+sFormId+' :submit').attr('disable', 'disabled');
$('#'+sFormId+' :button[type=submit]').attr('disable', 'disabled');
firstErrorId = '';
- var iErrorsCount = 0;
- $('#'+sFormId+' :input.mandatory').each( function() {
- if (( this.value == '') || (this.value == 0))
- {
- this.style.backgroundColor = '#fcc';
- iErrorsCount++;
- if (iErrorsCount == 1)
- {
- firstErrorId = this.id;
- }
- }
- else
- {
- this.style.backgroundColor = '#fff';
- }
+ // The two 'fields' below will be updated when the 'validate' event is processed
+ oFormErrors['err_'+sFormId] = 0; // Number of errors encountered when validating the form
+ oFormErrors['input_'+sFormId] = null; // First 'input' with an error, to set the focus to it
+ $('#'+sFormId+' :input').each( function()
+ {
+ validateEventResult = $(this).trigger('validate', sFormId);
}
);
- if(iErrorsCount > 0)
+ if(oFormErrors['err_'+sFormId] > 0)
{
alert('Please fill-in all mandatory fields before continuing.');
$('#'+sFormId+' :submit').attr('disable', '');
$('#'+sFormId+' :button[type=submit]').attr('disable', '');
- if (firstErrorId != '')
+ if (oFormErrors['input_'+sFormId] != null)
{
- $('#'+firstErrorId).focus();
+ $('#'+oFormErrors['input_'+sFormId]).focus();
}
}
- return(iErrorsCount == 0);
+ return (oFormErrors['err_'+sFormId] == 0); // If no error, submit the form
+}
+
+function ValidateField(sFieldId, sPattern, bMandatory, sFormId)
+{
+ var bValid = true;
+ var currentVal = $('#'+sFieldId).val();
+ if (bMandatory && ((currentVal == '') || (currentVal == 0)))
+ {
+ bValid = false;
+ }
+ else if (sPattern != '')
+ {
+ re = new RegExp(sPattern);
+ //console.log('Validating field: '+sFieldId + ' current value: '+currentVal + ' pattern: '+sPattern );
+ bValid = re.test(currentVal);
+ }
+ if (bValid)
+ {
+ // Visual feedback
+ $('#v_'+sFieldId).html('
');
+ }
+ else
+ {
+ // Report the error...
+ oFormErrors['err_'+sFormId]++;
+ if (oFormErrors['input_'+sFormId] == null)
+ {
+ // Let's remember the first input with an error, so that we can put back the focus on it later
+ oFormErrors['input_'+sFormId] = sFieldId;
+ }
+ // Visual feedback
+ $('#v_'+sFieldId).html('
');
+ }
+ //console.log('Form: '+sFormId+' Validating field: '+sFieldId + ' current value: '+currentVal+' pattern: '+sPattern+' result: '+bValid );
+ return bValid;
+}
+
+function UpdateDependentFields(aFieldNames)
+{
+ //console.log('UpdateDependentFields:');
+ //console.log(aFieldNames);
+ index = 0;
+ oWizardHelper.ResetQuery();
+ oWizardHelper.UpdateWizard();
+ while(index < aFieldNames.length )
+ {
+ sAttCode = aFieldNames[index];
+ oWizardHelper.RequestAllowedValues(sAttCode);
+ index++;
+ }
+ oWizardHelper.AjaxQueryServer();
}
diff --git a/js/wizardhelper.js b/js/wizardhelper.js
index 9e8831ee9..94937816a 100644
--- a/js/wizardhelper.js
+++ b/js/wizardhelper.js
@@ -3,11 +3,11 @@ function WizardHelper(sClass)
{
this.m_oData = { 'm_sClass' : '',
'm_oFieldsMap': {},
- 'm_aCurrentValues': [],
+ 'm_oCurrentValues': {},
'm_aDefaultValueRequested': [],
'm_aAllowedValuesRequested': [],
- 'm_aDefaultValue': [],
- 'm_aAllowedValues': [],
+ 'm_oDefaultValue': {},
+ 'm_oAllowedValues': {},
'm_iFieldsCount' : 0
};
this.m_oData.m_sClass = sClass;
@@ -16,7 +16,6 @@ function WizardHelper(sClass)
this.SetFieldsMap = function (oFieldsMap)
{
this.m_oData.m_oFieldsMap = oFieldsMap;
-
}
this.SetFieldsCount = function (count)
@@ -28,7 +27,6 @@ function WizardHelper(sClass)
this.RequestDefaultValue = function (sFieldName)
{
currentValue = this.UpdateCurrentValue(sFieldName);
- sFieldId = this.m_oData.m_oFieldsMap[sFieldName];
if (currentValue == null)
{
this.m_oData.m_aDefaultValueRequested.push(sFieldName);
@@ -40,7 +38,7 @@ function WizardHelper(sClass)
}
this.SetCurrentValue = function (sFieldName, currentValue)
{
- this.m_oData.m_aCurrentValues[this.m_oData.m_oFieldsMap[sFieldName]] = currentValue;
+ this.m_oData.m_oCurrentValues[sFieldName] = currentValue;
}
this.ToJSON = function ()
@@ -57,44 +55,40 @@ function WizardHelper(sClass)
this.ResetQuery = function ()
{
this.m_oData.m_aDefaultValueRequested = [];
- this.m_oData.m_aDefaultValue = [];
+ this.m_oData.m_oDefaultValue = {};
this.m_oData.m_aAllowedValuesRequested = [];
- this.m_oData.m_aAllowedValues = [];
+ this.m_oData.m_oAllowedValues = {};
}
this.UpdateFields = function ()
{
//console.log('** UpdateFields **');
- //console.log(this.m_oData);
- for(i=0; i< this.m_oData.m_aAllowedValuesRequested.length; i++)
+ // Set the full HTML for the input field
+ for(i=0; iGetName();
$sClassLabel = MetaModel::GetName(get_class($oObj));
$oObj->DBDeleteTracked($oMyChange);
- $oP->add("".Dict::Format('UI:Delete:_Name_Class_Deleted')."
\n");
+ $oP->add("".Dict::Format('UI:Delete:_Name_Class_Deleted', $sName, $sClassLabel)."
\n");
}
}
else
@@ -995,6 +995,12 @@ try
$aTransition = $aTransitions[$sStimulus];
$sTargetState = $aTransition['target_state'];
$aTargetStates = MetaModel::EnumStates($sClass);
+ $oP->add_linked_script("../js/json.js");
+ $oP->add_linked_script("../js/forms-json-utils.js");
+ $oP->add_linked_script("../js/wizardhelper.js");
+ $oP->add_linked_script("../js/wizard.utils.js");
+ $oP->add_linked_script("../js/linkswidget.js");
+ $oP->add_linked_script("../js/jquery.blockUI.js");
$oP->add("\n");
@@ -1006,6 +1012,8 @@ try
$oP->add("\n");
$oP->add("\n");
$oP->add("
\n");
$oP->add("\n");
+
+ $iFieldsCount = count($aFieldsMap);
+ $sJsonFieldsMap = json_encode($aFieldsMap);
+
+ $oP->add_script(
+<<add("\n");
$aTargetState = $aTargetStates[$sTargetState];
$aExpectedAttributes = $aTargetState['attribute_list'];
@@ -1101,7 +1123,7 @@ try
$oMyChange->Set("userinfo", $sUserString);
$iChangeId = $oMyChange->DBInsert();
$oObj->DBUpdateTracked($oMyChange);
- $oP->p(Dict::Format('UI:Class_Object_Updated'), get_class($oObj), $oObj->GetName());
+ $oP->p(Dict::Format('UI:Class_Object_Updated', get_class($oObj), $oObj->GetName()));
}
$oObj->DisplayDetails($oP);
}
diff --git a/pages/ajax.render.php b/pages/ajax.render.php
index 351a326a4..a3d1e8e2d 100644
--- a/pages/ajax.render.php
+++ b/pages/ajax.render.php
@@ -91,7 +91,7 @@ switch($operation)
$value = $oObj->Get($sAttCode);
$displayValue = $oObj->GetEditValue($sAttCode);
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
- $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, 'att_'.$sId, '', 0, array('this' => $oObj));
+ $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', 0, array('this' => $oObj));
$oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue);
}