dsfr-form_builder 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dsfr-form_builder.rb +130 -103
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4e816709049a90277c9e2b5483c9dba3151de298cbe31e31c82c2726b27498d
4
- data.tar.gz: 9f0079eeca19ecc0379603d01388cf6b73235a3aaab2c540e782f89742745692
3
+ metadata.gz: 9bd7c63d280b90c223ac24825d70fd3b529c71fb1d242335785235a5a6e7370c
4
+ data.tar.gz: c90dbb0f5479e9401afebcd99e199c0d2c95be725b2333d84f1b2dd03b24bdee
5
5
  SHA512:
6
- metadata.gz: 27a89a0ce90dfe65b2c69bec7f62bf5b499b2093427fd2bf381b8b30a83107fdfbea4a6f5639e46f10a86918edc6d8a22d11ba72bc1e4154b56eba22beea94dc
7
- data.tar.gz: '00187f9758bdb49d5a70a7a506fea6423a86325076fdde5b3ce6c9123f9eef3e8cebe7b7deb564fce890fec7f1924596f218c21e51209e2221935bc1b43d6b03'
6
+ metadata.gz: f8d706444e7afec231c0bd8cf094be44c9a8ad4b0c5af3149749fca02d9da74b51d13c69b884183ad97fda19d8f847006f4f7501b9df43b5dbf8436c5764397f
7
+ data.tar.gz: e1297220a5fef326d01d1381d70fe85543b9d3371327f25f7d294ac7564ccea48d28d54db4edc747ce199e8765b0fa474ae67ffafa2f340ebcf9d1a2914b33d2
@@ -1,7 +1,6 @@
1
1
  module Dsfr
2
2
  include ActiveSupport::Configurable
3
3
 
4
-
5
4
  class FormBuilder < ActionView::Helpers::FormBuilder
6
5
  include ActionView::Helpers::OutputSafetyHelper
7
6
 
@@ -12,160 +11,179 @@ module Dsfr
12
11
  self.display_required_tags = options.fetch(:display_required_tags, true)
13
12
  end
14
13
 
15
- def dsfr_text_field(attribute, opts = {})
16
- dsfr_input_field(attribute, :text_field, opts)
17
- end
18
-
19
- def dsfr_text_area(attribute, opts = {})
20
- dsfr_input_field(attribute, :text_area, opts)
21
- end
22
-
23
- def dsfr_email_field(attribute, opts = {})
24
- dsfr_input_field(attribute, :email_field, opts)
14
+ def dsfr_button(value = nil, options = {}, &block)
15
+ if block_given?
16
+ options = value || {}
17
+ value = @template.capture { yield(value) }
18
+ end
19
+ options[:type] ||= :button
20
+ options[:class] = @template.class_names("fr-btn", options[:class])
21
+ button(value, options)
25
22
  end
26
23
 
27
- def dsfr_url_field(attribute, opts = {})
28
- dsfr_input_field(attribute, :url_field, opts)
24
+ def dsfr_submit(value = nil, options = {}, &block)
25
+ if block_given?
26
+ options = value || {}
27
+ value = @template.capture { yield(value) }
28
+ end
29
+ options[:type] = :submit
30
+ dsfr_button(value, options)
29
31
  end
30
32
 
31
- def dsfr_phone_field(attribute, opts = {})
32
- dsfr_input_field(attribute, :phone_field, opts)
33
+ %i[text_field text_area email_field url_field phone_field number_field].each do |field_type|
34
+ define_method("dsfr_#{field_type}") do |attribute, **options|
35
+ dsfr_input_field(attribute, field_type, **options)
36
+ end
33
37
  end
34
38
 
35
- def dsfr_number_field(attribute, opts = {})
36
- dsfr_input_field(attribute, :number_field, opts)
39
+ def dsfr_input_group(attribute, opts, kind: :input, &block)
40
+ @template.tag.div(
41
+ yield(block),
42
+ data: opts[:data],
43
+ class: @template.class_names(
44
+ "fr-#{kind}-group",
45
+ opts[:class],
46
+ "fr-#{kind}-group--error" => @object&.errors&.include?(attribute)
47
+ )
48
+ )
37
49
  end
38
50
 
39
- def dsfr_input_group(attribute, opts, &block)
40
- @template.content_tag(:div, class: input_group_classes(attribute, opts), data: opts[:data]) do
41
- yield(block)
51
+ def dsfr_input_field(attribute, input_kind, opts = {})
52
+ dsfr_input_group(attribute, opts) do
53
+ @template.safe_join([
54
+ dsfr_label_with_hint(attribute, opts),
55
+ public_send(input_kind, attribute, class: "fr-input", **opts.except(:class, :hint, :label, :data)),
56
+ dsfr_error_message(attribute)
57
+ ])
42
58
  end
43
59
  end
44
60
 
