diff --git a/typo3/sysext/backend/Classes/Form/AbstractNode.php b/typo3/sysext/backend/Classes/Form/AbstractNode.php index 04c483048e87ae0fe5fc3208f9580946b2e05fda..98cfd32c02686038e985161aa23e76610c8cb88c 100644 --- a/typo3/sysext/backend/Classes/Form/AbstractNode.php +++ b/typo3/sysext/backend/Classes/Form/AbstractNode.php @@ -153,6 +153,22 @@ abstract class AbstractNode implements NodeInterface { */ protected function getValidationDataAsJsonString(array $config) { $validationRules = array(); + if (!empty($config['eval'])) { + $evalList = GeneralUtility::trimExplode(',', $config['eval'], TRUE); + unset($config['eval']); + foreach ($evalList as $evalType) { + $validationRules[] = array( + 'type' => $evalType, + 'config' => $config + ); + } + } + if (!empty($config['range'])) { + $validationRules[] = array( + 'type' => 'range', + 'config' => $config['range'] + ); + } if (!empty($config['maxitems']) || !empty($config['minitems'])) { $minItems = (isset($config['minitems'])) ? (int)$config['minitems'] : 0; $maxItems = (isset($config['maxitems'])) ? (int)$config['maxitems'] : 10000; diff --git a/typo3/sysext/backend/Classes/Form/Element/InputElement.php b/typo3/sysext/backend/Classes/Form/Element/InputElement.php index bb319c6137f777ee02c01ef24d5205dc0067c031..d589b9e94e87740bf10d19199c9d1b02f1567277 100644 --- a/typo3/sysext/backend/Classes/Form/Element/InputElement.php +++ b/typo3/sysext/backend/Classes/Form/Element/InputElement.php @@ -153,20 +153,25 @@ class InputElement extends AbstractFormElement { } } } - $paramsList = GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ',' . GeneralUtility::quoteJSvalue(implode(',', $evalList)) . ',' . GeneralUtility::quoteJSvalue(trim($config['is_in'])) . ',' . ($config['checkbox'] ? 1 : 0) . ',' . GeneralUtility::quoteJSvalue($config['checkbox']); - $parameterArray['fieldChangeFunc'] = array_merge(array('typo3form.fieldGet' => 'typo3form.fieldGet(' . $paramsList . ');'), $parameterArray['fieldChangeFunc']); - + $paramsList = array( + 'field' => $parameterArray['itemFormElName'], + 'evalList' => implode(',', $evalList), + 'is_in' => trim($config['is_in']), + 'checkbox' => ($config['checkbox'] ? 1 : 0), + 'checkboxValue' => $config['checkbox'], + ); // set classes $classes[] = 'form-control'; $classes[] = 't3js-clearable'; $classes[] = 'hasDefaultValue'; // calculate attributes + $attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString($config); + $attributes['data-formengine-input-params'] = json_encode($paramsList); $attributes['id'] = str_replace('.', '', uniqid('formengine-input-', TRUE)); $attributes['name'] = $parameterArray['itemFormElName'] . '_hr'; $attributes['value'] = ''; $attributes['maxlength'] = $config['max'] ?: 256; - $attributes['onchange'] = implode('', $parameterArray['fieldChangeFunc']); if (!empty($styles)) { $attributes['style'] = implode(' ', $styles); @@ -178,30 +183,26 @@ class InputElement extends AbstractFormElement { $attributes['maxlength'] = (int)$config['max']; } + // This is the EDITABLE form field. + $placeholderValue = $this->getPlaceholderValue($table, $config, $row); + if (!empty($placeholderValue)) { + $attributes['placeholder'] = trim($languageService->sL($placeholderValue)); + } + // Build the attribute string $attributeString = ''; foreach ($attributes as $attributeName => $attributeValue) { $attributeString .= ' ' . $attributeName . '="' . htmlspecialchars($attributeValue) . '"'; } - // This is the EDITABLE form field. - $placeholderValue = $this->getPlaceholderValue($table, $config, $row); - $placeholderAttribute = ''; - if (!empty($placeholderValue)) { - $placeholderAttribute = ' placeholder="' . htmlspecialchars(trim($languageService->sL($placeholderValue))) . '" '; - } - $html = ' <input type="text"' . $attributeString - . $placeholderAttribute . $parameterArray['onFocus'] . ' />'; // This is the ACTUAL form field - values from the EDITABLE field must be transferred to this field which is the one that is written to the database. $html .= '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />'; - $resultArray['extJSCODE'] = 'typo3form.fieldSet(' . $paramsList . ');'; - // Going through all custom evaluations configured for this field // @todo: Similar to above code! foreach ($evalList as $evalData) { diff --git a/typo3/sysext/backend/Classes/Form/FormEngine.php b/typo3/sysext/backend/Classes/Form/FormEngine.php index d5952a826d3942abd99e58930793b974b7363d1f..84ed66148ef91a08f2bae7268e9b9417f8641d4d 100644 --- a/typo3/sysext/backend/Classes/Form/FormEngine.php +++ b/typo3/sysext/backend/Classes/Form/FormEngine.php @@ -211,9 +211,7 @@ class FormEngine { * * @var array */ - protected $requireJsModules = array( - 'TYPO3/CMS/Backend/FormEngineValidation' => NULL - ); + protected $requireJsModules = array(); /** * Constructor function, setting internal variables, loading the styles used. @@ -1227,6 +1225,11 @@ class FormEngine { $this->requireJsModules['TYPO3/CMS/Backend/FormEngine'] = 'function(FormEngine) { FormEngine.setBrowserUrl(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('browser')) . '); }'; + $this->requireJsModules['TYPO3/CMS/Backend/FormEngineValidation'] = 'function(FormEngineValidation) { + FormEngineValidation.setUsMode(' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '1' : '0') . '); + FormEngineValidation.registerReady(); + }'; + foreach ($this->requireJsModules as $moduleName => $callbacks) { if (!is_array($callbacks)) { $callbacks = array($callbacks); @@ -1247,7 +1250,6 @@ class FormEngine { ); $pageRenderer->addInlineSettingArray('Textarea', $textareaSettings); - $this->loadJavascriptLib('sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js'); $this->loadJavascriptLib('sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js'); $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ValueSlider'); // Needed for FormEngine manipulation (date picker) @@ -1290,8 +1292,6 @@ class FormEngine { TBE_EDITOR.labels.refresh_login = ' . GeneralUtility::quoteJSvalue($languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login')) . '; TBE_EDITOR.labels.onChangeAlert = ' . GeneralUtility::quoteJSvalue($languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.onChangeAlert')) . '; TBE_EDITOR.labels.remainingCharacters = ' . GeneralUtility::quoteJSvalue($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.remainingCharacters')) . '; - evalFunc.USmode = ' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '1' : '0') . '; - TBE_EDITOR.customEvalFunctions = {}; '; diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js index 5c975ee46a1503d53c51940ed74776b05d863569..a8cff87cf87023d9e878b54be47c929c31ddf4af 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js @@ -13,138 +13,478 @@ /** * contains all JS functions related to TYPO3 TCEforms/FormEngineValidation + * @internal */ define('TYPO3/CMS/Backend/FormEngineValidation', ['jquery', 'TYPO3/CMS/Backend/FormEngine'], function ($, FormEngine) { /** - * the main FormEngineValidation object + * The main FormEngineValidation object * - * @type {{rulesSelector: string, dateTimeSelector: string, groupFieldHiddenElement: string, relatedFieldSelector: string}} + * @type {{rulesSelector: string, inputSelector: string, markerSelector: string, dateTimeSelector: string, groupFieldHiddenElement: string, relatedFieldSelector: string, errorClass: string, lastYear: number, lastDate: number, lastTime: number, refDate: Date, USmode: number, passwordDummy: string}} */ var FormEngineValidation = { rulesSelector: '[data-formengine-validation-rules]', + inputSelector: '[data-formengine-input-params]', + markerSelector: '.t3js-formengine-validation-marker', dateTimeSelector: '.t3js-datetimepicker', groupFieldHiddenElement: '.t3js-formengine-field-group input[type=hidden]', - relatedFieldSelector: '[data-relatedfieldname]' + relatedFieldSelector: '[data-relatedfieldname]', + errorClass: 'has-error', + lastYear: 0, + lastDate: 0, + lastTime: 0, + refDate: new Date(), + USmode: 0, + passwordDummy: '********' }; /** - * initialize validation for the first time + * Initialize validation for the first time */ FormEngineValidation.initialize = function() { - $(document).find('.has-error').removeClass('has-error'); + $(document).find('.' + FormEngineValidation.errorClass).removeClass(FormEngineValidation.errorClass); - // bind to field changes + // Bind to field changes $(document).on('change', FormEngineValidation.rulesSelector, function() { - // we need to wait, because the update of the select field needs some time + // we need to wait, because the update of the select fields needs some time window.setTimeout(function() { FormEngineValidation.validate(); }, 500); + var $paletteField = $(this).closest('.t3js-formengine-palette-field'); + $paletteField.addClass('has-change'); }); - // bind to datepicker changes + // Bind to datepicker changes $(document).on('dp.change', FormEngineValidation.dateTimeSelector, function(event) { FormEngineValidation.validate(); + var $paletteField = $(this).closest('.t3js-formengine-palette-field'); + $paletteField.addClass('has-change'); }); - if (typeof RTEarea !== 'undefined') { - console.log(RTEarea); + // Initialize input fields + $(document).find(FormEngineValidation.inputSelector).each(function() { + var config = $(this).data('formengine-input-params'); + var fieldName = config.field; + var $field = $('[name="' + fieldName + '"]'); + $field.data('main-field', fieldName); + $field.data('config', config); + FormEngineValidation.initializeInputField(fieldName); + }); + + var today = new Date(); + FormEngineValidation.lastYear = FormEngineValidation.getYear(today); + FormEngineValidation.lastDate = FormEngineValidation.getDate(today); + FormEngineValidation.lastTime = 0; + FormEngineValidation.refDate = today; + FormEngineValidation.USmode = 0; + }; + + /** + * + * @param {number} mode + */ + FormEngineValidation.setUsMode = function(mode) { + FormEngineValidation.USmode = mode; + }; + + /** + * Initialize field by name + * + * @param {string} fieldName + */ + FormEngineValidation.initializeInputField = function(fieldName) { + var $field = $('[name="' + fieldName + '"]'); + var $humanReadableField = $('[name="' + fieldName + '_hr"]'); + var $checkboxField = $('[name="' + fieldName + '_cb"]'); + var $mainField = $('[name="' + $field.data('main-field') + '"]'); + if ($mainField.length === 0) { + $mainField = $field; + } + + var config = $mainField.data('config'); + if (typeof config !== 'undefined') { + var evalList = FormEngineValidation.trimExplode(',', config.evalList); + var value = $field.val(); + + if (config.checkbox && value == config.checkboxValue) { + $field.val(''); + if ($checkboxField.length) { + $checkboxField.attr('checked', ''); + } + } else { + for (var i = 0; i < evalList.length; i++) { + value = FormEngineValidation.formatValue(evalList[i], value, config) + } + if (value.length) { + $humanReadableField.val(value); + } + if ($checkboxField.length) { + $checkboxField.attr('checked', 'checked'); + } + } } + + $humanReadableField.data('main-field', fieldName); + $humanReadableField.data('config', config); + $humanReadableField.on('change', function() { + FormEngineValidation.updateInputField($(this).attr('name')); + }); + + $checkboxField.data('main-field', fieldName); + $checkboxField.data('config', config); + $checkboxField.on('click', function() { + FormEngineValidation.updateInputField($(this).attr('name')); + }); }; /** - * validate the complete form + * Format field value + * + * @param {string} type + * @param {string} value + * @param {array} config + * @returns {string} */ - FormEngineValidation.validate = function() { - $(document).find('.t3js-formengine-validation-marker, .t3js-tabmenu-item') - .removeClass('has-error') - .removeClass('has-validation-error'); + FormEngineValidation.formatValue = function(type, value, config) { + var theString = ''; + switch (type) { + case 'date': + if (!parseInt(value)) { + return ''; + } + theTime = new Date(parseInt(value) * 1000); + if (FormEngineValidation.USmode) { + theString = (theTime.getUTCMonth() + 1) + '-' + theTime.getUTCDate() + '-' + this.getYear(theTime); + } else { + theString = theTime.getUTCDate() + '-' + (theTime.getUTCMonth() + 1) + '-' + this.getYear(theTime); + } + break; + case 'datetime': + if (!parseInt(value)) { + return ''; + } + theString = FormEngineValidation.formatValue('time', value, config) + ' ' + FormEngineValidation.formatValue('date', value, config); + break; + case 'time': + case 'timesec': + if (!parseInt(value)) { + return ''; + } + var theTime = new Date(parseInt(value) * 1000); + var h = theTime.getUTCHours(); + var m = theTime.getUTCMinutes(); + var s = theTime.getUTCSeconds(); + theString = h + ':' + ((m < 10) ? '0' : '') + m + ((type == 'timesec') ? ':' + ((s < 10) ? '0' : '') + s : ''); + break; + case 'password': + theString = (value) ? FormEngineValidation.passwordDummy : ''; + break; + case 'int': + theString = (config.checkbox && value == config.checkboxValue) ? '' : value; + break; + default: + theString = value; + } + return theString; + }; - $(FormEngineValidation.rulesSelector).each(function() { - var $field = $(this); - var $rules = $field.data('formengine-validation-rules'); - var markParent = false; - var selected = 0; - $rules.each(function(rule) { - switch (rule.type) { - case 'required': - if ($field.val() === '') { - markParent = true; - $field.closest('.t3js-formengine-validation-marker').addClass('has-error'); - } - break; - case 'range': + /** + * Update input field after change + * + * @param {string} fieldName + */ + FormEngineValidation.updateInputField = function(fieldName) { + var $field = $('[name="' + fieldName + '"]'); + var $mainField = $('[name="' + $field.data('main-field') + '"]'); + if ($mainField.length === 0) { + $mainField = $field; + } + var $humanReadableField = $('[name="' + $mainField.attr('name') + '_hr"]'); + + var config = $mainField.data('config'); + if (typeof config !== 'undefined') { + var evalList = FormEngineValidation.trimExplode(',', config.evalList); + var origValue = $humanReadableField.val(); + var newValue = $humanReadableField.val(); + + for (var i = 0; i < evalList.length; i++) { + newValue = FormEngineValidation.processValue(evalList[i], newValue, config); + } + var typeConfig = $field.data('formengine-validation-rules'); + var type = ''; + if (typeof typeConfig !== 'undefined' && typeConfig.length) { + type = typeConfig[0].type; + } + switch (type) { + case 'password': + $mainField.val(origValue); + $humanReadableField.val(newValue); + break; + default: + $mainField.val(newValue); + $humanReadableField.val(newValue); + } + } + }; + + /** + * Run validation for field + * + * @param {object} $field + * @param {string} value + * @returns {string} + */ + FormEngineValidation.validateField = function($field, value) { + value = value || FormEngineValidation.ltrim($field.val()); + + var $rules = $field.data('formengine-validation-rules'); + var markParent = false; + var selected = 0; + var returnValue = value; + $rules.each(function(rule) { + switch (rule.type) { + case 'required': + if (value === '') { + markParent = true; + $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass); + } + break; + case 'range': + if (value !== '') { if (rule.minItems || rule.maxItems) { $relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]'); if ($relatedField.length) { selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length; if (selected < rule.minItems || selected > rule.maxItems) { markParent = true; - $field.closest('.t3js-formengine-validation-marker').addClass('has-error'); + $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass); } } else { selected = $field.val(); if (selected < rule.minItems || selected > rule.maxItems) { markParent = true; - $field.closest('.t3js-formengine-validation-marker').addClass('has-error'); + $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass); } } } - break; - case 'select': - if (rule.minItems || rule.maxItems) { - $relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]'); - if ($relatedField.length) { - selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length; - if (selected < rule.minItems || selected > rule.maxItems) { - markParent = true; - $field.closest('.t3js-formengine-validation-marker').addClass('has-error'); - } - } else { - selected = $field.find('option:selected').length; - if (selected < rule.minItems || selected > rule.maxItems) { - markParent = true; - $field.closest('.t3js-formengine-validation-marker').addClass('has-error'); - } - + if (rule.config.lower || rule.config.upper) { + minValue = rule.config.lower || 0; + maxValue = rule.config.upper || Number.MAX_VALUE; + if (value < minValue || value > maxValue) { + markParent = true; + $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass); } } - break; - case 'group': - if (rule.minItems || rule.maxItems) { - selected = $field.find('option').length; + } + break; + case 'select': + if (rule.minItems || rule.maxItems) { + $relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]'); + if ($relatedField.length) { + selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length; if (selected < rule.minItems || selected > rule.maxItems) { markParent = true; - $field.closest('.t3js-formengine-validation-marker').addClass('has-error'); + $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass); } - } - break; - case 'inline': - if (rule.minItems || rule.maxItems) { - selected = FormEngineValidation.trimExplode(',', $field.val()).length; + } else { + selected = $field.find('option:selected').length; if (selected < rule.minItems || selected > rule.maxItems) { markParent = true; - $field.closest('.t3js-formengine-validation-marker').addClass('has-error'); + $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass); } + + } + } + break; + case 'group': + if (rule.minItems || rule.maxItems) { + selected = $field.find('option').length; + if (selected < rule.minItems || selected > rule.maxItems) { + markParent = true; + $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass); + } + } + break; + case 'inline': + if (rule.minItems || rule.maxItems) { + selected = FormEngineValidation.trimExplode(',', $field.val()).length; + if (selected < rule.minItems || selected > rule.maxItems) { + markParent = true; + $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass); + } + } + break; + case 'null': + // unknown type null, we ignore it + break; + default: + } + }); + if (markParent) { + // check tabs + FormEngineValidation.markParentTab($field); + } + return returnValue; + }; + + /** + * Process a value by given command and config + * + * @param {string} command + * @param {string} value + * @param {array} config + * @returns {string} + */ + FormEngineValidation.processValue = function(command, value, config) { + var newString = ''; + var theValue = ''; + var theCmd = ''; + var a = 0; + var returnValue = value; + switch (command) { + case 'alpha': + case 'num': + case 'alphanum': + case 'alphanum_x': + newString = ''; + for (a = 0; a < value.length; a++) { + theChar = value.substr(a, 1); + var special = (theChar === '_' || theChar === '-'); + var alpha = (theChar >= 'a' && theChar <= 'z') || (theChar >= 'A' && theChar <= 'Z'); + var num = (theChar >= '0' && theChar <= '9'); + switch (command) { + case 'alphanum': + special = 0; + break; + case 'alpha': + num = 0; + special = 0; + break; + case 'num': + alpha = 0; + special = 0; + break; + } + if (alpha || num || theChar == ' ' || special) { + newString += theChar; + } + } + if (newString !== value) { + returnValue = newString; + } + break; + case 'is_in': + if (config.is_in) { + theValue = '' + value; + for (a = 0; a < theValue.length; a++) { + theChar = theValue.substr(a, 1); + if (config.is_in.indexOf(theChar) != -1) { + newString += theChar; } - break; - default: - FormEngineValidation.log('unknown validation type: ' + rule.type); - } - }); - if (markParent) { - // check tabs - FormEngineValidation.markParentTab($field); + } + } else { + newString = theValue; + } + returnValue = newString; + break; + case 'nospace': + theValue = '' + value; + newString = ''; + for (a = 0; a < theValue.length; a++) { + var theChar = theValue.substr(a, 1); + if (theChar != ' ') { + newString += theChar; + } + } + returnValue = newString; + break; + case 'md5': + if (value !== '') { + returnValue = MD5(value); + } + break; + case 'upper': + returnValue = value.toUpperCase(); + break; + case 'lower': + returnValue = value.toLowerCase(); + break; + case 'int': + if (value !== '') { + returnValue = FormEngineValidation.parseInt(value); + } + break; + case 'double2': + if (value !== '') { + returnValue = FormEngineValidation.parseDouble(value); + } + break; + case 'trim': + returnValue = FormEngineValidation.ltrim(FormEngineValidation.btrim(value)); + break; + case 'datetime': + if (value !== '') { + theCmd = value.substr(0, 1); + returnValue = FormEngineValidation.parseDateTime(value, theCmd); + } + break; + case 'date': + if (value !== '') { + theCmd = value.substr(0, 1); + returnValue = FormEngineValidation.parseDate(value, theCmd); + } + break; + case 'time': + case 'timesec': + if (value !== '') { + theCmd = value.substr(0, 1); + returnValue = FormEngineValidation.parseTime(value, theCmd, command); + } + break; + case 'year': + if (value !== '') { + theCmd = value.substr(0, 1); + returnValue = FormEngineValidation.parseYear(value, theCmd); + } + break; + case 'null': + // unknown type null, we ignore it + break; + case 'password': + var theString = (value) ? FormEngineValidation.passwordDummy : ''; + returnValue = theString; + break; + default: + if (typeof TBE_EDITOR.customEvalFunctions !== 'undefined' && typeof TBE_EDITOR.customEvalFunctions[command] === 'function') { + returnValue = TBE_EDITOR.customEvalFunctions[command](value); + } + } + return returnValue; + }; + + /** + * Validate the complete form + */ + FormEngineValidation.validate = function() { + $(document).find(FormEngineValidation.markerSelector + ', .t3js-tabmenu-item') + .removeClass(FormEngineValidation.errorClass) + .removeClass('has-validation-error'); + + $(FormEngineValidation.rulesSelector).each(function() { + var $field = $(this); + var newValue = FormEngineValidation.validateField($field); + if (newValue.length) { + $field.val(newValue); } }); }; /** - * helper function to get clean trimmed array from comma list + * Helper function to get clean trimmed array from comma list * - * @param delimiter - * @param string + * @param {string} delimiter + * @param {string} string * @returns {Array} */ FormEngineValidation.trimExplode = function(delimiter, string) { @@ -160,9 +500,400 @@ define('TYPO3/CMS/Backend/FormEngineValidation', ['jquery', 'TYPO3/CMS/Backend/F }; /** - * find tab by field and mark it as has-validation-error + * Parse value to integer + * + * @param {string} value + * @returns {number} + */ + FormEngineValidation.parseInt = function(value) { + var theVal = '' + value; + if (!value) { + return 0; + } + for (var a = 0; a < theVal.length; a++) { + if (theVal.substr(a,1)!='0') { + return parseInt(theVal.substr(a,theVal.length)) || 0; + } + } + return 0; + }; + + /** + * Parse value to double + * + * @param {string} value + * @returns {string} + */ + FormEngineValidation.parseDouble = function(value) { + var theVal = '' + value; + theVal = theVal.replace(/[^0-9,\.-]/g, ''); + var negative = theVal.substring(0, 1) === '-'; + theVal = theVal.replace(/-/g, ''); + theVal = theVal.replace(/,/g, '.'); + if (theVal.indexOf('.') === -1) { + theVal += '.0'; + } + var parts = theVal.split('.'); + var dec = parts.pop(); + theVal = Number(parts.join('') + '.' + dec); + if (negative) { + theVal *= -1; + } + theVal = theVal.toFixed(2); + + return theVal; + }; + + /** * - * @param $element + * @param {string} value + * @returns {string} + */ + FormEngineValidation.ltrim = function(value) { + var theVal = '' + value; + if (!value) { + return ''; + } + for (var a = 0; a < theVal.length; a++) { + if (theVal.substr(a, 1) != ' ') { + return theVal.substr(a, theVal.length); + } + } + return ''; + }; + + /** + * + * @param {string} value + * @returns {string} + */ + FormEngineValidation.btrim = function(value) { + var theVal = '' + value; + if (!value) { + return ''; + } + for (var a = theVal.length; a > 0; a--) { + if (theVal.substr(a-1, 1) != ' ') { + return theVal.substr(0, a); + } + } + return ''; + }; + + /** + * Parse datetime value + * + * @param {string} value + * @param {string} command + * @returns {*} + */ + FormEngineValidation.parseDateTime = function(value, command) { + var today = new Date(); + var lastTime; + var values = FormEngineValidation.split(value); + var add; + switch (command) { + case 'd': + case 't': + case 'n': + lastTime = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(today), 0); + if (values.valPol[1]) { + add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1])); + } + break; + case '+': + case '-': + if (lastTime == 0) { + lastTime = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(today), 0); + } + if (values.valPol[1]) { + add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1])); + } + break; + default: + var index = value.indexOf(' '); + if (index != -1) { + var dateVal = FormEngineValidation.parseDate(value, value.substr(index,value.length)); + // set refDate so that evalFunc_input on time will work with correct DST information + FormEngineValidation.refDate = new Date(dateVal * 1000); + lastTime = dateVal + FormEngineValidation.parseTime(value, value.substr(0,index)); + } else { + // only date, no time + lastTime = FormEngineValidation.parseDate(value, value); + } + } + lastTime += add * 24 * 60 * 60; + return lastTime; + }; + + /** + * Parse date value + * + * @param {string} value + * @param {string} command + * @returns {*} + */ + FormEngineValidation.parseDate = function(value, command) { + var today = new Date(); + var lastDate; + var values = FormEngineValidation.split(value); + var add; + switch (command) { + case 'd': + case 't': + case 'n': + lastDate = FormEngineValidation.getTimestamp(today); + if (values.valPol[1]) { + add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1])); + } + break; + case '+': + case '-': + if (values.valPol[1]) { + add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1])); + } + break; + default: + var index = 4; + if (values.valPol[index]) { + add = FormEngineValidation.pol(values.valPol[index], FormEngineValidation.parseInt(values.values[index])); + } + if (values.values[1] && values.values[1].length > 2) { + if (values.valPol[2]) { + add = FormEngineValidation.pol(values.valPol[2], FormEngineValidation.parseInt(values.values[2])); + } + var temp = values.values[1]; + values = FormEngineValidation.splitSingle(temp); + } + + var year = (values.values[3]) ? FormEngineValidation.parseInt(values.values[3]) : FormEngineValidation.getYear(today); + if ((year >= 0 && year < 38) || (year >= 70 && year < 100) || (year >= 1902 && year < 2038)) { + if (year < 100) { + year = (year < 38) ? year += 2000 : year += 1900; + } + } else { + year = FormEngineValidation.getYear(today); + } + var usMode = FormEngineValidation.USmode ? 1 : 2; + var month = (values.values[usMode]) ? FormEngineValidation.parseInt(values.values[usMode]) : today.getUTCMonth() + 1; + usMode = FormEngineValidation.USmode ? 2 : 1; + var day = (values.values[usMode]) ? FormEngineValidation.parseInt(values.values[usMode]) : today.getUTCDate(); + + var theTime = new Date(parseInt(year), parseInt(month)-1, parseInt(day)); + + // Substract timezone offset from client + lastDate = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(theTime), 0); + } + lastDate += add * 24 * 60 * 60; + return lastDate; + }; + + /** + * Parse time value + * + * @param {string} value + * @param {string} command + * @returns {*} + */ + FormEngineValidation.parseTime = function(value, command, type) { + var today = new Date(); + var lastTime; + var values = FormEngineValidation.split(value); + var add; + switch (command) { + case 'd': + case 't': + case 'n': + lastTime = FormEngineValidation.getTimeSecs(today); + if (values.valPol[1]) { + add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1])); + } + break; + case '+': + case '-': + if (lastTime == 0) { + lastTime = FormEngineValidation.getTimeSecs(today); + } + if (values.valPol[1]) { + add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1])); + } + break; + default: + var index = (type == 'timesec') ? 4 : 3; + if (values.valPol[index]) { + add = FormEngineValidation.pol(values.valPol[index], FormEngineValidation.parseInt(values.values[index])); + } + if (values.values[1] && values.values[1].length > 2) { + if (values.valPol[2]) { + add = FormEngineValidation.pol(values.valPol[2], FormEngineValidation.parseInt(values.values[2])); + } + var temp = values.values[1]; + values = FormEngineValidation.splitSingle(temp); + } + var sec = (values.values[3]) ? FormEngineValidation.parseInt(values.values[3]) : today.getUTCSeconds(); + if (sec > 59) { + sec = 59; + } + var min = (values.values[2]) ? FormEngineValidation.parseInt(values.values[2]) : today.getUTCMinutes(); + if (min > 59) { + min = 59; + } + var hour = (values.values[1]) ? FormEngineValidation.parseInt(values.values[1]) : today.getUTCHours(); + if (hour >= 24) { + hour = 0; + } + + var theTime = new Date(FormEngineValidation.getYear(FormEngineValidation.refDate), FormEngineValidation.refDate.getUTCMonth(), FormEngineValidation.refDate.getUTCDate(), hour, min, (( type == 'timesec' ) ? sec : 0)); + + // Substract timezone offset from client + lastTime = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(theTime), 1); + } + lastTime += add * 60; + if (lastTime < 0) { + lastTime += 24 * 60 * 60; + } + return lastTime; + }; + + /** + * Parse year value + * + * @param {string} value + * @param {string} command + * @returns {*} + */ + FormEngineValidation.parseYear = function(value, command) { + var today = new Date(); + var values = FormEngineValidation.split(value); + var add = 0; + switch (command) { + case 'd': + case 't': + case 'n': + FormEngineValidation.lastYear = FormEngineValidation.getYear(today); + if (values.valPol[1]) { + add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1])); + } + break; + case '+': + case '-': + if (values.valPol[1]) { + add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1])); + } + break; + default: + if (values.valPol[2]) { + add = FormEngineValidation.pol(values.valPol[2], FormEngineValidation.parseInt(values.values[2])); + } + var year = (values.values[1]) ? FormEngineValidation.parseInt(values.values[1]) : FormEngineValidation.getYear(today); + if ((year >= 0 && year < 38) || (year >= 70 && year<100) || (year >= 1902 && year < 2038)) { + if (year < 100) { + year = (year < 38) ? year += 2000 : year += 1900; + } + } else { + year = FormEngineValidation.getYear(today); + } + FormEngineValidation.lastYear = year; + } + FormEngineValidation.lastYear += add; + return FormEngineValidation.lastYear; + }; + + /** + * Get year from date object + * + * @param {Date} timeObj + * @returns {number} + */ + FormEngineValidation.getYear = function(timeObj) { + if (timeObj === null) { + return; + } + return timeObj.getUTCFullYear(); + }; + + /** + * Get date as timestamp from Date object + * + * @param {Date} timeObj + * @returns {number} + */ + FormEngineValidation.getDate = function(timeObj) { + var theTime = new Date(FormEngineValidation.getYear(timeObj), timeObj.getUTCMonth(), timeObj.getUTCDate()); + return FormEngineValidation.getTimestamp(theTime); + }; + + /** + * + * @param {string} foreign + * @param {string} value + * @returns {Object} + */ + FormEngineValidation.pol = function(foreign, value) { + return eval(((foreign == '-') ? '-' : '') + value); + }; + + /** + * Substract timezone offset from client to a timestamp to get UTC-timestamp to be send to server + * + * @param {number} timestamp + * @param {number} timeonly + * @returns {*} + */ + FormEngineValidation.convertClientTimestampToUTC = function(timestamp, timeonly) { + var timeObj = new Date(timestamp*1000); + timeObj.setTime((timestamp - timeObj.getTimezoneOffset()*60)*1000); + if (timeonly) { + // only seconds since midnight + return FormEngineValidation.getTime(timeObj); + } else { + // seconds since the "unix-epoch" + return FormEngineValidation.getTimestamp(timeObj); + } + }; + + /** + * Parse date string or object and return unix timestamp + * + * @param {string} timeObj + * @returns {number} + */ + FormEngineValidation.getTimestamp = function(timeObj) { + return Date.parse(timeObj)/1000; + }; + + /** + * Seconds since midnight + * + * @param timeObj + * @returns {*} + */ + FormEngineValidation.getTime = function(timeObj) { + return timeObj.getUTCHours() * 60 * 60 + timeObj.getUTCMinutes() * 60 + FormEngineValidation.getSecs(timeObj); + }; + + /** + * + * @param timeObj + * @returns {number} + */ + FormEngineValidation.getSecs = function(timeObj) { + return timeObj.getUTCSeconds(); + }; + + /** + * + * @param timeObj + * @returns {number} + */ + FormEngineValidation.getTimeSecs = function(timeObj) { + return timeObj.getHours() * 60 * 60 + timeObj.getMinutes() * 60 + timeObj.getSeconds(); + }; + + /** + * Find tab by field and mark it as has-validation-error + * + * @param {object} $element */ FormEngineValidation.markParentTab = function($element) { var $panes = $element.parents('.tab-pane'); @@ -177,24 +908,98 @@ define('TYPO3/CMS/Backend/FormEngineValidation', ['jquery', 'TYPO3/CMS/Backend/F }; /** - * helper function for console.log message * - * @param msg + * @param value + * @returns {{values: Array, pointer: number}} + */ + FormEngineValidation.splitSingle = function(value) { + var theVal = '' + value; + var result = { + values: [], + pointer: 3 + }; + result.values[1] = theVal.substr(0,2); + result.values[2] = theVal.substr(2,2); + result.values[3] = theVal.substr(4,10); + return result; + }; + + /** + * + * @param theStr1 + * @param delim + * @param index + * @returns {*} */ - FormEngineValidation.log = function(msg) { - if (typeof console !== 'undefined') { - console.log(msg); + FormEngineValidation.splitStr = function(theStr1, delim, index) { + var theStr = '' + theStr1; + var lengthOfDelim = delim.length; + sPos = -lengthOfDelim; + if (index < 1) { + index = 1; } + for (a = 1; a < index; a++) { + sPos = theStr.indexOf(delim, sPos + lengthOfDelim); + if (sPos == -1) { + return null; + } + } + ePos = theStr.indexOf(delim, sPos + lengthOfDelim); + if (ePos == -1) { + ePos = theStr.length; + } + return (theStr.substring(sPos + lengthOfDelim, ePos)); }; /** - * initialize function + * + * @param value + * @returns {{values: Array, valPol: Array, pointer: number, numberMode: number, theVal: string}} */ - FormEngineValidation.initialize(); - // Start first validation after one second, because all fields are initial empty (typo3form.fieldSet) - window.setTimeout(function() { - FormEngineValidation.validate(); - }, 1000); + FormEngineValidation.split = function(value) { + var result = { + values: [], + valPol: [], + pointer: 0, + numberMode: 0, + theVal: '' + }; + value += ' '; + for (var a=0; a < value.length; a++) { + var theChar = value.substr(a, 1); + if (theChar < '0' || theChar > '9') { + if (result.numberMode) { + result.pointer++; + result.values[result.pointer] = result.theVal; + result.theVal = ''; + result.numberMode = 0; + } + if (theChar == '+' || theChar == '-') { + result.valPol[result.pointer + 1] = theChar; + } + } else { + result.theVal += theChar; + result.numberMode = 1; + } + } + return result; + }; + + FormEngineValidation.registerReady = function() { + $(document).ready(function() { + FormEngineValidation.initialize(); + // Start first validation after one second, because all fields are initial empty (typo3form.fieldSet) + window.setTimeout(function() { + FormEngineValidation.validate(); + }, 1000); + }); + }; + + /** + * Initialize function + */ + FormEngine.Validation = FormEngineValidation; + return FormEngine.Validation; }); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js index ce40145dbc415cdc36cddf24cb522bb13bc79781..d62fe626af7ec9120baf269aff46dcf1e19ed541 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js @@ -38,9 +38,9 @@ function evalFunc() { this.ltrim = evalFunc_ltrim; this.btrim = evalFunc_btrim; var today = new Date(); - this.lastYear = this.getYear(today); - this.lastDate = this.getDate(today); - this.lastTime = 0; + this.lastYear = this.getYear(today); + this.lastDate = this.getDate(today); + this.lastTime = 0; this.refDate = today; this.isInString = ''; this.USmode = 0; @@ -101,7 +101,7 @@ function evalFunc_caseSwitch(type,inVal) { newString+=theChar; } } - break; + break; case "is_in": if (this.isInString) { for (var a=0;a<theVal.length;a++) { @@ -111,16 +111,16 @@ function evalFunc_caseSwitch(type,inVal) { } } } else {newString = theVal;} - break; + break; case "nospace": newString = this.noSpace(theVal); - break; + break; case "upper": newString = theVal.toUpperCase(); - break; + break; case "lower": newString = theVal.toLowerCase(); - break; + break; default: return inVal; } @@ -164,7 +164,7 @@ function evalFunc_parseDouble(value) { var dec = parts.pop(); theVal = Number(parts.join("") + "." + dec); if (negative) { - theVal *= -1; + theVal *= -1; } theVal = theVal.toFixed(2); @@ -272,31 +272,31 @@ function evalFunc_input(type,inVal) { if (values.valPol[1]) { add = this.pol(values.valPol[1],this.parseInt(values.values[1])); } - break; + break; case "+": case "-": if (this.lastTime == 0) { this.lastTime = this.convertClientTimestampToUTC(this.getTimestamp(today), 0); } if (values.valPol[1]) { - add = this.pol(values.valPol[1],this.parseInt(values.values[1])); + add = this.pol(values.valPol[1], this.parseInt(values.values[1])); } - break; + break; default: var index = value.indexOf(' '); if (index!=-1) { - var dateVal = this.input("date",value.substr(index,value.length)); - // set refDate so that evalFunc_input on time will work with correct DST information + var dateVal = this.input("date", value.substr(index,value.length)); + // set refDate so that evalFunc_input on time will work with correct DST information this.refDate = new Date(dateVal*1000); - this.lastTime = dateVal + this.input("time",value.substr(0,index)); + this.lastTime = dateVal + this.input("time", value.substr(0,index)); } else { - // only date, no time + // only date, no time this.lastTime = this.input("date", value); } } this.lastTime+=add*24*60*60; return this.lastTime; - break; + break; case "year": switch (theCmd) { case "d": @@ -306,13 +306,13 @@ function evalFunc_input(type,inVal) { if (values.valPol[1]) { add = this.pol(values.valPol[1],this.parseInt(values.values[1])); } - break; + break; case "+": case "-": if (values.valPol[1]) { add = this.pol(values.valPol[1],this.parseInt(values.values[1])); } - break; + break; default: if (values.valPol[2]) { add = this.pol(values.valPol[2],this.parseInt(values.values[2])); @@ -329,7 +329,7 @@ function evalFunc_input(type,inVal) { } this.lastYear+=add; return this.lastYear; - break; + break; case "date": switch (theCmd) { case "d": @@ -339,13 +339,13 @@ function evalFunc_input(type,inVal) { if (values.valPol[1]) { add = this.pol(values.valPol[1],this.parseInt(values.values[1])); } - break; + break; case "+": case "-": if (values.valPol[1]) { add = this.pol(values.valPol[1],this.parseInt(values.values[1])); } - break; + break; default: var index = 4; if (values.valPol[index]) { @@ -372,12 +372,12 @@ function evalFunc_input(type,inVal) { var theTime = new Date(parseInt(year), parseInt(month)-1, parseInt(day)); - // Substract timezone offset from client + // Substract timezone offset from client this.lastDate = this.convertClientTimestampToUTC(this.getTimestamp(theTime), 0); } this.lastDate+=add*24*60*60; return this.lastDate; - break; + break; case "time": case "timesec": switch (theCmd) { @@ -388,7 +388,7 @@ function evalFunc_input(type,inVal) { if (values.valPol[1]) { add = this.pol(values.valPol[1],this.parseInt(values.values[1])); } - break; + break; case "+": case "-": if (this.lastTime == 0) { @@ -397,7 +397,7 @@ function evalFunc_input(type,inVal) { if (values.valPol[1]) { add = this.pol(values.valPol[1],this.parseInt(values.values[1])); } - break; + break; default: var index = (type=="timesec")?4:3; if (values.valPol[index]) { @@ -419,13 +419,13 @@ function evalFunc_input(type,inVal) { var theTime = new Date(this.getYear(this.refDate), this.refDate.getUTCMonth(), this.refDate.getUTCDate(), hour, min, ((type=="timesec")?sec:0)); - // Substract timezone offset from client + // Substract timezone offset from client this.lastTime = this.convertClientTimestampToUTC(this.getTimestamp(theTime), 1); } this.lastTime+=add*60; if (this.lastTime<0) {this.lastTime+=24*60*60;} return this.lastTime; - break; + break; default: return value; } @@ -441,11 +441,11 @@ function evalFunc_output(type,value,FObj) { } else { theString = theTime.getUTCDate()+'-'+(theTime.getUTCMonth()+1)+'-'+this.getYear(theTime); } - break; + break; case "datetime": if (!parseInt(value)) {return '';} theString = this.output("time",value)+' '+this.output("date",value); - break; + break; case "time": case "timesec": if (!parseInt(value)) {return '';} @@ -454,13 +454,13 @@ function evalFunc_output(type,value,FObj) { var m = theTime.getUTCMinutes(); var s = theTime.getUTCSeconds(); theString = h+':'+((m<10)?'0':'')+m + ((type=="timesec")?':'+((s<10)?'0':'')+s:''); - break; + break; case "password": theString = (value) ? TS.passwordDummy : ""; - break; + break; case "int": theString = (FObj.checkbox && value==FObj.checkboxValue)?'':value; - break; + break; default: theString = value; } @@ -512,10 +512,10 @@ function evalFunc_convertClientTimestampToUTC(timestamp, timeonly) { var timeObj = new Date(timestamp*1000); timeObj.setTime((timestamp - timeObj.getTimezoneOffset()*60)*1000); if (timeonly) { - // only seconds since midnight + // only seconds since midnight return this.getTime(timeObj); } else { - // seconds since the "unix-epoch" + // seconds since the "unix-epoch" return this.getTimestamp(timeObj); } } diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js index ce56cd7fde45a5554e951f4f1c1ca87318e5121b..dbc1c7e1b02b35f09eff5cc118cbb93241f8703f 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js @@ -150,6 +150,8 @@ var TBE_EDITOR = { // modify the "field has changed" info by adding a class to the container element (based on palette or main field) var $formField = TYPO3.jQuery('[name="' + el + '"]'); + var $humanReadableField = TYPO3.jQuery('[name="' + el + '_hr"]'); + $humanReadableField.triggerHandler('change'); var $paletteField = $formField.closest('.t3js-formengine-palette-field'); $paletteField.addClass('has-change'); @@ -347,8 +349,8 @@ function typoSetup () { this.passwordDummy = '********'; this.decimalSign = '.'; } +// @todo: maybe obsolete, need a deeper check var TS = new typoSetup(); -var evalFunc = new evalFunc(); // backwards compatibility for extensions var TBE_EDITOR_setHiddenContent = TBE_EDITOR.setHiddenContent; @@ -440,6 +442,15 @@ var typo3form = { } }; +// @TODO: This function is a copy from jsfunc.evalfield.js +// @TODO: Remove it later, after TBE_EDITOR is not used anymore. +function evalFunc_dummy (evallist,is_in,checkbox,checkboxValue) { + this.evallist = evallist; + this.is_in = is_in; + this.checkboxValue = checkboxValue; + this.checkbox = checkbox; +} + // backwards compatibility for extensions var typo3FormFieldSet = typo3form.fieldSet; var typo3FormFieldGet = typo3form.fieldGet; diff --git a/typo3/sysext/core/Documentation/Changelog/master/Important-67852-RemoveJsfuncevalfieldjsFromFormEngine.rst b/typo3/sysext/core/Documentation/Changelog/master/Important-67852-RemoveJsfuncevalfieldjsFromFormEngine.rst new file mode 100644 index 0000000000000000000000000000000000000000..b2dab2054fdb54c255017a970e79e014857a96ad --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Important-67852-RemoveJsfuncevalfieldjsFromFormEngine.rst @@ -0,0 +1,12 @@ +============================================================== +Important: #67852 - Remove jsfunc.evalfield.js from FormEngine +============================================================== + +Description +=========== + +After 12 years, the usage of ``jsfunc.evalfield.js`` has been removed from ``FormEngine``. +The JavaScript has been moved into FormEngineValidation AMD module. +Processors and Validator has been split up in two different function. + +Including the ``jsfunc.evalfield.js`` still works, but will be removed shortly. \ No newline at end of file diff --git a/typo3/sysext/frontend/Configuration/TCA/tt_content.php b/typo3/sysext/frontend/Configuration/TCA/tt_content.php index ba85fe66974a58015d2bde914fe37f53361a0432..f959ab8d85b0e315fa647284a5a1b8d9da336cf3 100644 --- a/typo3/sysext/frontend/Configuration/TCA/tt_content.php +++ b/typo3/sysext/frontend/Configuration/TCA/tt_content.php @@ -471,8 +471,8 @@ return array( 'max' => '4', 'eval' => 'int', 'range' => array( - 'upper' => '999', - 'lower' => '25' + 'upper' => 1999, + 'lower' => 0, ), 'default' => 0 ) @@ -486,8 +486,8 @@ return array( 'max' => '4', 'eval' => 'int', 'range' => array( - 'upper' => '700', - 'lower' => '25' + 'upper' => 1999, + 'lower' => 0, ), 'default' => 0 ) diff --git a/typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php b/typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php index 468fb6b4dcf02bb244637f4e298725a8fb6fbef9..150dfb0b9c1205c9583869f50b42aa466f45257c 100644 --- a/typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php +++ b/typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php @@ -618,7 +618,9 @@ class ElementBrowser { } else { field.value = input; } - field.onchange(); + if (typeof field.onchange === \'function\') { + field.onchange(); + } ' . $update . ' } }