mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 23:44:11 +01:00
551 lines
16 KiB
JavaScript
551 lines
16 KiB
JavaScript
// Copyright (C) 2010-2024 Combodo SAS
|
|
//
|
|
// This file is part of iTop.
|
|
//
|
|
// iTop is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// iTop is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
// ID of the (hidden) form field used to store the JSON representation of the
|
|
// object being edited in this page
|
|
var sJsonFieldId = 'json_object';
|
|
|
|
// The memory representation of the object
|
|
var oObj = {};
|
|
|
|
// Mapping between the fields of the form and the attribute of the current object
|
|
// If aFieldsMap[2] contains 'foo' it means that oObj.foo corresponds to the field
|
|
// of Id 'att_2' in the form
|
|
var aFieldsMap = new Array;
|
|
|
|
window.bInSubmit = false; // For handling form cancellation via OnBeforeUnload events
|
|
|
|
// Update the whole object from the form and also update its
|
|
// JSON (serialized) representation in the (hidden) field
|
|
function UpdateObjectFromForm(aFieldsMap, oObj)
|
|
{
|
|
for(i=0; i<aFieldsMap.length; i++)
|
|
{
|
|
var oElement = document.getElementById('att_'+i);
|
|
var sFieldName = aFieldsMap[i];
|
|
oObj['m_aCurrValues'][sFieldName] = oElement.value;
|
|
sJSON = JSON.stringify(oObj);
|
|
var oJSON = document.getElementById(sJsonFieldId);
|
|
oJSON.value = sJSON;
|
|
}
|
|
return oObj;
|
|
}
|
|
|
|
// Update the specified field from the current object
|
|
function UpdateFieldFromObject(idField, aFieldsMap, oObj)
|
|
{
|
|
var oElement = document.getElementById('att_'+idField);
|
|
oElement.value = oObj['m_aCurrValues'][aFieldsMap[idField]];
|
|
}
|
|
// Update all the fields of the Form from the current object
|
|
function UpdateFormFromObject(aFieldsMap, oObj)
|
|
{
|
|
for(i=0; i<aFieldsMap.length; i++)
|
|
{
|
|
UpdateFieldFromForm(i, aFieldsMap, oObj);
|
|
}
|
|
}
|
|
|
|
// This function is meant to be called from the AJAX page
|
|
// It reloads the object (oObj) from the JSON representation
|
|
// and also updates the form field that contains the JSON
|
|
// representation of the object
|
|
function ReloadObjectFromServer(sJSON)
|
|
{
|
|
//console.log('JSON value:', sJSON);
|
|
var oJSON = document.getElementById(sJsonFieldId);
|
|
oJSON.value = sJSON;
|
|
oObj = JSON.parse( '(' + sJSON + ')' );
|
|
return oObj;
|
|
}
|
|
|
|
function GoToStep(iCurrentStep, iNextStep)
|
|
{
|
|
var oCurrentStep = document.getElementById('wizStep'+iCurrentStep);
|
|
if (iNextStep > iCurrentStep)
|
|
{
|
|
// Check the values when moving forward
|
|
if (CheckFields('wizStep'+iCurrentStep, true))
|
|
{
|
|
oCurrentStep.style.display = 'none';
|
|
ActivateStep(iNextStep);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
oCurrentStep.style.display = 'none';
|
|
ActivateStep(iNextStep);
|
|
}
|
|
}
|
|
|
|
function ActivateStep(iTargetStep)
|
|
{
|
|
UpdateObjectFromForm(aFieldsMap, oObj);
|
|
var oNextStep = document.getElementById('wizStep'+(iTargetStep));
|
|
window.location.href='#step'+iTargetStep;
|
|
// If a handler for entering this step exists, call it
|
|
if (typeof(this['OnEnterStep'+iTargetStep]) == 'function')
|
|
{
|
|
eval( 'OnEnterStep'+iTargetStep+'();');
|
|
}
|
|
oNextStep.style.display = '';
|
|
G_iCurrentStep = iTargetStep;
|
|
//$('#wizStep'+(iTargetStep)).block({ message: null });
|
|
}
|
|
|
|
function OnUnload(sTransactionId, sObjClass, iObjKey, sToken)
|
|
{
|
|
if (!window.bInSubmit)
|
|
{
|
|
// If it's not a submit, then it's a "cancel" (Pressing the Cancel button, closing the window, using the back button...)
|
|
var sUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php';
|
|
var oFormData = new FormData();
|
|
oFormData.append('operation', 'on_form_cancel');
|
|
oFormData.append('transaction_id', sTransactionId);
|
|
oFormData.append('obj_class', sObjClass);
|
|
oFormData.append('obj_key', iObjKey);
|
|
oFormData.append('token', sToken);
|
|
navigator.sendBeacon(sUrl, oFormData);
|
|
}
|
|
}
|
|
|
|
function OnSubmit(sFormId)
|
|
{
|
|
if($('#'+sFormId).attr('data-form-state') === 'onsubmit')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$('#'+sFormId).attr('data-form-state','onsubmit');
|
|
|
|
window.bInSubmit=true; // This is a submit, make sure that when the page gets unloaded we don't cancel the action
|
|
|
|
if ($('#'+sFormId).data('force_submit')) {
|
|
return true;
|
|
}
|
|
|
|
var bResult = CheckFields(sFormId, true);
|
|
if (!bResult)
|
|
{
|
|
window.bInSubmit = false; // Submit is/will be canceled
|
|
$('#'+sFormId).attr('data-form-state', 'default');
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
// Store the result of the form validation... there may be several forms per page, beware
|
|
var oFormErrors = { err_form0: 0 };
|
|
|
|
function CheckFields(sFormId, bDisplayAlert)
|
|
{
|
|
// if some fields are in wait, no submit is allowed
|
|
if ($('#'+sFormId+' .blockMsg').length>0)
|
|
{
|
|
CombodoModal.OpenWarningModal(Dict.S('UI:Button:Wait'));
|
|
return false;
|
|
}
|
|
|
|
$('#'+sFormId+' :submit').prop('disable', true);
|
|
$('#'+sFormId+' :button[type=submit]').prop('disable', true);
|
|
firstErrorId = '';
|
|
|
|
// 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()
|
|
{
|
|
// this is synchronous !
|
|
// each field should register this event to launch ValidateField() if needed
|
|
validateEventResult = $(this).trigger('validate', sFormId);
|
|
}
|
|
);
|
|
if(oFormErrors['err_'+sFormId] > 0)
|
|
{
|
|
if (bDisplayAlert)
|
|
{
|
|
activateFirstTabWithError(sFormId);
|
|
CombodoModal.OpenErrorModal(Dict.S('UI:FillAllMandatoryFields'));
|
|
}
|
|
$('#'+sFormId+' :submit').prop('disable', false);
|
|
$('#'+sFormId+' :button[type=submit]').prop('disable', false);
|
|
if (oFormErrors['input_'+sFormId] != null) {
|
|
$('#'+oFormErrors['input_'+sFormId]).focus();
|
|
}
|
|
}
|
|
|
|
return (oFormErrors['err_'+sFormId] == 0); // If no error, submit the form
|
|
}
|
|
|
|
function activateFirstTabWithError(sFormId) {
|
|
var $form = $("#"+sFormId),
|
|
$tabsContainer = $form.find(".ui-widget.ui-widget-content"),
|
|
$tabs = $tabsContainer.find(".ui-tabs-panel");
|
|
|
|
$tabs.each(function (index, element) {
|
|
var $fieldsWithError = $(element).find(".form_validation");
|
|
if ($fieldsWithError.length > 0 && ($tabsContainer.tabs("instance") !== undefined))
|
|
{
|
|
$tabsContainer.tabs("option", "active", index);
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
function ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain)
|
|
{
|
|
if (bValid)
|
|
{
|
|
// Visual feedback - none when it's Ok
|
|
$('#field_'+sFieldId+' .ibo-input-wrapper').removeClass('is-error')
|
|
$('#v_'+sFieldId).html('');
|
|
$('#'+sFieldId+'[data-validate*="dependencies"]').trigger('change.dependencies').removeAttr('data-validate');
|
|
}
|
|
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;
|
|
}
|
|
|
|
if($('#field_'+sFieldId+' .ibo-input-wrapper').attr('data-validation') === 'untouched') {
|
|
$('#field_'+sFieldId+' .ibo-input-wrapper').removeAttr('data-validation');
|
|
}
|
|
else{
|
|
$('#field_'+sFieldId+' .ibo-input-wrapper').addClass('is-error');
|
|
}
|
|
|
|
if ($('#v_'+sFieldId).text() == '')
|
|
{
|
|
$('#v_'+sFieldId).html(sExplain);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* To be launched on each field from normal event (click, change, ...) and 'validate' event for form submission.
|
|
* Calls ReportFieldValidationStatus() to update global vars containing fields status
|
|
* @param sFieldId
|
|
* @param sPattern
|
|
* @param bMandatory
|
|
* @param sFormId
|
|
* @param nullValue
|
|
* @param originalValue
|
|
* @returns {boolean}
|
|
* @constructor
|
|
*/
|
|
function ValidateField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue)
|
|
{
|
|
var bValid = true;
|
|
var sExplain = '';
|
|
if ($('#'+sFieldId).prop('disabled'))
|
|
{
|
|
bValid = true; // disabled fields are not checked
|
|
}
|
|
else
|
|
{
|
|
var currentVal = $('#'+sFieldId).val();
|
|
|
|
if (currentVal == '$$NULL$$') // Convention to indicate a non-valid value since it may have to be passed as text
|
|
{
|
|
bValid = false;
|
|
}
|
|
else if (bMandatory && (currentVal == nullValue))
|
|
{
|
|
bValid = false;
|
|
sExplain = Dict.S('UI:ValueMustBeSet');
|
|
}
|
|
else if ((originalValue != undefined) && (currentVal == originalValue))
|
|
{
|
|
bValid = false;
|
|
if (originalValue == nullValue)
|
|
{
|
|
sExplain = Dict.S('UI:ValueMustBeSet');
|
|
}
|
|
else
|
|
{
|
|
sExplain = Dict.S('UI:ValueMustBeChanged');
|
|
}
|
|
}
|
|
else if (currentVal == nullValue)
|
|
{
|
|
// An empty field is Ok...
|
|
bValid = true;
|
|
}
|
|
else if (sPattern != '')
|
|
{
|
|
re = new RegExp(sPattern);
|
|
//console.log('Validating field: '+sFieldId + ' current value: '+currentVal + ' pattern: '+sPattern );
|
|
bValid = re.test(currentVal);
|
|
sExplain = Dict.S('UI:ValueInvalidFormat');
|
|
}
|
|
}
|
|
ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain);
|
|
//console.log('Form: '+sFormId+' Validating field: '+sFieldId + ' current value: '+currentVal+' pattern: '+sPattern+' result: '+bValid );
|
|
return true; // Do not stop propagation ??
|
|
}
|
|
|
|
function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue)
|
|
{
|
|
let oField = $('#'+sFieldId);
|
|
if (oField.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
let oCKEditor = CombodoCKEditorHandler.GetInstanceSynchronous('#'+sFieldId);
|
|
|
|
var bValid;
|
|
var sExplain = '';
|
|
if (oField.prop('disabled')) {
|
|
bValid = true; // disabled fields are not checked
|
|
} else {
|
|
// If the CKEditor is not yet loaded, we need to wait for it to be ready
|
|
// but as we need this function to be synchronous, we need to call it again when the CKEditor is ready
|
|
if (oCKEditor === undefined){
|
|
CombodoCKEditorHandler.GetInstance('#'+sFieldId).then((oCKEditor) => {
|
|
ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue);
|
|
});
|
|
return;
|
|
}
|
|
let sTextContent;
|
|
let sFormattedContent = oCKEditor.getData();
|
|
|
|
// Get the contents without the tags
|
|
// Check if we have a formatted content that is HTML, otherwise we just have plain text, and we can use it directly
|
|
sTextContent = $(sFormattedContent).length > 0 ? $(sFormattedContent).text() : sFormattedContent;
|
|
|
|
if (sTextContent === '') {
|
|
// No plain text, maybe there is just an image
|
|
let oImg = $(sFormattedContent).find("img");
|
|
if (oImg.length !== 0) {
|
|
sTextContent = 'image';
|
|
}
|
|
}
|
|
|
|
// Get the original value without the tags
|
|
let oFormattedOriginalContents = (originalValue !== undefined) ? $('<div></div>').html(originalValue) : undefined;
|
|
let sTextOriginalContents = (oFormattedOriginalContents !== undefined) ? oFormattedOriginalContents.text() : undefined;
|
|
|
|
if (bMandatory && (sTextContent === nullValue)) {
|
|
bValid = false;
|
|
sExplain = Dict.S('UI:ValueMustBeSet');
|
|
} else if ((sTextOriginalContents !== undefined) && (sTextContent === sTextOriginalContents)) {
|
|
bValid = false;
|
|
if (sTextOriginalContents === nullValue) {
|
|
sExplain = Dict.S('UI:ValueMustBeSet');
|
|
} else {
|
|
// Note: value change check is not working well yet as the HTML to Text conversion is not exactly the same when done from the PHP value or the CKEditor value.
|
|
sExplain = Dict.S('UI:ValueMustBeChanged');
|
|
}
|
|
} else {
|
|
bValid = true;
|
|
}
|
|
|
|
// Put and event to check the field when the content changes, remove the event right after as we'll call this same function again, and we don't want to call the event more than once (especially not ^2 times on each call)
|
|
oCKEditor.model.document.once('change:data', (event) => {
|
|
ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue);
|
|
});
|
|
}
|
|
|
|
ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain);
|
|
return bValid;
|
|
}
|
|
|
|
function ResetPwd(id)
|
|
{
|
|
// Reset the values of the password fields
|
|
$('#'+id).val('*****');
|
|
$('#'+id+'_confirm').val('*****');
|
|
// And reset the flag, to tell it that the password remains unchanged
|
|
$('#'+id+'_changed').val(0);
|
|
// Visual feedback, None when it's Ok
|
|
$('#v_'+id).html('');
|
|
}
|
|
|
|
// Called whenever the content of a one way encrypted password changes
|
|
function PasswordFieldChanged(id)
|
|
{
|
|
// Set the flag, to tell that the password changed
|
|
$('#'+id+'_changed').val(1);
|
|
}
|
|
|
|
// Special validation function for one way encrypted password fields
|
|
function ValidatePasswordField(id, sFormId)
|
|
{
|
|
var bChanged = $('#'+id+'_changed').val();
|
|
if (bChanged)
|
|
{
|
|
if ($('#'+id).val() != $('#'+id+'_confirm').val())
|
|
{
|
|
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] = id;
|
|
}
|
|
// Visual feedback
|
|
$('#v_'+id).html(Dict.S('UI:Component:Input:Password:DoesNotMatch'));
|
|
$('#field_'+id +' .ibo-input-wrapper').addClass('is-error');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
$('#v_'+id).html('');
|
|
$('#field_'+id +' .ibo-input-wrapper').removeClass('is-error');
|
|
return true;
|
|
}
|
|
|
|
//Special validation function for case log fields, taking into account the history
|
|
// to determine if the field is empty or not
|
|
function ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue)
|
|
{
|
|
var bValid = true;
|
|
var sExplain = '';
|
|
var sTextContent;
|
|
|
|
if ($('#'+sFieldId).prop('disabled'))
|
|
{
|
|
bValid = true; // disabled fields are not checked
|
|
}
|
|
else
|
|
{
|
|
// Get the contents (with tags)
|
|
// Note: For CaseLog we can't retrieve the formatted contents from CKEditor (unlike in ValidateCKEditorField() method) because of the place holder.
|
|
sTextContent = $('#' + sFieldId).val();
|
|
var count = $('#'+sFieldId+'_count').val();
|
|
|
|
if (bMandatory && (count == 0) && (sTextContent == nullValue))
|
|
{
|
|
// No previous entry and no content typed
|
|
bValid = false;
|
|
sExplain = Dict.S('UI:ValueMustBeSet');
|
|
}
|
|
else if ((originalValue != undefined) && (sTextContent == originalValue))
|
|
{
|
|
bValid = false;
|
|
sExplain = Dict.S('UI:ValueMustBeChanged');
|
|
}
|
|
}
|
|
ReportFieldValidationStatus(sFieldId, sFormId, bValid, '' /* sExplain */);
|
|
|
|
// We need to check periodically as CKEditor doesn't trigger our events. More details in UIHTMLEditorWidget::Display() @ line 92
|
|
setTimeout(function(){ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue);}, 500);
|
|
}
|
|
|
|
// Validate the inputs depending on the current setting
|
|
function ValidateRedundancySettings(sFieldId, sFormId)
|
|
{
|
|
var bValid = true;
|
|
var sExplain = '';
|
|
|
|
$('#'+sFieldId+' :input[type="radio"]:checked').parent().find(':input[type="string"]').each(function (){
|
|
var sValue = $(this).val().trim();
|
|
if (sValue == '')
|
|
{
|
|
bValid = false;
|
|
sExplain = Dict.S('UI:ValueMustBeSet');
|
|
}
|
|
else
|
|
{
|
|
// There is something... check if it is a number
|
|
re = new RegExp('^[0-9]+$');
|
|
bValid = re.test(sValue);
|
|
if (bValid)
|
|
{
|
|
var iValue = parseInt(sValue , 10);
|
|
if ($(this).hasClass('redundancy-min-up-percent'))
|
|
{
|
|
// A percentage
|
|
if ((iValue < 0) || (iValue > 100))
|
|
{
|
|
bValid = false;
|
|
}
|
|
}
|
|
else if ($(this).hasClass('redundancy-min-up-count'))
|
|
{
|
|
// A count
|
|
if (iValue < 0)
|
|
{
|
|
bValid = false;
|
|
}
|
|
}
|
|
|
|
}
|
|
if (!bValid)
|
|
{
|
|
sExplain = Dict.S('UI:ValueInvalidFormat');
|
|
}
|
|
}
|
|
});
|
|
|
|
ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain);
|
|
return bValid;
|
|
}
|
|
|
|
//Special validation function for custom fields
|
|
function ValidateCustomFields(sFieldId, sFormId)
|
|
{
|
|
var oFieldSet = $('#'+sFieldId+'_console_form').console_form_handler('option', 'field_set');
|
|
bValid = oFieldSet.triggerHandler('validate');
|
|
ReportFieldValidationStatus(sFieldId, sFormId, bValid, '');
|
|
return bValid;
|
|
}
|
|
|
|
// Manage a 'duration' field
|
|
function UpdateDuration(iId)
|
|
{
|
|
var iDays = parseInt($('#'+iId+'_d').val(), 10);
|
|
var iHours = parseInt($('#'+iId+'_h').val(), 10);
|
|
var iMinutes = parseInt($('#'+iId+'_m').val(), 10);
|
|
var iSeconds = parseInt($('#'+iId+'_s').val(), 10);
|
|
|
|
var iDuration = (((iDays*24)+ iHours)*60+ iMinutes)*60 + iSeconds;
|
|
$('#'+iId).val(iDuration);
|
|
$('#'+iId).trigger('change');
|
|
return true;
|
|
}
|
|
|
|
// Called when filling an autocomplete field
|
|
//deprecated in 2.8
|
|
function OnAutoComplete(id, event, data, formatted)
|
|
{
|
|
if (data)
|
|
{
|
|
// A valid match was found: data[0] => label, data[1] => value
|
|
if (data[1] != $('#'+id).val())
|
|
{
|
|
$('#'+id).val(data[1]);
|
|
$('#'+id).trigger('change');
|
|
$('#'+id).trigger('extkeychange');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($('#label_'+id).val() == '')
|
|
{
|
|
$('#'+id).val(''); // Empty value
|
|
}
|
|
else
|
|
{
|
|
$('#'+id).val('$$NULL$$'); // Convention: not a valid value
|
|
}
|
|
$('#'+id).trigger('change');
|
|
}
|
|
}
|