Commit dec6f5bf authored by Andreas Fernandez's avatar Andreas Fernandez Committed by Richard Haeser
Browse files

[BUGFIX] Re-validate changed fields only

Probably since ever, FormEngine validates the whole form when a field
changed or an IRRE item got removed. This patch introduces the following
changes:

- only fields affected by the `change` event get validated
- FormEngineValidation.validate() accepts an optional argument to define
  the container whose fields get validated. If omitted, the whole form
  is validated.

Resolves: #93046
Releases: master, 10.4
Change-Id: I48d2f597c88815ebc0374bb02cf0aed6ac4086fe
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/67094

Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Richard Haeser's avatarRichard Haeser <richard@richardhaeser.com>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Richard Haeser's avatarRichard Haeser <richard@richardhaeser.com>
parent 117eef29
......@@ -409,8 +409,8 @@ class InlineControlContainer {
);
FormEngine.reinitialize();
FormEngine.Validation.initializeInputFields();
FormEngine.Validation.validate();
FormEngineValidation.initializeInputFields();
FormEngineValidation.validate(this.container);
}
});
}
......@@ -581,8 +581,8 @@ class InlineControlContainer {
progress.done();
FormEngine.reinitialize();
FormEngine.Validation.initializeInputFields();
FormEngine.Validation.validate();
FormEngineValidation.initializeInputFields();
FormEngineValidation.validate(this.container);
if (this.hasObjectGroupDefinedUniqueConstraints()) {
const recordContainer = InlineControlContainer.getInlineRecordContainer(objectId);
......@@ -776,7 +776,7 @@ class InlineControlContainer {
new RegularEvent('transitionend', (): void => {
recordContainer.parentElement.removeChild(recordContainer);
FormEngineValidation.validate();
FormEngineValidation.validate(this.container);
}).bindTo(recordContainer);
this.revertUnique(objectUid);
......
......@@ -136,7 +136,7 @@ export abstract class AbstractSortableSelectItems {
FormEngine.updateHiddenFieldValueFromSelect(fieldElement, FormEngine.getFieldElement(relatedFieldName).get(0));
FormEngine.legacyFieldChangedCb();
FormEngineValidation.markFieldAsChanged($(fieldElement));
FormEngineValidation.validate();
FormEngineValidation.validateField(fieldElement);
});
}
}
......@@ -12,7 +12,7 @@
*/
import DocumentService = require('TYPO3/CMS/Core/DocumentService');
import FormEngine = require('TYPO3/CMS/Backend/FormEngine');
import FormEngineValidation = require('TYPO3/CMS/Backend/FormEngineValidation');
import RegularEvent = require('TYPO3/CMS/Core/Event/RegularEvent');
class InputDateTimeElement {
......@@ -30,8 +30,8 @@ class InputDateTimeElement {
private registerEventHandler(element: HTMLInputElement): void {
new RegularEvent('formengine.dp.change', (e: CustomEvent): void => {
FormEngine.Validation.validate();
FormEngine.Validation.markFieldAsChanged(e.target as HTMLInputElement);
FormEngineValidation.validateField(e.target as HTMLInputElement);
FormEngineValidation.markFieldAsChanged(e.target as HTMLInputElement);
document.querySelectorAll('.module-docheader-bar .btn').forEach((btn: HTMLButtonElement): void => {
btn.classList.remove('disabled');
......
......@@ -130,7 +130,7 @@ class FlexFormElement {
$section.remove();
});
FormEngine.Validation.validate();
FormEngine.Validation.validate(this.$el.get(0));
Modal.currentModal.trigger('modal-dismiss');
});
});
......@@ -253,8 +253,9 @@ $(function (): void {
flexFormContainerName: me.data('flexformcontainername'),
}).then(async (response: AjaxResponse): Promise<any> => {
const data = await response.resolve();
me.closest('.t3-form-field-container').find('.t3-flex-container').append(data.html);
$('.t3-flex-container').t3FormEngineFlexFormElement();
const flexContainer = me.closest('.t3-form-field-container').find('.t3-flex-container');
flexContainer.append(data.html);
flexContainer.t3FormEngineFlexFormElement();
if (data.scriptCall && data.scriptCall.length > 0) {
$.each(data.scriptCall, function (index: number, value: string): void {
// eslint-disable-next-line no-eval
......@@ -272,7 +273,7 @@ $(function (): void {
}
FormEngine.reinitialize();
FormEngine.Validation.initializeInputFields();
FormEngine.Validation.validate();
FormEngine.Validation.validate(flexContainer.get(0));
});
});
......
......@@ -32,9 +32,10 @@ declare namespace TYPO3 {
export namespace Backend {
export class FormEngineValidation {
public readonly errorClass: string;
public markFieldAsChanged(field: HTMLInputElement|HTMLTextAreaElement|JQuery): void;
public markFieldAsChanged(field: HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement|JQuery): void;
public initializeInputFields(): void;
public validate(): void;
public validate(section?: Element): void;
public validateField(field: HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement|JQuery, value?: string): void;
}
export namespace FormEngine {
......
......@@ -218,7 +218,7 @@ define(['jquery',
$fieldEl.val(value);
}
if (typeof FormEngine.Validation !== 'undefined' && typeof FormEngine.Validation.validate === 'function') {
FormEngine.Validation.validate();
FormEngine.Validation.validateField($fieldEl);
}
};
......
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","jquery","TYPO3/CMS/Backend/FormEngine","TYPO3/CMS/Backend/FormEngineValidation"],(function(e,t,o,n,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AbstractSortableSelectItems=void 0,o=__importDefault(o);class i{constructor(){this.registerSortableEventHandler=e=>{const t=e.closest(".form-wizards-wrap").querySelector(".form-wizards-items-aside");null!==t&&t.addEventListener("click",t=>{let l;if(null===(l=t.target.closest(".t3js-btn-option")))return void(t.target.matches(".t3js-btn-option")&&(l=t.target));t.preventDefault();const s=l.dataset.fieldname;l.classList.contains("t3js-btn-moveoption-top")?i.moveOptionToTop(e):l.classList.contains("t3js-btn-moveoption-up")?i.moveOptionUp(e):l.classList.contains("t3js-btn-moveoption-down")?i.moveOptionDown(e):l.classList.contains("t3js-btn-moveoption-bottom")?i.moveOptionToBottom(e):l.classList.contains("t3js-btn-removeoption")&&i.removeOption(e,n.getFieldElement(s,"_avail").get(0)),n.updateHiddenFieldValueFromSelect(e,n.getFieldElement(s).get(0)),n.legacyFieldChangedCb(),r.markFieldAsChanged(o.default(e)),r.validate()})}}static moveOptionToTop(e){Array.from(e.querySelectorAll(":checked")).reverse().forEach(t=>{e.insertBefore(t,e.firstElementChild)})}static moveOptionToBottom(e){e.querySelectorAll(":checked").forEach(t=>{e.insertBefore(t,null)})}static moveOptionUp(e){const t=Array.from(e.children),o=Array.from(e.querySelectorAll(":checked"));for(let n of o){if(0===t.indexOf(n)&&null===n.previousElementSibling)break;e.insertBefore(n,n.previousElementSibling)}}static moveOptionDown(e){const t=Array.from(e.children).reverse(),o=Array.from(e.querySelectorAll(":checked")).reverse();for(let n of o){if(0===t.indexOf(n)&&null===n.nextElementSibling)break;e.insertBefore(n,n.nextElementSibling.nextElementSibling)}}static removeOption(e,t){e.querySelectorAll(":checked").forEach(o=>{const n=t.querySelector('option[value="'+o.value+'"]');null!==n&&(n.classList.remove("hidden"),n.disabled=!1),e.removeChild(o)})}}t.AbstractSortableSelectItems=i}));
\ No newline at end of file
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","jquery","TYPO3/CMS/Backend/FormEngine","TYPO3/CMS/Backend/FormEngineValidation"],(function(e,t,o,n,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AbstractSortableSelectItems=void 0,o=__importDefault(o);class i{constructor(){this.registerSortableEventHandler=e=>{const t=e.closest(".form-wizards-wrap").querySelector(".form-wizards-items-aside");null!==t&&t.addEventListener("click",t=>{let l;if(null===(l=t.target.closest(".t3js-btn-option")))return void(t.target.matches(".t3js-btn-option")&&(l=t.target));t.preventDefault();const s=l.dataset.fieldname;l.classList.contains("t3js-btn-moveoption-top")?i.moveOptionToTop(e):l.classList.contains("t3js-btn-moveoption-up")?i.moveOptionUp(e):l.classList.contains("t3js-btn-moveoption-down")?i.moveOptionDown(e):l.classList.contains("t3js-btn-moveoption-bottom")?i.moveOptionToBottom(e):l.classList.contains("t3js-btn-removeoption")&&i.removeOption(e,n.getFieldElement(s,"_avail").get(0)),n.updateHiddenFieldValueFromSelect(e,n.getFieldElement(s).get(0)),n.legacyFieldChangedCb(),r.markFieldAsChanged(o.default(e)),r.validateField(e)})}}static moveOptionToTop(e){Array.from(e.querySelectorAll(":checked")).reverse().forEach(t=>{e.insertBefore(t,e.firstElementChild)})}static moveOptionToBottom(e){e.querySelectorAll(":checked").forEach(t=>{e.insertBefore(t,null)})}static moveOptionUp(e){const t=Array.from(e.children),o=Array.from(e.querySelectorAll(":checked"));for(let n of o){if(0===t.indexOf(n)&&null===n.previousElementSibling)break;e.insertBefore(n,n.previousElementSibling)}}static moveOptionDown(e){const t=Array.from(e.children).reverse(),o=Array.from(e.querySelectorAll(":checked")).reverse();for(let n of o){if(0===t.indexOf(n)&&null===n.nextElementSibling)break;e.insertBefore(n,n.nextElementSibling.nextElementSibling)}}static removeOption(e,t){e.querySelectorAll(":checked").forEach(o=>{const n=t.querySelector('option[value="'+o.value+'"]');null!==n&&(n.classList.remove("hidden"),n.disabled=!1),e.removeChild(o)})}}t.AbstractSortableSelectItems=i}));
\ No newline at end of file
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports","TYPO3/CMS/Core/DocumentService","TYPO3/CMS/Backend/FormEngine","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,n,i,r){"use strict";return class{constructor(t){this.element=null,n.ready().then(()=>{this.element=document.getElementById(t),this.registerEventHandler(this.element),e(["../../DateTimePicker"],e=>{e.initialize(this.element)})})}registerEventHandler(e){new r("formengine.dp.change",e=>{i.Validation.validate(),i.Validation.markFieldAsChanged(e.target),document.querySelectorAll(".module-docheader-bar .btn").forEach(e=>{e.classList.remove("disabled"),e.disabled=!1})}).bindTo(e)}}}));
\ No newline at end of file
define(["require","exports","TYPO3/CMS/Core/DocumentService","TYPO3/CMS/Backend/FormEngineValidation","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,n,r,i){"use strict";return class{constructor(t){this.element=null,n.ready().then(()=>{this.element=document.getElementById(t),this.registerEventHandler(this.element),e(["../../DateTimePicker"],e=>{e.initialize(this.element)})})}registerEventHandler(e){new i("formengine.dp.change",e=>{r.validateField(e.target),r.markFieldAsChanged(e.target),document.querySelectorAll(".module-docheader-bar .btn").forEach(e=>{e.classList.remove("disabled"),e.disabled=!1})}).bindTo(e)}}}));
\ No newline at end of file
......@@ -154,7 +154,7 @@ define(['d3', 'TYPO3/CMS/Backend/SvgTree', 'TYPO3/CMS/Backend/FormEngine'],
// Initialise "value" attribute of input field after load and revalidate form engine fields
this.saveCheckboxes(this.nodes);
if (typeof TYPO3.FormEngine.Validation !== 'undefined' && typeof TYPO3.FormEngine.Validation.validate === 'function') {
TYPO3.FormEngine.Validation.validate();
TYPO3.FormEngine.Validation.validateField(this.settings.input);
}
};
......
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","jquery","Sortable","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Backend/FormEngine","TYPO3/CMS/Backend/Modal"],(function(require,exports,jquery_1,Sortable,AjaxRequest,FormEngine,Modal){"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),jquery_1=__importDefault(jquery_1);class FlexFormElement{constructor(e,t){this.el=e;const o=this;this.el=e,this.$el=jquery_1.default(e);return void 0!==this.$el.data("TYPO3.FormEngine.FlexFormElement")&&this.$el.removeData("TYPO3.FormEngine.FlexFormElement"),this.$el.data("TYPO3.FormEngine.FlexFormElement",this),t||(t=FlexFormElement.defaults),t.allowRestructure=this.$el.data("t3-flex-allow-restructure"),t.flexformId=this.$el.attr("id"),this.opts=jquery_1.default.extend({},FlexFormElement.defaults,t),this.initializeEvents(),this.$el.find(this.opts.sectionSelector).each((function(){o.generateSectionPreview(jquery_1.default(this))})),this}initializeEvents(){return this.$el.prev(this.opts.flexFormToggleAllSectionsSelector).off("click").on("click",()=>{this.$el.find(this.opts.sectionToggleButtonSelector).trigger("click")}),this.opts.allowRestructure&&(this.createSortable(),this.$el.off("click").on("click",this.opts.deleteIconSelector,e=>{e.preventDefault();const t=TYPO3.lang["flexform.section.delete.title"]||"Are you sure?",o=TYPO3.lang["flexform.section.delete.message"]||"Are you sure you want to delete this section?",l=Modal.confirm(t,o);l.on("confirm.button.cancel",()=>{Modal.currentModal.trigger("modal-dismiss")}),l.on("confirm.button.ok",()=>{const t=jquery_1.default(e.target).closest(this.opts.sectionSelector);t.find(this.opts.sectionActionInputFieldSelector).detach().appendTo(t.parent()).val("DELETE"),t.addClass("t3-flex-section--deleted"),t.on("transitionend",()=>{t.remove()}),FormEngine.Validation.validate(),Modal.currentModal.trigger("modal-dismiss")})}),this.$el.on("click",this.opts.sectionToggleButtonSelector,e=>{e.preventDefault();const t=jquery_1.default(e.currentTarget).closest(this.opts.sectionSelector);this.toggleSection(t)}).on("click",this.opts.sectionToggleButtonSelector+" .form-irre-header-control",(function(e){e.stopPropagation()}))),this}createSortable(){new Sortable(this.el,{group:this.el.id,handle:".t3js-sortable-handle",onSort:()=>{this.setActionStatus(),jquery_1.default(document).trigger("flexform:sorting-changed")}})}setActionStatus(){this.$el.find(this.opts.sectionSelector+" "+this.opts.sectionActionInputFieldSelector).each((function(e){this.value=String(e)}))}toggleSection(e){const t=e.find(this.opts.sectionContentSelector);t.toggle(),t.is(":visible")?(e.find(this.opts.sectionToggleIconOpenSelector).show(),e.find(this.opts.sectionToggleIconCloseSelector).hide(),e.find(this.opts.sectionToggleInputFieldSelector).val(0)):(e.find(this.opts.sectionToggleIconOpenSelector).hide(),e.find(this.opts.sectionToggleIconCloseSelector).show(),e.find(this.opts.sectionToggleInputFieldSelector).val(1)),this.generateSectionPreview(e)}generateSectionPreview(e){const t=e.find(this.opts.sectionContentSelector);let o="";t.is(":visible")||t.find("input[type=text], textarea").each((function(){let e=jquery_1.default(jquery_1.default.parseHTML(jquery_1.default(this).val())).text();e.length>50&&(e=e.substring(0,50)+"..."),o+=(o?" / ":"")+e})),0===e.find(this.opts.sectionHeaderPreviewSelector).length&&e.find(this.opts.sectionHeaderSelector).find(".t3js-record-title").parent().append('<span class="'+this.opts.sectionHeaderPreviewSelector.replace(/\./,"")+'"></span>'),e.find(this.opts.sectionHeaderPreviewSelector).text(o)}}FlexFormElement.defaults={deleteIconSelector:".t3js-delete",sectionSelector:".t3js-flex-section",sectionContentSelector:".t3js-flex-section-content",sectionHeaderSelector:".t3js-flex-section-header",sectionHeaderPreviewSelector:".t3js-flex-section-header-preview",sectionActionInputFieldSelector:".t3js-flex-control-action",sectionToggleInputFieldSelector:".t3js-flex-control-toggle",sectionToggleIconOpenSelector:".t3js-flex-control-toggle-icon-open",sectionToggleIconCloseSelector:".t3js-flex-control-toggle-icon-close",sectionToggleButtonSelector:'[data-toggle="formengine-flex"]',flexFormToggleAllSectionsSelector:".t3js-form-field-toggle-flexsection",sectionDeletedClass:"t3js-flex-section-deleted",allowRestructure:!1,flexformId:!1},jquery_1.default.fn.t3FormEngineFlexFormElement=function(e){return this.each((function(){new FlexFormElement(this,e)}))},jquery_1.default((function(){jquery_1.default(".t3-flex-container").t3FormEngineFlexFormElement(),jquery_1.default(document).on("click",".t3js-flex-container-add",(function(e){const me=jquery_1.default(this);e.preventDefault(),new AjaxRequest(TYPO3.settings.ajaxUrls.record_flex_container_add).post({vanillaUid:me.data("vanillauid"),databaseRowUid:me.data("databaserowuid"),command:me.data("command"),tableName:me.data("tablename"),fieldName:me.data("fieldname"),recordTypeValue:me.data("recordtypevalue"),dataStructureIdentifier:me.data("datastructureidentifier"),flexFormSheetName:me.data("flexformsheetname"),flexFormFieldName:me.data("flexformfieldname"),flexFormContainerName:me.data("flexformcontainername")}).then(async response=>{const data=await response.resolve();me.closest(".t3-form-field-container").find(".t3-flex-container").append(data.html),jquery_1.default(".t3-flex-container").t3FormEngineFlexFormElement(),data.scriptCall&&data.scriptCall.length>0&&jquery_1.default.each(data.scriptCall,(function(index,value){eval(value)})),data.stylesheetFiles&&data.stylesheetFiles.length>0&&jquery_1.default.each(data.stylesheetFiles,(function(e,t){let o=document.createElement("link");o.rel="stylesheet",o.type="text/css",o.href=t,document.head.appendChild(o)})),FormEngine.reinitialize(),FormEngine.Validation.initializeInputFields(),FormEngine.Validation.validate()})}))}))}));
\ No newline at end of file
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","jquery","Sortable","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Backend/FormEngine","TYPO3/CMS/Backend/Modal"],(function(require,exports,jquery_1,Sortable,AjaxRequest,FormEngine,Modal){"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),jquery_1=__importDefault(jquery_1);class FlexFormElement{constructor(e,t){this.el=e;const o=this;this.el=e,this.$el=jquery_1.default(e);return void 0!==this.$el.data("TYPO3.FormEngine.FlexFormElement")&&this.$el.removeData("TYPO3.FormEngine.FlexFormElement"),this.$el.data("TYPO3.FormEngine.FlexFormElement",this),t||(t=FlexFormElement.defaults),t.allowRestructure=this.$el.data("t3-flex-allow-restructure"),t.flexformId=this.$el.attr("id"),this.opts=jquery_1.default.extend({},FlexFormElement.defaults,t),this.initializeEvents(),this.$el.find(this.opts.sectionSelector).each((function(){o.generateSectionPreview(jquery_1.default(this))})),this}initializeEvents(){return this.$el.prev(this.opts.flexFormToggleAllSectionsSelector).off("click").on("click",()=>{this.$el.find(this.opts.sectionToggleButtonSelector).trigger("click")}),this.opts.allowRestructure&&(this.createSortable(),this.$el.off("click").on("click",this.opts.deleteIconSelector,e=>{e.preventDefault();const t=TYPO3.lang["flexform.section.delete.title"]||"Are you sure?",o=TYPO3.lang["flexform.section.delete.message"]||"Are you sure you want to delete this section?",n=Modal.confirm(t,o);n.on("confirm.button.cancel",()=>{Modal.currentModal.trigger("modal-dismiss")}),n.on("confirm.button.ok",()=>{const t=jquery_1.default(e.target).closest(this.opts.sectionSelector);t.find(this.opts.sectionActionInputFieldSelector).detach().appendTo(t.parent()).val("DELETE"),t.addClass("t3-flex-section--deleted"),t.on("transitionend",()=>{t.remove()}),FormEngine.Validation.validate(this.$el.get(0)),Modal.currentModal.trigger("modal-dismiss")})}),this.$el.on("click",this.opts.sectionToggleButtonSelector,e=>{e.preventDefault();const t=jquery_1.default(e.currentTarget).closest(this.opts.sectionSelector);this.toggleSection(t)}).on("click",this.opts.sectionToggleButtonSelector+" .form-irre-header-control",(function(e){e.stopPropagation()}))),this}createSortable(){new Sortable(this.el,{group:this.el.id,handle:".t3js-sortable-handle",onSort:()=>{this.setActionStatus(),jquery_1.default(document).trigger("flexform:sorting-changed")}})}setActionStatus(){this.$el.find(this.opts.sectionSelector+" "+this.opts.sectionActionInputFieldSelector).each((function(e){this.value=String(e)}))}toggleSection(e){const t=e.find(this.opts.sectionContentSelector);t.toggle(),t.is(":visible")?(e.find(this.opts.sectionToggleIconOpenSelector).show(),e.find(this.opts.sectionToggleIconCloseSelector).hide(),e.find(this.opts.sectionToggleInputFieldSelector).val(0)):(e.find(this.opts.sectionToggleIconOpenSelector).hide(),e.find(this.opts.sectionToggleIconCloseSelector).show(),e.find(this.opts.sectionToggleInputFieldSelector).val(1)),this.generateSectionPreview(e)}generateSectionPreview(e){const t=e.find(this.opts.sectionContentSelector);let o="";t.is(":visible")||t.find("input[type=text], textarea").each((function(){let e=jquery_1.default(jquery_1.default.parseHTML(jquery_1.default(this).val())).text();e.length>50&&(e=e.substring(0,50)+"..."),o+=(o?" / ":"")+e})),0===e.find(this.opts.sectionHeaderPreviewSelector).length&&e.find(this.opts.sectionHeaderSelector).find(".t3js-record-title").parent().append('<span class="'+this.opts.sectionHeaderPreviewSelector.replace(/\./,"")+'"></span>'),e.find(this.opts.sectionHeaderPreviewSelector).text(o)}}FlexFormElement.defaults={deleteIconSelector:".t3js-delete",sectionSelector:".t3js-flex-section",sectionContentSelector:".t3js-flex-section-content",sectionHeaderSelector:".t3js-flex-section-header",sectionHeaderPreviewSelector:".t3js-flex-section-header-preview",sectionActionInputFieldSelector:".t3js-flex-control-action",sectionToggleInputFieldSelector:".t3js-flex-control-toggle",sectionToggleIconOpenSelector:".t3js-flex-control-toggle-icon-open",sectionToggleIconCloseSelector:".t3js-flex-control-toggle-icon-close",sectionToggleButtonSelector:'[data-toggle="formengine-flex"]',flexFormToggleAllSectionsSelector:".t3js-form-field-toggle-flexsection",sectionDeletedClass:"t3js-flex-section-deleted",allowRestructure:!1,flexformId:!1},jquery_1.default.fn.t3FormEngineFlexFormElement=function(e){return this.each((function(){new FlexFormElement(this,e)}))},jquery_1.default((function(){jquery_1.default(".t3-flex-container").t3FormEngineFlexFormElement(),jquery_1.default(document).on("click",".t3js-flex-container-add",(function(e){const me=jquery_1.default(this);e.preventDefault(),new AjaxRequest(TYPO3.settings.ajaxUrls.record_flex_container_add).post({vanillaUid:me.data("vanillauid"),databaseRowUid:me.data("databaserowuid"),command:me.data("command"),tableName:me.data("tablename"),fieldName:me.data("fieldname"),recordTypeValue:me.data("recordtypevalue"),dataStructureIdentifier:me.data("datastructureidentifier"),flexFormSheetName:me.data("flexformsheetname"),flexFormFieldName:me.data("flexformfieldname"),flexFormContainerName:me.data("flexformcontainername")}).then(async response=>{const data=await response.resolve(),flexContainer=me.closest(".t3-form-field-container").find(".t3-flex-container");flexContainer.append(data.html),flexContainer.t3FormEngineFlexFormElement(),data.scriptCall&&data.scriptCall.length>0&&jquery_1.default.each(data.scriptCall,(function(index,value){eval(value)})),data.stylesheetFiles&&data.stylesheetFiles.length>0&&jquery_1.default.each(data.stylesheetFiles,(function(e,t){let o=document.createElement("link");o.rel="stylesheet",o.type="text/css",o.href=t,document.head.appendChild(o)})),FormEngine.reinitialize(),FormEngine.Validation.initializeInputFields(),FormEngine.Validation.validate(flexContainer.get(0))})}))}))}));
\ No newline at end of file
......@@ -55,8 +55,8 @@ define([
FormEngineValidation.initializeInputFields().promise().done(function() {
// Bind to field changes
$(document).on('change', FormEngineValidation.rulesSelector, function() {
FormEngineValidation.validate();
FormEngineValidation.markFieldAsChanged($(this));
FormEngineValidation.validateField(this);
FormEngineValidation.markFieldAsChanged(this);
});
FormEngineValidation.registerSubmitCallback();
......@@ -129,7 +129,6 @@ define([
$humanReadableField.on('change', function() {
FormEngineValidation.updateInputField($(this).attr('data-formengine-input-name'));
});
$humanReadableField.on('keyup', FormEngineValidation.validate);
// add the attribute so that acceptance tests can know when the field initialization has completed
$humanReadableField.attr('data-formengine-input-initialized', 'true');
......@@ -241,14 +240,22 @@ define([
/**
* Run validation for field
*
* @param {Object} $field
* @param {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement|jQuery} field
* @param {String} [value=$field.val()]
* @returns {String}
*/
FormEngineValidation.validateField = function($field, value) {
value = value || $field.val() || '';
FormEngineValidation.validateField = function(field, value) {
if (field instanceof $) {
field = field.get(0);
}
value = value || field.value || '';
var rules = $field.data('formengine-validation-rules');
if (typeof field.dataset.formengineValidationRules === 'undefined') {
return value;
}
var rules = JSON.parse(field.dataset.formengineValidationRules);
var markParent = false;
var selected = 0;
// keep the original value, validateField should not alter it
......@@ -270,17 +277,17 @@ define([
case 'required':
if (value === '') {
markParent = true;
$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
field.closest(FormEngineValidation.markerSelector).classList.add(FormEngineValidation.errorClass);
}
break;
case 'range':
if (value !== '') {
if (rule.minItems || rule.maxItems) {
$relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]');
$relatedField = $(document).find('[name="' + field.dataset.relatedfieldname + '"]');
if ($relatedField.length) {
selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length;
} else {
selected = $field.val();
selected = field.value;
}
if (typeof rule.minItems !== 'undefined') {
minItems = rule.minItems * 1;
......@@ -311,11 +318,11 @@ define([
break;
case 'select':
if (rule.minItems || rule.maxItems) {
$relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]');
$relatedField = $(document).find('[name="' + field.dataset.relatedfieldname + '"]');
if ($relatedField.length) {
selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length;
} else {
selected = $field.find('option:selected').length;
selected = field.querySelectorAll('option:checked').length;
}
if (typeof rule.minItems !== 'undefined') {
minItems = rule.minItems * 1;
......@@ -333,7 +340,7 @@ define([
break;
case 'group':
if (rule.minItems || rule.maxItems) {
selected = $field.find('option').length;
selected = field.querySelectorAll('option').length;
if (typeof rule.minItems !== 'undefined') {
minItems = rule.minItems * 1;
if (!isNaN(minItems) && selected < minItems) {
......@@ -350,7 +357,7 @@ define([
break;
case 'inline':
if (rule.minItems || rule.maxItems) {
selected = FormEngineValidation.trimExplode(',', $field.val()).length;
selected = FormEngineValidation.trimExplode(',', field.value).length;
if (typeof rule.minItems !== 'undefined') {
minItems = rule.minItems * 1;
if (!isNaN(minItems) && selected < minItems) {
......@@ -370,13 +377,13 @@ define([
break;
}
});
if (markParent) {
// mark field
$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
// check tabs
FormEngineValidation.markParentTab($field);
}
const isValid = !markParent;
field.closest(FormEngineValidation.markerSelector).classList.toggle(FormEngineValidation.errorClass, !isValid);
FormEngineValidation.markParentTab($(field), isValid);
$(document).trigger('t3-formengine-postfieldvalidation');
return returnValue;
};
......@@ -507,13 +514,15 @@ define([
/**
* Validate the complete form
*/
FormEngineValidation.validate = function() {
FormEngineValidation.validate = function(section) {
$(document).find(FormEngineValidation.markerSelector + ', .t3js-tabmenu-item')
.removeClass(FormEngineValidation.errorClass)
.removeClass('has-validation-error');
$(FormEngineValidation.rulesSelector).each(function() {
const sectionElement = section || document;
$(sectionElement).find(FormEngineValidation.rulesSelector).each(function() {
var $field = $(this);
if (!$field.closest('.t3js-flex-section-deleted, .t3js-inline-record-deleted').length) {
var modified = false;
var currentValue = $field.val();
......@@ -538,7 +547,6 @@ define([
}
}
});
$(document).trigger('t3-formengine-postfieldvalidation');
};
/**
......@@ -833,16 +841,21 @@ define([
* Find tab by field and mark it as has-validation-error
*
* @param {Object} $element
* @param {Boolean} isValid
*/
FormEngineValidation.markParentTab = function($element) {
FormEngineValidation.markParentTab = function($element, isValid) {
var $panes = $element.parents('.tab-pane');
$panes.each(function() {
var $pane = $(this);
if (isValid) {
// If incoming element is valid, check for errors in the same sheet
isValid = $pane.find('.has-error').length === 0;
}
var id = $pane.attr('id');
$(document)
.find('a[href="#' + id + '"]')
.closest('.t3js-tabmenu-item')
.addClass('has-validation-error');
.toggleClass('has-validation-error', !isValid);
});
};
......
......@@ -72,7 +72,12 @@ var TBE_EDITOR = {
if (TYPO3.FormEngine && TYPO3.FormEngine.Validation) {
TYPO3.FormEngine.Validation.updateInputField(theField);
TYPO3.FormEngine.Validation.validate();
if ($formField.length > 0) {
TYPO3.FormEngine.Validation.validateField($formField.get(0));
if ($humanReadableField.length > 0 && !$formField.is($humanReadableField)) {
TYPO3.FormEngine.Validation.validateField($humanReadableField.get(0));
}
}
}
},
isFormChanged: function(noAlert) {
......
......@@ -245,7 +245,7 @@ class RichTextElement extends AbstractFormElement
CKEDITOR.instances["' . $fieldId . '"].on(\'change\', function(e) {
var commands = e.sender.commands;
CKEDITOR.instances["' . $fieldId . '"].updateElement();
FormEngine.Validation.validate();
FormEngine.Validation.validateField($(escapedFieldSelector));
FormEngine.Validation.markFieldAsChanged($(escapedFieldSelector));
// remember changes done in maximized state and mark field as changed, once minimized again
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment