tech(refacto): extrait la logique de rendu du dsfr sur les composants

This commit is contained in:
Martin 2023-08-21 16:18:31 +02:00
parent f54ab0bf96
commit 094f4f3ce6
48 changed files with 207 additions and 146 deletions

View file

@ -17,6 +17,10 @@ class Dsfr::InputComponent < ApplicationComponent
@required = required
end
def dsfr_champ_container
:div
end
# add invalid class on input when input is invalid
# and and valid on input only if another input is invalid
def input_group_opts
@ -50,6 +54,10 @@ class Dsfr::InputComponent < ApplicationComponent
get_slot(:label).presence || default_label
end
def dsfr_input_classname
'fr-input'
end
# kind of input helpers
def password?
@input_type == :password_field

View file

@ -8,6 +8,30 @@ module Dsfr
renders_one :hint
def dsfr_group_classname
if dsfr_champ_container == :fieldset
'fr-fieldset'
else
"#{dsfr_input_classname}-group"
end
end
def input_group_error_class_names
{
"#{dsfr_group_classname}--error" => errors_on_attribute?,
"#{dsfr_group_classname}--valid" => !errors_on_attribute? && errors_on_another_attribute?
}
end
def errors_on_attribute?
errors.has_key?(attribute_or_rich_body)
end
# errors helpers
def error_full_messages
errors.full_messages_for(attribute_or_rich_body)
end
private
# lookup for edge case from `form.rich_text_area`
@ -21,72 +45,8 @@ module Dsfr
end
end
def fr_select?
return false if fr_fieldset?
[
'departements',
'drop_down_list',
'multiple_drop_down_list',
'pays',
'regions'
].include?(@champ.type_champ)
end
def fr_input?
[
'annuaire_education',
'date',
'datetime',
'decimal_number',
'dgfip',
'dossier_link',
'email',
'iban',
'integer_number',
'mesri',
'number',
'phone',
'piece_justificative',
'pole_emploi',
'rna',
'siret',
'text',
'textarea',
'titre_identite'
].include?(@champ.type_champ)
end
def fr_radio?
[
'boolean'
].include?(@champ.type_champ)
end
def fr_fieldset?
@champ.dsfr_champ_container == :fieldset
end
def dsfr_group_classname
if fr_fieldset?
dsfr_input_classname
else
"#{dsfr_input_classname}-group"
end
end
def dsfr_input_classname
return "fr-fieldset" if fr_fieldset?
return "fr-input" if fr_input?
return "fr-select" if fr_select?
return "fr-radio" if fr_radio?
end
def input_group_error_class_names
{
"#{dsfr_group_classname}--error" => errors_on_attribute?,
"#{dsfr_group_classname}--valid" => !errors_on_attribute? && errors_on_another_attribute?
}
!['fr-input', 'fr-radio', 'fr-select'].include?(dsfr_input_classname)
end
def input_error_class_names
@ -133,23 +93,10 @@ module Dsfr
@opts
end
def describedby_id
dom_id(@champ, :error_full_messages)
end
def errors_on_another_attribute?
!errors.empty?
end
def errors_on_attribute?
errors.has_key?(attribute_or_rich_body)
end
# errors helpers
def error_full_messages
errors.full_messages_for(attribute_or_rich_body)
end
def map_array_to_hash_with_true(array_or_string_or_nil)
Array(array_or_string_or_nil).to_h { [_1, true] }
end

View file

@ -1,2 +1,5 @@
class EditableChamp::AnnuaireEducationComponent < EditableChamp::ComboSearchComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,2 +1,9 @@
class EditableChamp::CheckboxComponent < EditableChamp::EditableChampBaseComponent
def dsfr_champ_container
:fieldset
end
def dsfr_input_classname
'fr-radio'
end
end

View file

@ -1,6 +1,10 @@
class EditableChamp::CommunesComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_champ_container
:fieldset
end
private
def commune_options

View file

@ -1,5 +1,5 @@
---
fr:
postal_code: "Renseignez le <strong>code postal</strong>"
commune: "Sélectionnez <strong>la commune</strong> dans la liste"
commune: "Sélectionnez la commune dans la liste"
not_found: Aucune commune trouvée pour le code postal %{postal_code}. Verifiez que vous n'avez pas fait derreur.

View file

