From e7cb52e9c5d3fe1603cc9f702d0058069d958171 Mon Sep 17 00:00:00 2001 From: Lukasz Ostafin Date: Fri, 18 Feb 2022 13:50:49 +0100 Subject: [PATCH 1/5] Validation content type --- .../js/scripts/admin.contenttype.edit.js | 95 ++++++++++++++++--- .../admin/content_type/create.html.twig | 7 +- .../themes/admin/content_type/edit.html.twig | 7 +- .../content_type/field_definition.html.twig | 2 +- .../part/field_definition_form.html.twig | 2 +- .../ContentType/ContentTypeUpdateType.php | 1 - 6 files changed, 94 insertions(+), 20 deletions(-) diff --git a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js index 37d1dd1018..7225c13531 100644 --- a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js +++ b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js @@ -4,13 +4,21 @@ let sourceContainer = null; let currentDraggedItem = null; let draggedItemPosition = null; + let isEditFormValid = false; const draggableGroups = []; const token = doc.querySelector('meta[name="CSRF-Token"]').content; const siteaccess = doc.querySelector('meta[name="SiteAccess"]').content; + const editForm = doc.querySelector('.ibexa-content-type-edit-form'); + const inputsToValidate = editForm.querySelectorAll('.ibexa-input[required]:not([disabled]):not([hidden])'); const sectionsNode = doc.querySelector('.ibexa-content-type-edit__sections'); const filterFieldInput = doc.querySelector('.ibexa-available-field-types__sidebar-filter'); const popupMenuElement = sectionsNode.querySelector('.ibexa-popup-menu'); const addGroupTriggerBtn = sectionsNode.querySelector('.ibexa-content-type-edit__add-field-definitions-group-btn'); + const noFieldsAddedError = Translator.trans( + /*@Desc("You have to add at least one field definition")*/ 'content_type.edit.error.no_added_fields_definition', + {}, + 'content_type', + ); const endpoints = { add: { actionName: 'add_field_definition', @@ -80,7 +88,9 @@ const fieldGroupInput = fieldNode.querySelector('.ibexa-input--field-group'); const removeFieldsBtn = fieldNode.querySelectorAll('.ibexa-collapse__extra-action-button--remove-field-definitions'); + const inputsToValidate = fieldNode.querySelectorAll('.ibexa-input[required]:not([disabled]):not([hidden])'); + inputsToValidate.forEach(attachValidateEvents); removeDragPlaceholders(); fieldGroupInput.value = fieldsGroupId; targetContainer.insertBefore(fieldNode, targetPlace); @@ -132,7 +142,6 @@ }); }; const afterChangeGroup = () => { - const submitBtn = doc.querySelector('.ibexa-content-type-edit__publish-content-type'); const fieldsDefinitionCount = doc.querySelectorAll('.ibexa-collapse--field-definition').length; const groups = doc.querySelectorAll('.ibexa-collapse--field-definitions-group'); const itemsAction = doc.querySelectorAll('.ibexa-content-type-edit__add-field-definitions-group .ibexa-popup-menu__item-content'); @@ -158,8 +167,6 @@ doc.querySelectorAll('.ibexa-collapse--field-definition').forEach((fieldDefinition, index) => { fieldDefinition.querySelector('.ibexa-input--position').value = index; }); - - submitBtn.toggleAttribute('disabled', !fieldsDefinitionCount); }; const addField = () => { if (!sourceContainer.classList.contains('ibexa-available-field-types__list')) { @@ -253,7 +260,60 @@ }) .catch(ibexa.helpers.notification.showErrorNotification); }; + const validateInput = (input) => { + const isInputValid = !!input.value; + const field = input.closest('.form-group'); + const labelNode = field.querySelector('.ibexa-label'); + const errorNode = field.querySelector('.ibexa-form-error'); + + input.classList.toggle('is-invalid', !isInputValid); + + if (errorNode) { + const fieldName = labelNode.innerHTML; + const errorMessage = ibexa.errors.emptyField.replace('{fieldName}', fieldName); + + errorNode.innerHTML = !isInputValid ? errorMessage : ''; + } + + if (isEditFormValid && !isInputValid) { + isEditFormValid = false; + } + }; + const validateForm = () => { + const inputsToValidate = editForm.querySelectorAll('.ibexa-input[required]:not([disabled]):not([hidden])'); + const fieldDefinitionsStatuses = {}; + + isEditFormValid = true; + + inputsToValidate.forEach((input) => { + const fieldDefinition = input.closest('.ibexa-collapse--field-definition'); + + if (fieldDefinition) { + const { fieldDefinitionIdentifier } = fieldDefinition.dataset; + const isInputValid = !!input.value; + + if (!fieldDefinitionsStatuses[fieldDefinitionIdentifier]) { + fieldDefinitionsStatuses[fieldDefinitionIdentifier] = []; + } + + fieldDefinitionsStatuses[fieldDefinitionIdentifier].push(isInputValid); + } + + validateInput(input); + }); + + for (const [fieldDefinitionIdentifier, inputsStatus] of Object.entries(fieldDefinitionsStatuses)) { + const isFieldDefinitionValid = inputsStatus.every((status) => status); + const fieldDefinitionNode = doc.querySelector(`[data-field-definition-identifier="${fieldDefinitionIdentifier}"]`); + fieldDefinitionNode.classList.toggle('is-invalid', !isFieldDefinitionValid); + } + }; + const attachValidateEvents = (input) => { + input.addEventListener('change', () => validateForm(input), false); + input.addEventListener('blur', () => validateForm(input), false); + input.addEventListener('input', () => validateForm(input), false); + }; class FieldDefinitionDraggable extends ibexa.core.Draggable { onDrop(event) { targetContainer = event.currentTarget; @@ -368,20 +428,25 @@ draggableGroups.push(draggable); }); - doc.querySelector('.ibexa-btn--save-content-type').addEventListener( - 'click', - () => { - if (doc.querySelectorAll('.ibexa-collapse--field-definition').length) { - return; + inputsToValidate.forEach(attachValidateEvents); + + editForm.addEventListener( + 'submit', + (event) => { + const fieldDefinitionsCount = doc.querySelectorAll('.ibexa-collapse--field-definition').length; + + validateForm(); + + if (!fieldDefinitionsCount) { + isEditFormValid = false; + ibexa.helpers.notification.showErrorNotification(noFieldsAddedError); } - ibexa.helpers.notification.showErrorNotification( - Translator.trans( - /*@Desc("You have to add at least one field definition")*/ 'content_type.edit.error.no_added_fields_definition', - {}, - 'content_type', - ), - ); + if (!isEditFormValid) { + event.preventDefault(); + + return; + } }, false, ); diff --git a/src/bundle/Resources/views/themes/admin/content_type/create.html.twig b/src/bundle/Resources/views/themes/admin/content_type/create.html.twig index 499aaf87c5..14ebe18751 100644 --- a/src/bundle/Resources/views/themes/admin/content_type/create.html.twig +++ b/src/bundle/Resources/views/themes/admin/content_type/create.html.twig @@ -18,7 +18,12 @@ {% endblock %} {% block form %} - {{ form_start(form, { attr: { class: 'ibexa-form' } } ) }} + {{ form_start(form, { + attr: { + class: 'ibexa-content-type-edit-form ibexa-form', + novalidate: 'novalidate', + } + }) }}
add('removeDraft', SubmitType::class, ['label' => /** @Desc("Cancel") */ 'content_type.remove_draft', 'validation_groups' => false]) ->add('publishContentType', SubmitType::class, [ 'label' => /** @Desc("OK") */ 'content_type.publish', - 'disabled' => !$hasFieldDefinition, ]) ; } From e540e7f3efdafd4b0a96986050828f28f7e6a2a0 Mon Sep 17 00:00:00 2001 From: Lukasz Ostafin Date: Wed, 23 Feb 2022 12:59:52 +0100 Subject: [PATCH 2/5] After CR --- .../js/scripts/admin.contenttype.edit.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js index 7225c13531..76f21aaf29 100644 --- a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js +++ b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js @@ -261,21 +261,21 @@ .catch(ibexa.helpers.notification.showErrorNotification); }; const validateInput = (input) => { - const isInputValid = !!input.value; + const isInputEmpty = !input.value; const field = input.closest('.form-group'); const labelNode = field.querySelector('.ibexa-label'); const errorNode = field.querySelector('.ibexa-form-error'); - input.classList.toggle('is-invalid', !isInputValid); + input.classList.toggle('is-invalid', isInputEmpty); if (errorNode) { const fieldName = labelNode.innerHTML; const errorMessage = ibexa.errors.emptyField.replace('{fieldName}', fieldName); - errorNode.innerHTML = !isInputValid ? errorMessage : ''; + errorNode.innerHTML = isInputEmpty ? errorMessage : ''; } - if (isEditFormValid && !isInputValid) { + if (isEditFormValid && isInputEmpty) { isEditFormValid = false; } }; @@ -290,24 +290,24 @@ if (fieldDefinition) { const { fieldDefinitionIdentifier } = fieldDefinition.dataset; - const isInputValid = !!input.value; + const isInputEmpty = !input.value; if (!fieldDefinitionsStatuses[fieldDefinitionIdentifier]) { fieldDefinitionsStatuses[fieldDefinitionIdentifier] = []; } - fieldDefinitionsStatuses[fieldDefinitionIdentifier].push(isInputValid); + fieldDefinitionsStatuses[fieldDefinitionIdentifier].push(isInputEmpty); } validateInput(input); }); - for (const [fieldDefinitionIdentifier, inputsStatus] of Object.entries(fieldDefinitionsStatuses)) { - const isFieldDefinitionValid = inputsStatus.every((status) => status); + Object.entries(fieldDefinitionsStatuses).forEach(([fieldDefinitionIdentifier, inputsStatus]) => { + const isFieldDefinitionValid = inputsStatus.every((hasError) => !hasError); const fieldDefinitionNode = doc.querySelector(`[data-field-definition-identifier="${fieldDefinitionIdentifier}"]`); fieldDefinitionNode.classList.toggle('is-invalid', !isFieldDefinitionValid); - } + }) }; const attachValidateEvents = (input) => { input.addEventListener('change', () => validateForm(input), false); From 4dc78f6010885e7464e33d1084569377cde15a1b Mon Sep 17 00:00:00 2001 From: Lukasz Ostafin Date: Wed, 23 Feb 2022 13:02:49 +0100 Subject: [PATCH 3/5] After CR --- .../Resources/public/js/scripts/admin.contenttype.edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js index 76f21aaf29..7c1e01d850 100644 --- a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js +++ b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js @@ -307,7 +307,7 @@ const fieldDefinitionNode = doc.querySelector(`[data-field-definition-identifier="${fieldDefinitionIdentifier}"]`); fieldDefinitionNode.classList.toggle('is-invalid', !isFieldDefinitionValid); - }) + }); }; const attachValidateEvents = (input) => { input.addEventListener('change', () => validateForm(input), false); From a57b21bbcb6299d353eebc92b35e63afb191c28b Mon Sep 17 00:00:00 2001 From: Lukasz Ostafin Date: Wed, 23 Feb 2022 13:25:19 +0100 Subject: [PATCH 4/5] Added variable for inputs to validate selector --- .../Resources/public/js/scripts/admin.contenttype.edit.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js index 7c1e01d850..68f8ce62f3 100644 --- a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js +++ b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js @@ -1,5 +1,6 @@ (function(global, doc, ibexa, Routing, Translator) { const TIMEOUT_REMOVE_PLACEHOLDERS = 1500; + const SELECTOR_INPUTS_TO_VALIDATE = '.ibexa-input[required]:not([disabled]):not([hidden])'; let targetContainer = null; let sourceContainer = null; let currentDraggedItem = null; @@ -9,7 +10,7 @@ const token = doc.querySelector('meta[name="CSRF-Token"]').content; const siteaccess = doc.querySelector('meta[name="SiteAccess"]').content; const editForm = doc.querySelector('.ibexa-content-type-edit-form'); - const inputsToValidate = editForm.querySelectorAll('.ibexa-input[required]:not([disabled]):not([hidden])'); + const inputsToValidate = editForm.querySelectorAll(SELECTOR_INPUTS_TO_VALIDATE); const sectionsNode = doc.querySelector('.ibexa-content-type-edit__sections'); const filterFieldInput = doc.querySelector('.ibexa-available-field-types__sidebar-filter'); const popupMenuElement = sectionsNode.querySelector('.ibexa-popup-menu'); @@ -88,7 +89,7 @@ const fieldGroupInput = fieldNode.querySelector('.ibexa-input--field-group'); const removeFieldsBtn = fieldNode.querySelectorAll('.ibexa-collapse__extra-action-button--remove-field-definitions'); - const inputsToValidate = fieldNode.querySelectorAll('.ibexa-input[required]:not([disabled]):not([hidden])'); + const inputsToValidate = fieldNode.querySelectorAll(SELECTOR_INPUTS_TO_VALIDATE); inputsToValidate.forEach(attachValidateEvents); removeDragPlaceholders(); @@ -280,7 +281,7 @@ } }; const validateForm = () => { - const inputsToValidate = editForm.querySelectorAll('.ibexa-input[required]:not([disabled]):not([hidden])'); + const inputsToValidate = editForm.querySelectorAll(SELECTOR_INPUTS_TO_VALIDATE); const fieldDefinitionsStatuses = {}; isEditFormValid = true; From ed0628d768d0dd494a3df3be832a1f4f42724e99 Mon Sep 17 00:00:00 2001 From: Lukasz Ostafin Date: Thu, 24 Feb 2022 13:37:57 +0100 Subject: [PATCH 5/5] After CR --- .../js/scripts/admin.contenttype.edit.js | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js index 68f8ce62f3..4f9e104cce 100644 --- a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js +++ b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js @@ -6,11 +6,11 @@ let currentDraggedItem = null; let draggedItemPosition = null; let isEditFormValid = false; + const editForm = doc.querySelector('.ibexa-content-type-edit-form'); + let inputsToValidate = editForm.querySelectorAll(SELECTOR_INPUTS_TO_VALIDATE); const draggableGroups = []; const token = doc.querySelector('meta[name="CSRF-Token"]').content; const siteaccess = doc.querySelector('meta[name="SiteAccess"]').content; - const editForm = doc.querySelector('.ibexa-content-type-edit-form'); - const inputsToValidate = editForm.querySelectorAll(SELECTOR_INPUTS_TO_VALIDATE); const sectionsNode = doc.querySelector('.ibexa-content-type-edit__sections'); const filterFieldInput = doc.querySelector('.ibexa-available-field-types__sidebar-filter'); const popupMenuElement = sectionsNode.querySelector('.ibexa-popup-menu'); @@ -89,9 +89,9 @@ const fieldGroupInput = fieldNode.querySelector('.ibexa-input--field-group'); const removeFieldsBtn = fieldNode.querySelectorAll('.ibexa-collapse__extra-action-button--remove-field-definitions'); - const inputsToValidate = fieldNode.querySelectorAll(SELECTOR_INPUTS_TO_VALIDATE); + const fieldInputsToValidate = fieldNode.querySelectorAll(SELECTOR_INPUTS_TO_VALIDATE); - inputsToValidate.forEach(attachValidateEvents); + fieldInputsToValidate.forEach(attachValidateEvents); removeDragPlaceholders(); fieldGroupInput.value = fieldsGroupId; targetContainer.insertBefore(fieldNode, targetPlace); @@ -143,7 +143,6 @@ }); }; const afterChangeGroup = () => { - const fieldsDefinitionCount = doc.querySelectorAll('.ibexa-collapse--field-definition').length; const groups = doc.querySelectorAll('.ibexa-collapse--field-definitions-group'); const itemsAction = doc.querySelectorAll('.ibexa-content-type-edit__add-field-definitions-group .ibexa-popup-menu__item-content'); @@ -276,15 +275,13 @@ errorNode.innerHTML = isInputEmpty ? errorMessage : ''; } - if (isEditFormValid && isInputEmpty) { - isEditFormValid = false; - } + isEditFormValid = isEditFormValid && !isInputEmpty; }; const validateForm = () => { - const inputsToValidate = editForm.querySelectorAll(SELECTOR_INPUTS_TO_VALIDATE); const fieldDefinitionsStatuses = {}; isEditFormValid = true; + inputsToValidate = editForm.querySelectorAll(SELECTOR_INPUTS_TO_VALIDATE); inputsToValidate.forEach((input) => { const fieldDefinition = input.closest('.ibexa-collapse--field-definition'); @@ -311,9 +308,9 @@ }); }; const attachValidateEvents = (input) => { - input.addEventListener('change', () => validateForm(input), false); - input.addEventListener('blur', () => validateForm(input), false); - input.addEventListener('input', () => validateForm(input), false); + input.addEventListener('change', validateForm, false); + input.addEventListener('blur', validateForm, false); + input.addEventListener('input', validateForm, false); }; class FieldDefinitionDraggable extends ibexa.core.Draggable { onDrop(event) {