45
- def dsfr_input_field(attribute, input_kind, opts = {})
46
- dsfr_input_group(attribute, opts) do
47
- @template.safe_join(
48
- [
49
- dsfr_label_with_hint(attribute, opts),
50
- public_send(input_kind, attribute, class: "fr-input", **opts.except(:class, :hint)),
51
- dsfr_error_message(attribute)
52
- ]
53
- )
61
+ def dsfr_file_field(attribute, opts = {})
62
+ dsfr_input_group(attribute, opts, kind: :upload) do
63
+ @template.safe_join([
64
+ dsfr_label_with_hint(attribute, opts.except(:class)),
65
+ file_field(attribute, class: "fr-upload", **opts.except(:class, :hint, :label, :data)),
66
+ dsfr_error_message(attribute)
67
+ ])
54
68
  end
55
69
  end
56
70
 
57
71
  def dsfr_check_box(attribute, opts = {}, checked_value = "1", unchecked_value = "0")
58
- @template.content_tag(:div, class: "fr-fieldset__element") do
59
- @template.content_tag(:div, class: "fr-checkbox-group") do
60
- @template.safe_join(
61
- [
62
- check_box(attribute, opts, checked_value, unchecked_value),
63
- dsfr_label_with_hint(attribute, opts)
64
- ]
65
- )
72
+ @template.tag.div(class: @template.class_names("fr-fieldset__element", "fr-fieldset__element--inline" => opts.delete(:inline))) do
73
+ dsfr_input_group(attribute, opts, kind: :checkbox) do
74
+ @template.safe_join([
75
+ check_box(attribute, opts.except(:label, :hint), checked_value, unchecked_value),
76
+ dsfr_label_with_hint(attribute, opts)
77
+ ])
66
78
  end
67
79
  end
68
80
  end
69
81
 
82
+ def dsfr_collection_check_boxes(method, collection, value_method, text_method, opts = {}, html_options = {})
83
+ legend = opts.delete(:legend) || @object&.class&.human_attribute_name(method)
84
+ if legend.blank?
85
+ raise ArgumentError.new("Please provide the legend option, or use an object whose class responds to :human_attribute_name")
86
+ end
87
+ legend = @template.safe_join([ legend, hint_tag(opts.delete(:hint)) ])
88
+ name = opts.delete(:name) || "#{@object_name}[#{method}][]"
89
+ html_options[:class] = @template.class_names("fr-fieldset", html_options[:class])
90
+ @template.tag.fieldset(**html_options) do
91
+ @template.safe_join([
92
+ @template.tag.legend(legend, class: "fr-fieldset__legend--regular fr-fieldset__legend"),
93
+ @template.hidden_field_tag(name, "", id: nil),
94
+ collection.map do |item|
95
+ value = item.send(value_method)
96
+ checkbox_options = {
97
+ name: name,
98
+ value: value,
99
+ id: field_id(method, value),
100
+ label: item.send(text_method),
101
+ inline: opts[:inline],
102
+ checked: selected?(method, value),
103
+ include_hidden: false
104
+ }
105
+ dsfr_check_box(method, checkbox_options, value, "")
106
+ end
107
+ ])
108
+ end
109
+ end
110
+
70
111
  def dsfr_select(attribute, choices, input_options: {}, **opts)
71
- @template.content_tag(:div, class: "fr-select-group") do
72
- @template.safe_join(
73
- [
74
- dsfr_label_with_hint(attribute, opts),
75
- dsfr_select_tag(attribute, choices, **opts, **(input_options)),
76
- dsfr_error_message(attribute)
77
- ]
78
- )
112
+ dsfr_input_group(attribute, opts, kind: :select) do
113
+ @template.safe_join([
114
+ dsfr_label_with_hint(attribute, opts),
115
+ dsfr_select_tag(attribute, choices, opts.merge(input_options).except(:hint, :name)),
116
+ dsfr_error_message(attribute)
117
+ ])
79
118
  end
80
119
  end
81
120
 
82
121
  def dsfr_select_tag(attribute, choices, opts)
83
- select(attribute, choices, { include_blank: opts[:include_blank] }, class: "fr-select")
122
+ opts[:class] = @template.class_names("fr-select", opts[:class])
123
+ options = opts.slice(:include_blank, :selected, :disabled)
124
+ html_options = opts.except(:include_blank, :selected, :disabled)
125
+ select(attribute, choices, options, **html_options)
84
126
  end
85
127
 
86
128
  def dsfr_radio_buttons(attribute, choices, legend: nil, hint: nil, **opts)
87
129
  legend_content = @template.safe_join([
88
130
  legend || @object.class.human_attribute_name(attribute),
89
- hint ? hint_tag(hint) : nil
90
- ].compact)
91
- @template.content_tag(:fieldset, class: "fr-fieldset") do
92
- @template.safe_join(
93
- [
94
- @template.content_tag(:legend, legend_content, class: "fr-fieldset__legend--regular fr-fieldset__legend"),
95
- choices.map { |c| dsfr_radio_option(attribute, value: c[:value], label_text: c[:label], hint: c[:hint], checked: c[:checked], **opts) }
96
- ]
97
- )
131
+ hint_tag(hint)
132
+ ])
133
+ @template.tag.fieldset(class: "fr-fieldset") do
134
+ @template.safe_join([
135
+ @template.tag.legend(legend_content, class: "fr-fieldset__legend--regular fr-fieldset__legend"),
136
+ choices.map do |choice|
137
+ dsfr_radio_option(
138
+ attribute,
139
+ value: choice[:value],
140
+ label_text: choice[:label],
141
+ hint: choice[:hint],
142
+ checked: choice[:checked],
143
+ **opts
144
+ )
145
+ end
146
+ ])
98
147
  end