@ -8,7 +8,7 @@
.fr-fieldset__element.fr-mb-0
- if commune_options.empty?
- # noop
-# noop
- elsif commune_options.size <= 3
%fieldset.fr-fieldset
.fr-fieldset__legend--regular.fr-fieldset__legend= t('.commune').html_safe
@ -16,11 +16,8 @@
- commune_options.each.with_index do |(option, value), index|
.fr-fieldset__element
.fr-radio-group
= @form.radio_button :value, value, checked: @champ.selected == value, id: index == 0 ? @champ.input_id : nil
= @form.label :value, option, for: option, class: 'fr-label'
/ %label
/ = @form.radio_button :value, value, checked: @champ.selected == value, id: index == 0 ? @champ.input_id : nil
/ = option
= @form.radio_button :value, value, checked: @champ.selected == value, id: index == 0 ? @champ.input_id : "radio-#{index}-#{value.parameterize}"
= @form.label :value, option, for: index == 0 ? @champ.input_id : "radio-#{index}-#{value.parameterize}", class: 'fr-label'
- else
= @form.label :value, t('.commune').html_safe, for: @champ.input_id, class: 'fr-label'
= @form.select :value, commune_options, commune_select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile fr-select"

View file

@ -1,2 +1,5 @@
class EditableChamp::DateComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::DatetimeComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def formatted_value_for_datetime_locale
if @champ.valid? && @champ.value.present?
# convert to a format that the datetime-local input can understand

View file

@ -1,2 +1,5 @@
class EditableChamp::DecimalNumberComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -3,6 +3,10 @@ class EditableChamp::DepartementsComponent < EditableChamp::EditableChampBaseCom
private
def dsfr_input_classname
'fr-select'
end
def options
APIGeoService.departements.map { ["#{_1[:code]} #{_1[:name]}", _1[:code]] }
end

View file

@ -1,2 +1,5 @@
class EditableChamp::DgfipComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::DossierLinkComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def dossier
@dossier ||= @champ.blank? ? nil : Dossier.visible_by_administration.find_by(id: @champ.to_s)
end

View file

@ -3,6 +3,14 @@ class EditableChamp::DropDownListComponent < EditableChamp::EditableChampBaseCom
class_names('width-100': contains_long_option?, 'fr-select': true)
end
def dsfr_input_classname
'fr-select'
end
def dsfr_champ_container
@champ.render_as_radios? ? :fieldset : :div
end
def contains_long_option?
max_length = 100
@champ.options.any? { _1.size > max_length }

View file

@ -1,6 +1,18 @@
class EditableChamp::EditableChampBaseComponent < ApplicationComponent
include Dsfr::InputErrorable
def dsfr_champ_container
:div
end
def dsfr_input_classname
nil
end
def describedby_id
@champ.describedby_id
end
def initialize(form:, champ:, seen_at: nil, opts: {})
@form, @champ, @seen_at, @opts = form, champ, seen_at, opts
@attribute = :value

View file

@ -1,11 +1,13 @@
class EditableChamp::EditableChampComponent < ApplicationComponent
include Dsfr::InputErrorable
def initialize(form:, champ:, seen_at: nil)
@form, @champ, @seen_at = form, champ, seen_at
@attribute = :value
end
def champ_component
@champ_component ||= component_class.new(form: @form, champ: @champ, seen_at: @seen_at)
end
private
def has_label?(champ)
@ -29,8 +31,8 @@ class EditableChamp::EditableChampComponent < ApplicationComponent
'editable-champ': true,
"editable-champ-#{@champ.type_champ}": true,
"hidden": !@champ.visible?,
dsfr_group_classname => true
}.merge(input_group_error_class_names)
champ_component.dsfr_group_classname => true
}.merge(champ_component.input_group_error_class_names)
),
id: @champ.input_group_id,
data: { controller: stimulus_controller, **data_dependent_conditions, **stimulus_values }

View file

@ -1,9 +1,9 @@
= content_tag((@champ.dsfr_champ_container), html_options) do
= content_tag((champ_component.dsfr_champ_container), html_options) do
- if has_label?(@champ)
= render EditableChamp::ChampLabelComponent.new form: @form, champ: @champ, seen_at: @seen_at
= render component_class.new(form: @form, champ: @champ, seen_at: @seen_at)
= render champ_component
= render Dsfr::InputStatusMessageComponent.new(errors_on_attribute: errors_on_attribute?, error_full_messages: error_full_messages, described_by: describedby_id)
= render Dsfr::InputStatusMessageComponent.new(errors_on_attribute: champ_component.errors_on_attribute?, error_full_messages: champ_component.error_full_messages, described_by: @champ.describedby_id)
= @form.hidden_field :id, value: @champ.id

View file

@ -1,4 +1,8 @@
class EditableChamp::EmailComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def email?
true
end

View file

@ -1,6 +1,10 @@
class EditableChamp::EpciComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_champ_container
:fieldset
end
private
def departement_options

View file

