diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index 27cb8df7b8..c98dff1ede 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -18,6 +18,7 @@ const layout = [ path.resolve(__dirname, '../public/js/scripts/helpers/pagination.helper.js'), path.resolve(__dirname, '../public/js/scripts/helpers/object.instances.js'), path.resolve(__dirname, '../public/js/scripts/helpers/middle.ellipsis.js'), + path.resolve(__dirname, '../public/js/scripts/helpers/form.validation.helper.js'), path.resolve(__dirname, '../public/js/scripts/helpers/form.error.helper.js'), path.resolve(__dirname, '../public/js/scripts/admin.format.date.js'), path.resolve(__dirname, '../public/js/scripts/core/draggable.js'), 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 7cbed01335..6f09f52ef1 100644 --- a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js +++ b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js @@ -350,7 +350,7 @@ if (isInputEmpty) { const fieldName = labelNode.innerHTML; const errorMessage = ibexa.errors.emptyField.replace('{fieldName}', fieldName); - const formattedError = ibexa.helpers.formError.formatLine(errorMessage); + const formattedError = ibexa.helpers.formValidation.formatErrorLine(errorMessage); errorNode.append(formattedError); } diff --git a/src/bundle/Resources/public/js/scripts/core/dropdown.js b/src/bundle/Resources/public/js/scripts/core/dropdown.js index 6cda97fcc1..a546f0efc4 100644 --- a/src/bundle/Resources/public/js/scripts/core/dropdown.js +++ b/src/bundle/Resources/public/js/scripts/core/dropdown.js @@ -49,6 +49,11 @@ this.recreateOptions(); } }); + this.sourceInvalidObserver = new MutationObserver((mutationsList) => { + const isInvalid = mutationsList[0].target.classList.contains('is-invalid'); + + this.container.classList.toggle('is-invalid', isInvalid); + }); this.currentSelectedValue = this.sourceInput.value; this.createSelectedItem = this.createSelectedItem.bind(this); @@ -243,6 +248,7 @@ } option.remove(); + this.currentSelectedValue = null; this.fitItems(); this.fireValueChangedEvent(); @@ -516,6 +522,10 @@ this.sourceOptionsObserver.observe(this.sourceInput, { childList: true, }); + this.sourceInvalidObserver.observe(this.sourceInput, { + attributes: true, + attributeFilter: ['class'], + }); const selectedItems = this.container.querySelectorAll( '.ibexa-dropdown__selected-item:not(.ibexa-dropdown__selected-overflow-number):not(.ibexa-dropdown__selected-placeholder)', diff --git a/src/bundle/Resources/public/js/scripts/fieldType/base/base-field.js b/src/bundle/Resources/public/js/scripts/fieldType/base/base-field.js index 6bf6fd1e59..6dc6eb8480 100644 --- a/src/bundle/Resources/public/js/scripts/fieldType/base/base-field.js +++ b/src/bundle/Resources/public/js/scripts/fieldType/base/base-field.js @@ -111,7 +111,7 @@ * @memberof BaseFieldValidator */ createErrorNode(message) { - return ibexa.helpers.formError.formatLine(message); + return ibexa.helpers.formValidation.formatErrorLine(message); } /** diff --git a/src/bundle/Resources/public/js/scripts/helpers/form.error.helper.js b/src/bundle/Resources/public/js/scripts/helpers/form.error.helper.js index 52f0e78eae..6bc39c4760 100644 --- a/src/bundle/Resources/public/js/scripts/helpers/form.error.helper.js +++ b/src/bundle/Resources/public/js/scripts/helpers/form.error.helper.js @@ -1,19 +1,12 @@ (function (global, doc, ibexa) { - const formatLine = (errorMessage) => { - const errorIcon = ` - - `; - const container = document.createElement('em'); - const errorMessageNode = document.createTextNode(errorMessage); - - container.classList.add('ibexa-form-error__row'); - container.insertAdjacentHTML('beforeend', errorIcon); - container.append(errorMessageNode); - - return container; - }; - + // @deprecated, will be removed in 5.0 ibexa.addConfig('helpers.formError', { - formatLine, + formatLine: (...args) => { + console.warn( + 'helpers.formError.formatLine method is deprecated and will be removed in 5.0, please use helpers.formValidation.formatErrorLine instead.', + ); + + return ibexa.helpers.formValidation.formatErrorLine(...args); + }, }); })(window, window.document, window.ibexa); diff --git a/src/bundle/Resources/public/js/scripts/helpers/form.validation.helper.js b/src/bundle/Resources/public/js/scripts/helpers/form.validation.helper.js new file mode 100644 index 0000000000..f6c15b4c76 --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/helpers/form.validation.helper.js @@ -0,0 +1,52 @@ +(function (global, doc, ibexa, Translator) { + const formatErrorLine = (errorMessage) => { + const errorIcon = ` + + `; + const container = document.createElement('em'); + const errorMessageNode = document.createTextNode(errorMessage); + + container.classList.add('ibexa-form-error__row'); + container.insertAdjacentHTML('beforeend', errorIcon); + container.append(errorMessageNode); + + return container; + }; + const checkIsEmpty = (field) => { + let errorMessage = ''; + const input = field.querySelector('.ibexa-input'); + const label = field.querySelector('.ibexa-label'); + + if (label) { + const fieldName = label.innerText; + + errorMessage = Translator.trans(/*@Desc("%fieldName% Field is required")*/ 'error.required.field', { fieldName }, 'forms'); + } else { + errorMessage = Translator.trans(/*@Desc("This value should not be blank")*/ 'error.required.field_not_blank', {}, 'forms'); + } + + return { + isValid: input.value, + errorMessage, + }; + }; + const validateIsEmptyField = (field) => { + const input = field.querySelector('.ibexa-input'); + const errorWrapper = field.querySelector('.ibexa-form-error'); + const validatorOutput = checkIsEmpty(field); + const { isValid, errorMessage } = validatorOutput; + + input.classList.toggle('is-invalid', !isValid); + errorWrapper.innerText = ''; + + if (!isValid) { + errorWrapper.append(formatErrorLine(errorMessage)); + } + + return validatorOutput; + }; + ibexa.addConfig('helpers.formValidation', { + formatErrorLine, + validateIsEmptyField, + }); +})(window, window.document, window.ibexa, window.Translator); diff --git a/src/bundle/Resources/public/js/scripts/user_password.change.js b/src/bundle/Resources/public/js/scripts/user_password.change.js index 15c0be63ad..e917a625e2 100644 --- a/src/bundle/Resources/public/js/scripts/user_password.change.js +++ b/src/bundle/Resources/public/js/scripts/user_password.change.js @@ -16,7 +16,7 @@ * @returns {HTMLElement} */ const createErrorNode = (message) => { - return ibexa.helpers.formError.formatLine(message); + return ibexa.helpers.formValidation.formatErrorLine(message); }; /** diff --git a/src/bundle/Resources/public/scss/_forms.scss b/src/bundle/Resources/public/scss/_forms.scss index 4e41255125..be61038366 100644 --- a/src/bundle/Resources/public/scss/_forms.scss +++ b/src/bundle/Resources/public/scss/_forms.scss @@ -75,7 +75,6 @@ form:not(.form-inline) { color: $ibexa-color-danger; line-height: calculateRem(18px); box-sizing: border-box; - margin-top: calculateRem(4px); &:not(& > &) { flex-direction: column; @@ -85,6 +84,7 @@ form:not(.form-inline) { display: flex; align-items: center; font-style: normal; + margin-top: calculateRem(4px); } &__icon { diff --git a/src/bundle/Resources/public/scss/fieldType/edit/_base-field.scss b/src/bundle/Resources/public/scss/fieldType/edit/_base-field.scss index e2efec709d..8ee6328880 100644 --- a/src/bundle/Resources/public/scss/fieldType/edit/_base-field.scss +++ b/src/bundle/Resources/public/scss/fieldType/edit/_base-field.scss @@ -21,7 +21,6 @@ .ibexa-form-error { min-height: calculateRem(18px); - margin: calculateRem(4px) 0; flex-direction: column; } diff --git a/src/bundle/Resources/views/themes/admin/ui/form_fields/dropdown_widget.html.twig b/src/bundle/Resources/views/themes/admin/ui/form_fields/dropdown_widget.html.twig index edbe2fa645..2a1bc78e69 100644 --- a/src/bundle/Resources/views/themes/admin/ui/form_fields/dropdown_widget.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/form_fields/dropdown_widget.html.twig @@ -27,6 +27,7 @@ is_selector: attr.is_selector|default(false), is_dynamic: attr.is_dynamic|default(false), has_select_all_toggler: attr.has_select_all_toggler|default(false), + placeholder: attr.placeholder|default(placeholder), } %} {% endif %} {%- endblock choice_widget -%}