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 -%}