diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index cf9efaa7..8ac122f9 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -90,6 +90,15 @@ $sizes: (
border: none;
}
+ @include form-validation-state("valid", $cyan, url("data:image/svg+xml,"));
+
+ .custom-checkbox {
+ .custom-control-input:checked ~ .custom-control-label {
+ &::after {
+ background-image: url("data:image/svg+xml,");
+ }
+ }
+ }
}
// TODO: Encontrar la forma de generar esto desde los locales de Rails
diff --git a/app/javascript/controllers/form_validation_controller.js b/app/javascript/controllers/form_validation_controller.js
new file mode 100644
index 00000000..fe65934e
--- /dev/null
+++ b/app/javascript/controllers/form_validation_controller.js
@@ -0,0 +1,29 @@
+import { Controller } from "stimulus";
+
+export default class extends Controller {
+ connect() {
+ this.element.setAttribute("novalidate", true);
+
+ for (const input of this.element.elements) {
+ if (input.type === "button" || input.type === "submit") continue;
+
+ if (input.dataset.action) {
+ input.dataset.action = `${input.dataset.action} htmx:validation:validate->form-validation#submit`;
+ } else {
+ input.dataset.action = "htmx:validation:validate->form-validation#submit";
+ }
+ }
+ }
+
+ submit(event = undefined) {
+ event?.preventDefault();
+ event?.stopPropagation();
+
+ if (this.element.reportValidity()) {
+ this.element.classList.remove("was-validated");
+ this.element.submit();
+ } else {
+ this.element.classList.add("was-validated");
+ }
+ }
+}
diff --git a/app/javascript/controllers/required_checkbox_controller.js b/app/javascript/controllers/required_checkbox_controller.js
index 9754b506..a24ce881 100644
--- a/app/javascript/controllers/required_checkbox_controller.js
+++ b/app/javascript/controllers/required_checkbox_controller.js
@@ -20,5 +20,20 @@ export default class extends Controller {
} else {
this.requiredTarget.required = !Array.from(this.checkboxTargets).some(x => x.checked);
}
+
+ for (const checkbox of this.checkboxTargets) {
+ if (checkbox === event.target) continue;
+
+ checkbox.required = !event.target.checked;
+ }
+ }
+
+ /*
+ * Si el checkbox es considerado
+ */
+ invalid(event) {
+ for (const checkbox of this.checkboxTargets) {
+ checkbox.required = true;
+ }
}
}
diff --git a/app/javascript/etc/index.js b/app/javascript/etc/index.js
index 3a1ef75c..641d8085 100644
--- a/app/javascript/etc/index.js
+++ b/app/javascript/etc/index.js
@@ -4,6 +4,5 @@ import './input-tag'
import './prosemirror'
import './timezone'
import './turbolinks-anchors'
-import './validation'
import './new_editor'
import './htmx_abort'
diff --git a/app/javascript/etc/validation.js b/app/javascript/etc/validation.js
deleted file mode 100644
index 5a48148f..00000000
--- a/app/javascript/etc/validation.js
+++ /dev/null
@@ -1,34 +0,0 @@
-document.addEventListener('turbolinks:load', () => {
- // Al enviar el formulario del artículo, aplicar la validación
- // localmente y actualizar los comentarios para lectores de pantalla.
- document.querySelectorAll('form').forEach(form => {
- form.addEventListener('submit', event => {
- const invalid_help = form.querySelectorAll('.invalid-help')
- const sending_help = form.querySelectorAll('.sending-help')
-
- invalid_help.forEach(i => i.classList.add('d-none'))
- sending_help.forEach(i => i.classList.add('d-none'))
-
- form.querySelectorAll('[aria-invalid="true"]').forEach(aria => {
- aria.setAttribute('aria-invalid', false)
- aria.setAttribute('aria-describedby', aria.parentElement.querySelector('.feedback').id)
- })
-
- if (form.checkValidity() === false) {
- event.preventDefault()
- event.stopPropagation()
-
- invalid_help.forEach(i => i.classList.remove('d-none'))
-
- form.querySelectorAll(':invalid').forEach(invalid => {
- invalid.setAttribute('aria-invalid', true)
- invalid.setAttribute('aria-describedby', invalid.parentElement.querySelector('.invalid-feedback').id)
- })
- } else {
- sending_help.forEach(i => i.classList.remove('d-none'))
- }
-
- form.classList.add('was-validated')
- })
- })
-})
diff --git a/app/views/posts/_form.haml b/app/views/posts/_form.haml
index 8a86d203..aa8f3d1d 100644
--- a/app/views/posts/_form.haml
+++ b/app/views/posts/_form.haml
@@ -33,7 +33,7 @@
- dir = t("locales.#{@locale}.dir")
-# Comienza el formulario
-= form_tag url, method: method, class: "form post #{extra_class}", multipart: true, data: { controller: 'unsaved-changes', action: 'unsaved-changes#submit beforeunload@window->unsaved-changes#unsaved turbolinks:before-visit@window->unsaved-changes#unsavedTurbolinks', 'unsaved-changes-confirm-value': t('.confirm') } do
+= form_tag url, method: method, class: "form post #{extra_class}", multipart: true, data: { controller: 'unsaved-changes form-validation', action: 'unsaved-changes#submit form-validation#submit beforeunload@window->unsaved-changes#unsaved turbolinks:before-visit@window->unsaved-changes#unsavedTurbolinks', 'unsaved-changes-confirm-value': t('.confirm') } do
-# Botones de guardado
= render 'posts/submit', site: site, post: post
diff --git a/app/views/posts/attributes/_new_array.haml b/app/views/posts/attributes/_new_array.haml
index 4293ded1..5778f619 100644
--- a/app/views/posts/attributes/_new_array.haml
+++ b/app/views/posts/attributes/_new_array.haml
@@ -15,11 +15,12 @@
Si la lista es obligatoria, al menos uno de los ítems tiene que
estar activado. Logramos esto con un checkbox oculto que se marca
como obligatorio al validar el formulario.
- - if metadata.required
- %input.form-control{ type: 'checkbox', name: name, data: { target: 'required-checkbox.required' }, required: metadata.value.empty? }
- .invalid-feedback Requerido!
.d-flex.align-items-center.justify-content-between
- = label_tag id, post_label_t(attribute, post: post)
+ %div
+ = label_tag id, post_label_t(attribute, post: post), class: 'mb-0'
+ - if metadata.required
+ %input.form-control.d-none{ type: 'checkbox', name: name, data: { target: 'required-checkbox.required', action: 'invalid->required-checkbox#invalid' }, required: metadata.value.empty? }
+ .invalid-feedback.mt-0= t('.required')
= render 'bootstrap/btn', content: t('.edit'), action: 'modal#show'
-# Mostramos la lista de valores actuales.
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 8d90a246..c3a16ba1 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -766,6 +766,7 @@ en:
edit: "Edit"
new_array:
edit: "Edit"
+ required: "Please select at least one option."
new_has_one:
edit: "Edit"
new_belongs_to:
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 414d3056..1a5a9be2 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -774,6 +774,7 @@ es:
edit: "Editar"
new_array:
edit: "Editar"
+ required: "Seleccioná al menos una opción."
new_has_one:
edit: "Editar"
new_belongs_to: