tech(refactor): much nicer code, thx LeSim
root -> rooted_tree depth_cach -> walk smal refactor comment remove form for header_section remove seen_at from header section header_section: champ -> header_section champ_subree: remove if not remove root_depth use header_section_level_value instead remove unused include remove ChampTreeComponent rename ChampsSubtreeComponent to SectionComponent use TreeableConcern only in section component remove fields_for_champ_component champs -> tail add split_section_champ helper refactor(editable_champ::header_section): keep same interface everywhere fix(repetition): add spec for SectionComponent on repetitions
This commit is contained in:
parent
cc2c856ec2
commit
e64ac79f05
16 changed files with 139 additions and 109 deletions
|
@ -1,47 +0,0 @@
|
|||
class EditableChamp::ChampsSubtreeComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include TreeableConcern
|
||||
|
||||
def initialize(nodes:)
|
||||
@nodes = to_fieldset(nodes:)
|
||||
end
|
||||
|
||||
def render_within_fieldset?
|
||||
first_champ_is_an_header_section? && any_champ_fillable?
|
||||
end
|
||||
|
||||
def header_section
|
||||
first_champ = @nodes.first
|
||||
return first_champ if first_champ.is_a?(Champs::HeaderSectionChamp)
|
||||
nil
|
||||
end
|
||||
|
||||
def champs
|
||||
return @nodes if !first_champ_is_an_header_section?
|
||||
_, *rest_of_champ = @nodes
|
||||
|
||||
rest_of_champ
|
||||
end
|
||||
|
||||
def tag_for_depth
|
||||
"h#{header_section.level + 1}"
|
||||
end
|
||||
|
||||
def fillable?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_fieldset(nodes:)
|
||||
nodes.map { _1.is_a?(Array) ? EditableChamp::ChampsSubtreeComponent.new(nodes: _1) : _1 }
|
||||
end
|
||||
|
||||
def first_champ_is_an_header_section?
|
||||
header_section.present?
|
||||
end
|
||||
|
||||
def any_champ_fillable?
|
||||
champs.any? { _1&.fillable? }
|
||||
end
|
||||
end
|
|
@ -1,17 +0,0 @@
|
|||
- if render_within_fieldset?
|
||||
= tag.fieldset(class: "reset-#{tag_for_depth}") do
|
||||
= tag.legend do
|
||||
= render EditableChamp::HeaderSectionComponent.new(champ: header_section, form: nil)
|
||||
- champs.each do |champ_or_section|
|
||||
- if !champ_or_section.is_a?(EditableChamp::ChampsSubtreeComponent)
|
||||
= render EditableChamp::FieldsForChampComponent.new(champ: champ_or_section, seen_at: nil)
|
||||
- else
|
||||
= render champ_or_section
|
||||
- else
|
||||
- if header_section
|
||||
= render EditableChamp::HeaderSectionComponent.new(champ: header_section, form: nil)
|
||||
- champs.each do |champ_or_section|
|
||||
- if !champ_or_section.is_a?(EditableChamp::ChampsSubtreeComponent)
|
||||
= render EditableChamp::FieldsForChampComponent.new(champ: champ_or_section, seen_at: nil)
|
||||
- else
|
||||
= render champ_or_section
|
|
@ -1,7 +0,0 @@
|
|||
class EditableChamp::ChampsTreeComponent < ApplicationComponent
|
||||
include TreeableConcern
|
||||
|
||||
def initialize(champs:, root_depth:)
|
||||
@tree = to_tree(champs:, root_depth:)
|
||||
end
|
||||
end
|
|
@ -1 +0,0 @@
|
|||
= render EditableChamp::ChampsSubtreeComponent.new(nodes: @tree)
|
|
@ -1,5 +0,0 @@
|
|||
class EditableChamp::FieldsForChampComponent < ApplicationComponent
|
||||
def initialize(champ:, seen_at: nil)
|
||||
@champ, @seen_at = champ, seen_at
|
||||
end
|
||||
end
|
|
@ -1,2 +0,0 @@
|
|||
= fields_for @champ.input_name, @champ do |form|
|
||||
= render EditableChamp::EditableChampComponent.new form: form, champ: @champ, seen_at: @seen_at
|
|
@ -1,7 +1,6 @@
|
|||
class EditableChamp::HeaderSectionComponent < ApplicationComponent
|
||||
def initialize(form:, champ:, seen_at: nil)
|
||||
def initialize(form: nil, champ:, seen_at: nil)
|
||||
@champ = champ
|
||||
@form = form
|
||||
end
|
||||
|
||||
def level
|
||||
|
@ -13,9 +12,9 @@ class EditableChamp::HeaderSectionComponent < ApplicationComponent
|
|||
end
|
||||
|
||||
def header_section_classnames
|
||||
class_names = ["fr-h#{level}", 'header-section']
|
||||
class_names = ["fr-h#{level}"]
|
||||
|
||||
class_names << 'header-section-counter' if @champ.dossier.auto_numbering_section_headers_for?(@champ)
|
||||
class_names << 'header-section' if @champ.dossier.auto_numbering_section_headers_for?(@champ)
|
||||
class_names
|
||||
end
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
- if @row.size > 1
|
||||
%fieldset
|
||||
%legend.block-id= "#{@champ.libelle} "
|
||||
= render EditableChamp::ChampsTreeComponent.new(champs: @row, root_depth: @champ.current_section_level)
|
||||
= render EditableChamp::SectionComponent.new(champs: @row)
|
||||
- else
|
||||
= render EditableChamp::ChampsTreeComponent.new(champs: @row, root_depth: @champ.current_section_level)
|
||||
= render EditableChamp::SectionComponent.new(champs: @row)
|
||||
|
||||
.flex.row-reverse{ 'data-turbo': 'true' }
|
||||
= 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
|
||||
|
|
64
app/components/editable_champ/section_component.rb
Normal file
64
app/components/editable_champ/section_component.rb
Normal file
|
@ -0,0 +1,64 @@
|
|||
class EditableChamp::SectionComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include TreeableConcern
|
||||
|
||||
def initialize(nodes: nil, champs: nil)
|
||||
if (nodes.nil?)
|
||||
nodes = to_tree(champs:)
|
||||
end
|
||||
@nodes = to_fieldset(nodes:)
|
||||
end
|
||||
|
||||
def render_within_fieldset?
|
||||
first_champ_is_an_header_section? && any_champ_fillable?
|
||||
end
|
||||
|
||||
def header_section
|
||||
return @nodes.first if @nodes.first.is_a?(Champs::HeaderSectionChamp)
|
||||
end
|
||||
|
||||
def splitted_tail
|
||||
tail.map { split_section_champ(_1) }
|
||||
end
|
||||
|
||||
def tail
|
||||
return @nodes if !first_champ_is_an_header_section?
|
||||
_, *rest_of_champ = @nodes
|
||||
|
||||
rest_of_champ
|
||||
end
|
||||
|
||||
def tag_for_depth
|
||||
"h#{header_section.level + 1}"
|
||||
end
|
||||
|
||||
# if two headers follows each others [h1, [h2, c]]
|
||||
# the first one must not be contained in fieldset
|
||||
# so we make the tree not fillable
|
||||
def fillable?
|
||||
false
|
||||
end
|
||||
|
||||
def split_section_champ(node)
|
||||
case node
|
||||
when EditableChamp::SectionComponent
|
||||
[node, nil]
|
||||
else
|
||||
[nil, node]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_fieldset(nodes:)
|
||||
nodes.map { _1.is_a?(Array) ? EditableChamp::SectionComponent.new(nodes: _1) : _1 }
|
||||
end
|
||||
|
||||
def first_champ_is_an_header_section?
|
||||
header_section.present?
|
||||
end
|
||||
|
||||
def any_champ_fillable?
|
||||
tail.any? { _1&.fillable? }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
- if render_within_fieldset?
|
||||
= tag.fieldset(class: "reset-#{tag_for_depth}") do
|
||||
= tag.legend do
|
||||
= render EditableChamp::HeaderSectionComponent.new(champ: header_section)
|
||||
- splitted_tail.each do |section, champ|
|
||||
- if section.present?
|
||||
= render section
|
||||
- else
|
||||
= fields_for champ.input_name, champ do |form|
|
||||
= render EditableChamp::EditableChampComponent.new form: ,champ:
|
||||
- else
|
||||
- if header_section
|
||||
= render EditableChamp::HeaderSectionComponent.new(champ: header_section)
|
||||
- splitted_tail.each do |section, champ|
|
||||
- if section.present?
|
||||
= render section
|
||||
- else
|
||||
= fields_for champ.input_name, champ do |form|
|
||||
= render EditableChamp::EditableChampComponent.new form: ,champ:
|
|
@ -128,7 +128,7 @@ module Administrateurs
|
|||
:drop_down_secondary_description,
|
||||
:collapsible_explanation_enabled,
|
||||
:collapsible_explanation_text,
|
||||
:header_section_level
|
||||
:header_section_level,
|
||||
editable_options: [
|
||||
:cadastres,
|
||||
:unesco,
|
||||
|
|
|
@ -6,32 +6,32 @@ module TreeableConcern
|
|||
|
||||
included do
|
||||
# as we progress in the list of ordered champs
|
||||
# we keep a reference to each level of nesting (depth_cache)
|
||||
# we keep a reference to each level of nesting (walk)
|
||||
# when we encounter an header_section, it depends of its own depth of nesting minus 1, ie:
|
||||
# h1 belongs to prior (root)
|
||||
# h1 belongs to prior (rooted_tree)
|
||||
# h2 belongs to prior h1
|
||||
# h3 belongs to prior h2
|
||||
# h1 belongs to prior (root)
|
||||
# h1 belongs to prior (rooted_tree)
|
||||
# then, each and every champs which are not an header_section
|
||||
# are added to the most_recent_subtree
|
||||
# are added to the current_tree
|
||||
# given a root_depth at 0, we build a full tree
|
||||
# given a root_depth > 0, we build a partial tree (aka, a repetition)
|
||||
def to_tree(champs:, root_depth:)
|
||||
root = []
|
||||
depth_cache = Array.new(MAX_DEPTH)
|
||||
depth_cache[root_depth] = root
|
||||
most_recent_subtree = root
|
||||
def to_tree(champs:)
|
||||
rooted_tree = []
|
||||
walk = Array.new(MAX_DEPTH)
|
||||
walk[0] = rooted_tree
|
||||
current_tree = rooted_tree
|
||||
|
||||
champs.each do |champ|
|
||||
if champ.header_section?
|
||||
champs_subtree = [champ]
|
||||
depth_cache[champ.level - 1].push(champs_subtree)
|
||||
most_recent_subtree = depth_cache[champ.level] = champs_subtree
|
||||
new_tree = [champ]
|
||||
walk[champ.header_section_level_value - 1].push(new_tree)
|
||||
current_tree = walk[champ.header_section_level_value] = new_tree
|
||||
else
|
||||
most_recent_subtree.push(champ)
|
||||
current_tree.push(champ)
|
||||
end
|
||||
end
|
||||
root
|
||||
rooted_tree
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,5 +42,5 @@
|
|||
= f.select :groupe_instructeur_id,
|
||||
dossier.procedure.groupe_instructeurs.active.map { |gi| [gi.label, gi.id] },
|
||||
{ include_blank: dossier.brouillon? }
|
||||
= render EditableChamp::ChampsTreeComponent.new(champs: dossier.champs_public, root_depth: 0)
|
||||
= render EditableChamp::SectionComponent.new(champs: dossier.champs_public)
|
||||
= render Dossiers::EditFooterComponent.new(dossier: dossier, annotation: false)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
%section.counter-start-header-section
|
||||
= render NestedForms::FormOwnerComponent.new
|
||||
= form_for dossier, url: annotations_instructeur_dossier_path(dossier.procedure, dossier), html: { class: 'form', multipart: true } do |f|
|
||||
= render EditableChamp::ChampsTreeComponent.new(champs: dossier.champs_private, root_depth: 0)
|
||||
= render EditableChamp::SectionComponent.new(champs: dossier.champs_private)
|
||||
|
||||
= render Dossiers::EditFooterComponent.new(dossier: dossier, annotation: true)
|
||||
- else
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
describe EditableChamp::ChampsSubtreeComponent, type: :component do
|
||||
describe EditableChamp::SectionComponent, type: :component do
|
||||
include TreeableConcern
|
||||
let(:component) { described_class.new(nodes: nodes) }
|
||||
let(:nodes) { to_tree(champs:, root_depth:) }
|
||||
let(:root_depth) { 0 }
|
||||
let(:component) { described_class.new(champs: champs) }
|
||||
before { render_inline(component).to_html }
|
||||
|
||||
context 'list of champs without an header_section' do
|
||||
|
@ -83,4 +81,33 @@ describe EditableChamp::ChampsSubtreeComponent, type: :component do
|
|||
expect(page).to have_selector("fieldset fieldset textarea", count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with repetition' do
|
||||
let(:procedure) do
|
||||
create(:procedure, types_de_champ_public: [
|
||||
{ type: :header_section, header_section_level: 1 },
|
||||
{
|
||||
type: :repetition,
|
||||
libelle: 'repetition',
|
||||
children: [
|
||||
{ type: :header_section, header_section_level: 1, libelle: 'child_1' },
|
||||
{ type: :text, libelle: 'child_2' }
|
||||
]
|
||||
}
|
||||
])
|
||||
end
|
||||
let(:dossier) { create(:dossier, :with_populated_champs, procedure: procedure) }
|
||||
let(:champs) { dossier.champs_public }
|
||||
|
||||
it 'render nested fieldsets, increase heading level for repetition header_section' do
|
||||
expect(page).to have_selector("fieldset")
|
||||
expect(page).to have_selector("legend h2")
|
||||
expect(page).to have_selector("fieldset fieldset")
|
||||
expect(page).to have_selector("fieldset fieldset legend h3")
|
||||
end
|
||||
|
||||
it 'contains as many text champ as repetition.rows' do
|
||||
expect(page).to have_selector("fieldset fieldset input[type=text]", count: dossier.champs_public.find(&:repetition?).rows.size)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,12 +3,12 @@ describe TreeableConcern do
|
|||
include TreeableConcern
|
||||
|
||||
attr_reader :root
|
||||
def initialize(champs:, root_depth:)
|
||||
@root = to_tree(champs:, root_depth:)
|
||||
def initialize(champs:)
|
||||
@root = to_tree(champs:)
|
||||
end
|
||||
end
|
||||
|
||||
subject { ChampsToTree.new(champs: champs, root_depth: 0).root }
|
||||
subject { ChampsToTree.new(champs: champs).root }
|
||||
describe "to_tree" do
|
||||
let(:header_1) { build(:champ_header_section_level_1) }
|
||||
let(:header_1_2) { build(:champ_header_section_level_2) }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue