chore(a11y): mutualise la gestion des <button> qui font des action POST/DESTROY dans un composant unique

This commit is contained in:
Martin 2023-03-31 16:10:47 +02:00
parent d161a624c5
commit 035da4403f
19 changed files with 45 additions and 46 deletions

View file

@ -1,7 +0,0 @@
# Display a form for destroying a file attachment via a button, but since it might already be nested within a form
# put this component before the actual form containing the editcomponent
class Attachment::DeleteFormComponent < ApplicationComponent
def call
form_tag('/attachments/:id', method: :delete, data: { 'turbo-method': :delete, turbo: true }, id: dom_id(ActiveStorage::Attachment.new, :delete)) {}
end
end

View file

@ -3,7 +3,7 @@
%div{ id: dom_id(attachment, :persisted_row) }
.flex.flex-gap-2{ class: class_names("attachment-error": attachment.virus_scanner_error?) }
- if user_can_destroy?
= button_tag(name: "action", formaction: destroy_attachment_path, class: "fr-btn fr-btn--tertiary fr-btn--sm fr-icon-delete-line", title: t(".delete_file", filename: attachment.filename), form: dom_id(ActiveStorage::Attachment.new, :delete), data: {turbo: true, 'turbo-method': 'delete'}) do
= render NestedForms::OwnedButtonComponent.new(formaction: destroy_attachment_path, http_method: :delete, opt: {class: "fr-btn fr-btn--tertiary fr-btn--sm fr-icon-delete-line", title: t(".delete_file", filename: attachment.filename)}) do
= t('.delete')
- elsif user_can_replace?
= button_tag t('.replace'), **replace_button_options, class: "fr-btn fr-btn--tertiary fr-btn--sm", title: t(".replace_file", filename: attachment.filename)

View file

@ -1,11 +0,0 @@
# Display a form for adding a repetition row via a button, but since it might already nested within a form
# put this component before the actual form containing the editable champs
class EditableChamp::AddFormRepetitionRowComponent < ApplicationComponent
def self.form_id
ActionView::RecordIdentifier.dom_id(Champs::RepetitionChamp.new, :create)
end
def call
form_tag('/champs/repetition/:id', method: :post, data: { 'turbo-method': :post, turbo: true }, id: self.class.form_id) {}
end
end

View file

@ -1,11 +0,0 @@
# Display a form for destroying a repetition row via a button, but since it might already be nested within a form
# put this component before the actual form containing the editable champs
class EditableChamp::DeleteFormRepetitionRowComponent < ApplicationComponent
def self.form_id
ActionView::RecordIdentifier.dom_id(Champs::RepetitionChamp.new, :delete)
end
def call
form_tag('/champs/repetition/:id', method: :delete, data: { 'turbo-method': :delete, turbo: true }, id: self.class.form_id) {}
end
end

View file

@ -3,5 +3,5 @@
= render EditableChamp::RepetitionRowComponent.new(form: @form, champ: @champ, row: champs, seen_at: @seen_at)
.actions{ 'data-turbo': 'true' }
= button_tag(name: "action", formaction: champs_repetition_path(@champ.id), class: "fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-circle-line fr-mb-3w", title: t(".add_title", libelle: @champ.libelle), form: EditableChamp::AddFormRepetitionRowComponent.form_id, id: dom_id(@champ, :create_repetition)) do
= render NestedForms::OwnedButtonComponent.new(formaction: champs_repetition_path(@champ.id), http_method: :create, opt: { class: "fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-circle-line fr-mb-3w", title: t(".add_title", libelle: @champ.libelle), id: dom_id(@champ, :create_repetition)}) do
= t(".add", libelle: @champ.libelle)

View file

@ -5,5 +5,5 @@
= render EditableChamp::EditableChampComponent.new form: form, champ: champ, seen_at: @seen_at
.flex.row-reverse{ 'data-turbo': 'true' }
= button_tag(name: "action", formaction: champs_repetition_path(@champ.id, row_id: @row.first.row_id), class: "fr-btn fr-btn--sm fr-btn--tertiary fr-text-action-high--red-marianne", title: t(".delete_title", row_number: @champ.rows.find_index(@row)), form: EditableChamp::DeleteFormRepetitionRowComponent.form_id) do
= render NestedForms::OwnedButtonComponent.new(formaction: champs_repetition_path(@champ.id, row_id: @row.first.row_id), http_method: :delete, opt: { class: "fr-btn fr-btn--sm fr-btn--tertiary fr-text-action-high--red-marianne", title: t(".delete_title", row_number: @champ.rows.find_index(@row))}) do
= t(".delete")

View file