@ -1,9 +1,11 @@
.fr-fieldset__element
= @form.label "Le département de lEPCI", for: @champ.code_departement_input_id, class: 'fr-label'
= @form.label :code_departement, for: @champ.code_departement_input_id, class: 'fr-label' do
- "Le département de lEPCI"
= @form.select :code_departement, departement_options, departement_select_options, required: @champ.required?, id: @champ.code_departement_input_id, class: "width-33-desktop width-100-mobile fr-select"
- if @champ.departement?
.fr-fieldset__element
.fr-select-group
= @form.label "EPCI", for: @champ.epci_input_id, class: 'fr-label'
= @form.label :value, for: @champ.epci_input_id, class: 'fr-label' do
- "EPCI"
= @form.select :value, epci_options, epci_select_options, required: @champ.required?, id: @champ.epci_input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile fr-select"

View file

@ -1,2 +1,5 @@
class EditableChamp::IbanComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,2 +1,5 @@
class EditableChamp::IntegerNumberComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::LinkedDropDownListComponent < EditableChamp::EditableChampBaseComponent
def dsfr_champ_container
:fieldset
end
private
def secondary_label

View file

@ -1,2 +1,5 @@
class EditableChamp::MesriComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,3 +1,11 @@
class EditableChamp::MultipleDropDownListComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_input_classname
'fr-select'
end
def dsfr_champ_container
@champ.render_as_checkboxes? ? :fieldset : :div
end
end

View file

@ -1,6 +1,7 @@
- if @champ.options?
- if @champ.render_as_checkboxes?
- @form.collection_check_boxes :value, @champ.enabled_non_empty_options, :to_s, :to_s do |b|
= @form.collection_check_boxes :value, @champ.enabled_non_empty_options, :to_s, :to_s do |b|
- capture do
.fr-fieldset__element
.fr-checkbox-group
= b.check_box(checked: @champ.selected_options.include?(b.value), aria: { describedby: @champ.describedby_id }, id: @champ.checkbox_id(b.value), class: 'fr-checkbox-group__checkbox')

View file

@ -1,2 +1,5 @@
class EditableChamp::NumberComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,6 +1,10 @@
class EditableChamp::PaysComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_input_classname
'fr-select'
end
private
def options

View file

@ -1,2 +1,5 @@
class EditableChamp::PhoneComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::PieceJustificativeComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def view_as
if @champ.dossier.brouillon?
:link

View file

@ -1,6 +1,10 @@
class EditableChamp::RegionsComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_input_classname
'fr-select'
end
private
def options

View file

@ -1,2 +1,5 @@
class EditableChamp::RNAComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,2 +1,5 @@
class EditableChamp::SiretComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,2 +1,5 @@
class EditableChamp::TextComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,3 +1,6 @@
class EditableChamp::TextareaComponent < EditableChamp::EditableChampBaseComponent
include HtmlToStringHelper
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::TitreIdentiteComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def user_can_destroy?
!@champ.mandatory? || @champ.dossier.brouillon?
end

View file

@ -1,2 +1,5 @@
class EditableChamp::YesNoComponent < EditableChamp::EditableChampBaseComponent
def dsfr_champ_container
:fieldset
end
end

View file

@ -156,10 +156,6 @@ class Champ < ApplicationRecord
stable_id_with_maybe_row.split('|')
end
def dsfr_champ_container
:div
end
def html_label?
true
end

View file

@ -1,8 +1,4 @@
class Champs::CheckboxChamp < Champs::BooleanChamp
def dsfr_champ_container
:fieldset
end
def for_export
true? ? 'on' : 'off'
end
@ -15,6 +11,11 @@ class Champs::CheckboxChamp < Champs::BooleanChamp
false
end
# TODO remove when normalize_checkbox_values is over
def true?
value_with_legacy == TRUE_VALUE
end
def html_label?
false
end
@ -22,4 +23,11 @@ class Champs::CheckboxChamp < Champs::BooleanChamp
def single_checkbox?
true
end
private
# TODO remove when normalize_checkbox_values is over
def value_with_legacy
value == 'on' ? TRUE_VALUE : value
end
end

View file

@ -96,10 +96,6 @@ class Champs::CommuneChamp < Champs::TextChamp
true
end
def dsfr_champ_container
:fieldset
end
private
def safe_to_s

View file

@ -30,10 +30,6 @@ class Champs::DropDownListChamp < Champ
render_as_radios?
end
def dsfr_champ_container
render_as_radios? ? :fieldset : :div
end
def selected
other? ? OTHER : value
end

View file

@ -22,10 +22,6 @@ class Champs::EpciChamp < Champs::TextChamp
code_departement.present?
end
def dsfr_champ_container
:fieldset
end
def html_label?
false
end

View file

@ -13,10 +13,6 @@ class Champs::LinkedDropDownListChamp < Champ
end
end
def dsfr_champ_container
:fieldset
end
def secondary_value
if value.present?
JSON.parse(value)[1]

View file

@ -43,10 +43,6 @@ class Champs::MultipleDropDownListChamp < Champ
enabled_non_empty_options.size <= THRESHOLD_NB_OPTIONS_AS_CHECKBOX
end
def dsfr_champ_container
render_as_checkboxes? ? :fieldset : :div
end
def html_label?
!render_as_checkboxes?
end

View file

@ -1,8 +1,4 @@
class Champs::YesNoChamp < Champs::BooleanChamp
def dsfr_champ_container
:fieldset
end
def legend_label?
true
end

View file

@ -44,8 +44,7 @@
%form.form
= form_for @dossier, url: '', html: { class: 'form' } do |f|
= f.fields_for :champs_public do |champ_form|
= render EditableChamp::EditableChampComponent.new champ: champ_form.object, form: champ_form
= render EditableChamp::SectionComponent.new(champs: @dossier.champs_public)
.editable-champ.editable-champ-text
%label Mot de passe

View file

@ -26,6 +26,9 @@ shared_examples "the user has got a prefilled dossier, owned by themselves" do
expect(page).to have_field("Le département de lEPCI", with: epci_value.first)
expect(page).to have_selector("option[value='#{epci_value.last}'][selected]")
expect(page).to have_field(type_de_champ_dossier_link.libelle, with: dossier_link_value)
label_commune = page.find(:label, text: commune_libelle)
radio_commune = page.find("##{label_commune['for']}")
expect(radio_commune).to be_checked
expect(page).to have_field(commune_libelle, with: '01457')
expect(page).to have_content(annuaire_education_value.last)
expect(page).to have_content(address_value.last)

View file

@ -21,15 +21,15 @@ describe 'The user' do
fill_in('decimal_number', with: '17')
fill_in('integer_number', with: '12')
scroll_to(find_field('checkbox'), align: :center)
check('checkbox')
find('label', text: 'checkbox').click
choose('Madame')
fill_in('email', with: 'loulou@yopmail.com')
fill_in('phone', with: '0123456789')
scroll_to(find_field('Non'), align: :center)
choose('Non')
choose('val2')
check('val1')
check('val3')
find('label', text: 'Non').click
find('.fr-radio-group label', text: 'val2').click
find('.fr-checkbox-group label', text: 'val1').click
find('.fr-checkbox-group label', text: 'val3').click
select('bravo', from: form_id_for('simple_choice_drop_down_list_long'))
select('alpha', from: form_id_for('multiple_choice_drop_down_list_long'))
select('charly', from: form_id_for('multiple_choice_drop_down_list_long'))
@ -37,8 +37,9 @@ describe 'The user' do
select('Australie', from: form_id_for('pays'))
select('Martinique', from: form_id_for('regions'))
select('02 Aisne', from: form_id_for('departements'))
fill_in('Renseignez le code postal puis sélectionnez la commune dans la liste', with: '60400')
select('Brétigny (60400)', from: form_id_for('communes'))
fill_in('Renseignez le code postal', with: '60400')
# wait_until { all('label', text: 'Sélectionnez la commune dans la liste').size == 1 }
select('Brétigny (60400)', from: form_id_for('commune'))
# communes needs more time to be updated
wait_until { champ_value_for('communes') == "Brétigny" }
@ -97,7 +98,7 @@ describe 'The user' do
expect(page).to have_button('alpha')
expect(page).to have_button('charly')
end
expect(page).to have_selected_value('communes', selected: 'Brétigny (60400)')
expect(page).to have_selected_value('commune', selected: 'Brétigny (60400)')
expect(page).to have_selected_value('pays', selected: 'Australie')
expect(page).to have_field('dossier_link', with: '123')
expect(page).to have_text('file.pdf')
@ -359,15 +360,15 @@ describe 'The user' do
fill_individual
expect(page).to have_no_css('label', text: 'champ_c', visible: true)
check('champ_a')
find('label', text: 'champ_a').click # check
wait_for_autosave
expect(page).to have_css('label', text: 'champ_c', visible: true)
uncheck('champ_a')
find('label', text: 'champ_a').click # uncheck
wait_for_autosave
expect(page).to have_no_css('label', text: 'champ_c', visible: true)
check('champ_b')
find('label', text: 'champ_b').click # check
wait_for_autosave
expect(page).to have_css('label', text: 'champ_c', visible: true)
@ -554,7 +555,7 @@ describe 'The user' do
end
def fill_individual
choose 'Monsieur'
find('label', text: 'Monsieur').click
fill_in('individual_prenom', with: 'prenom')
fill_in('individual_nom', with: 'nom')
click_on 'Continuer'