99
148
  end
100
149
 
101
150
  def dsfr_radio_option(attribute, value:, label_text:, hint:, checked:, rich: false, **opts)
102
- @template.content_tag(:div, class: "fr-fieldset__element") do
103
- classes = rich ? "fr-radio-group fr-radio-rich" : "fr-radio-group"
104
- @template.content_tag(:div, class: classes) do
105
- @template.safe_join(
106
- [
107
- radio_button(attribute, value, checked:, **opts),
108
- label([ attribute, value ].join("_").to_sym) do
109
- @template.safe_join(
110
- [
111
- label_text,
112
- hint.present? ? @template.content_tag(:span, hint, class: "fr-hint-text") : nil
113
- ]
114
- )
115
- end
116
- ]
117
- )
151
+ @template.tag.div(class: "fr-fieldset__element") do
152
+ @template.tag.div(class: @template.class_names("fr-radio-group", "fr-radio-rich" => rich)) do
153
+ @template.safe_join([
154
+ radio_button(attribute, value, checked:, **opts),
155
+ dsfr_label_with_hint(attribute, opts.merge(label_text: label_text, hint: hint, value: value))
156
+ ])
118
157
  end
119
158
  end
120
159
  end
121
160
 
122
161
  def dsfr_label_with_hint(attribute, opts = {})
123
- label_class = "fr-label #{opts[:class]}"
124
- label(attribute, class: label_class) do
125
- label_and_tags = [ label_value(attribute, opts) ]
126
- label_and_tags.push(required_tag) if opts[:required] && display_required_tags
127
- label_and_tags.push(hint_tag(opts[:hint])) if opts[:hint]
128
-
129
- @template.safe_join(label_and_tags)
162
+ label(attribute, class: @template.class_names("fr-label", opts[:class]), value: opts[:value]) do
163
+ @template.safe_join([
164
+ opts[:label_text] || label_value(attribute, opts),
165
+ (required_tag if opts[:required] && display_required_tags),
166
+ (hint_tag(opts[:hint]) if opts[:hint])
167
+ ])
130
168
  end
131
169
  end
132
170
 
133
171
  def required_tag
134
- @template.content_tag(:span, "*", class: "fr-text-error")
172
+ @template.tag.span("*", class: "fr-text-error")
135
173
  end
136
174
 
137
175
  def dsfr_error_message(attr)
138
- return if @object.errors[attr].none?
176
+ return if @object.nil? || @object.errors[attr].none?
139
177
 
140
- @template.content_tag(:p, class: "fr-messages-group") do
178
+ @template.tag.p(class: "fr-messages-group") do
141
179
  safe_join(@object.errors.full_messages_for(attr).map do |msg|
142
- @template.content_tag(:span, msg, class: "fr-message fr-message--error")
180
+ @template.tag.span(msg, class: "fr-message fr-message--error")
143
181
  end)
144
182
  end
145
183
  end
146
184
 
147
185
  def hint_tag(text)
148
- return "" unless text
149
-
150
- @template.content_tag(:span, class: "fr-hint-text") do
151
- text
152
- end
153
-
154
- @template.content_tag(:span, text, class: "fr-hint-text")
155
- end
156
-
157
- def join_classes(arr)
158
- arr.compact.join(" ")
159
- end
160
-
161
- def input_group_classes(attribute, opts)
162
- join_classes(
163
- [
164
- "fr-input-group",
165
- @object.errors[attribute].any? ? "fr-input-group--error" : nil,
166
- opts[:class]
167
- ]
168
- )
186
+ @template.tag.span(text, class: "fr-hint-text") if text
169
187
  end
170
188
 
171
189
  def label_value(attribute, opts)
@@ -173,5 +191,14 @@ module Dsfr
173
191
 
174
192
  (@object.try(:object) || @object).class.human_attribute_name(attribute)
175
193
  end
194
+
195
+ private
196
+
197
+ # TODO: Allow this helper to be used by select options and radio buttons.
198
+ def selected?(method, value)
199
+ return unless @object.respond_to?(method)
200
+
201
+ (@object.send(method) || []).include?(value)
202
+ end
176
203
  end
177
204
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dsfr-form_builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - BetaGouv developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-10 00:00:00.000000000 Z
11
+ date: 2026-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
104
  - !ruby/object:Gem::Version
105
105
  version: '0'
106
106
  requirements: []
107
- rubygems_version: 3.5.22
107
+ rubygems_version: 3.4.1
108
108
  signing_key:
109
109
  specification_version: 4
110
110
  summary: Ruby on Rails form builder pour le Système de Design de l'État (DSFR)