@ -0,0 +1,17 @@
# context: https://github.com/demarches-simplifiees/demarches-simplifiees.fr/issues/8661
# a11y: a post/delete/patch/put action must be wrapped in a <button>, not in an <a>
# but we can't nest <forms>
# this component exposes each http methods within a form, and can be used with OwnedButtonComponent
# background: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attributes
# see: from attribute & formaction
class NestedForms::FormOwnerComponent < ApplicationComponent
HTTP_METHODS = [:create, :delete]
private
def self.form_id(http_method)
raise ArgumentError, "invalid http_method: #{http_method}. supported methods are: #{HTTP_METHOD.join(',')}" if !HTTP_METHODS.include?(http_method)
"unested-form-#{http_method}"
end
end

View file

@ -0,0 +1,2 @@
- HTTP_METHODS.map do |http_method|
= form_tag('/', method: http_method, data: { 'turbo-method': http_method, turbo: true }, id: NestedForms::FormOwnerComponent.form_id(http_method)) {}

View file

@ -0,0 +1,13 @@
class NestedForms::OwnedButtonComponent < ApplicationComponent
renders_one :button_label
def initialize(formaction:, http_method:, opt: {})
@formaction = formaction
@http_method = http_method
@opt = opt
end
def call
button_tag(content, @opt.merge(formaction: @formaction, form: NestedForms::FormOwnerComponent.form_id(@http_method)))
end
end

View file

@ -7,7 +7,7 @@
.procedure-form#attestation-template-edit
.procedure-form__columns.container
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= form_for @attestation_template,
url: admin_procedure_attestation_template_path(@procedure),
html: { multipart: true, class: 'form form-ds-fr-white procedure-form__column--form' } do |f|

View file

@ -6,5 +6,5 @@
.container
%h1 Configuration des annotations privées
%br
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= render TypesDeChampEditor::EditorComponent.new(revision: @procedure.draft_revision, is_annotation: true)

View file

@ -6,5 +6,5 @@
.container
%h1 Configuration des champs
%br
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= render TypesDeChampEditor::EditorComponent.new(revision: @procedure.draft_revision)

View file

@ -6,7 +6,7 @@
['Description']] }
.procedure-form
.procedure-form__columns.container
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= form_for @procedure,
url: url_for({ controller: 'administrateurs/procedures', action: :update, id: @procedure.id }),
html: { class: 'form procedure-form__column--form',

View file

@ -6,7 +6,7 @@
.procedure-form
.procedure-form__columns.container
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= form_for @procedure,
url: url_for({ controller: 'administrateurs/procedures', action: :create, id: @procedure.id }),
html: { class: 'form procedure-form__column--form', multipart: true } do |f|

View file

@ -19,7 +19,7 @@
- if @avis.introduction_file.attached?
= render Attachment::ShowComponent.new(attachment: @avis.introduction_file.attachment)
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= form_for @avis, url: expert_avis_path(@avis.procedure, @avis), html: { data: { controller: 'persisted-form', persisted_form_key_value: dom_id(@avis) }, multipart: true } do |f|
- if @avis.question_label.present?

View file

@ -8,7 +8,7 @@
%p#avis-emails-description.avis-notice
Entrez les adresses email des experts à qui vous souhaitez demander un avis
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= form_for avis, url: url, html: { multipart: true, data: { controller: 'persisted-form', persisted_form_key_value: dom_id(@dossier, :avis_by_instructeur) } } do |f|
= hidden_field_tag 'avis[emails]', nil
.fr-input-group

View file

@ -9,9 +9,7 @@
- form_options = { url: brouillon_dossier_url(dossier), method: :patch }
- else
- form_options = { url: modifier_dossier_url(dossier), method: :patch }
= render EditableChamp::DeleteFormRepetitionRowComponent.new
= render EditableChamp::AddFormRepetitionRowComponent.new
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= form_for dossier, form_options.merge({ html: { id: 'dossier-edit-form', class: 'form', multipart: true, novalidate: 'novalidate' } }) do |f|
%header.mb-6

View file

@ -1,9 +1,7 @@
.container.dossier-edit
- if dossier.champs_private.present?
%section.counter-start-header-section
= render EditableChamp::AddFormRepetitionRowComponent.new
= render EditableChamp::DeleteFormRepetitionRowComponent.new
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= form_for dossier, url: annotations_instructeur_dossier_path(dossier.procedure, dossier), html: { class: 'form', multipart: true } do |f|
- dossier.champs_private.each do |champ|
= fields_for champ.input_name, champ do |form|

View file

@ -1,4 +1,4 @@
= render Attachment::DeleteFormComponent.new
= render NestedForms::FormOwnerComponent.new
= form_for(commentaire, url: form_url, html: { class: 'form', multipart: true, data: { controller: 'persisted-form', persisted_form_key_value: @dossier.present? ? dom_id(@dossier) : dom_id(@procedure, :bulk_message) } }) do |f|
- dossier = commentaire.dossier
- placeholder = t('views.shared.dossiers.messages.form.write_message_to_administration_placeholder')