dsfr-form_builder 0.0.8 → 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 +126 -121
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a0c8e3318e640b14b7afd55c5537fe023032157365a84b21511711f838953f8
4
- data.tar.gz: 0a4bb1ea72d2b0b10adcb0ceeeb3352f53e0085129be6ca9b92b2a5444cd6516
3
+ metadata.gz: 9bd7c63d280b90c223ac24825d70fd3b529c71fb1d242335785235a5a6e7370c
4
+ data.tar.gz: c90dbb0f5479e9401afebcd99e199c0d2c95be725b2333d84f1b2dd03b24bdee
5
5
  SHA512:
6
- metadata.gz: f7c13e694c9ed5e71a2d337fcd144ceae3ab7be5a432a7bd6a23c1f5148edd9529c2000910dcbc597640a6b41b84a99663b176b9c3cc59cc98fde7c2ce903cff
7
- data.tar.gz: '0428eb812019cb50208f820a582f8bdf37786f979cb660bd6b5952e905286142bbaefeffc9f84c61583180bd0c4c21eb3df17af110b06916f81894ba1eeefc04'
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,188 +11,194 @@ 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)
25
- end
26
-
27
- def dsfr_url_field(attribute, opts = {})
28
- dsfr_input_field(attribute, :url_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)
29
22
  end
30
23
 
31
- def dsfr_phone_field(attribute, opts = {})
32
- dsfr_input_field(attribute, :phone_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)
33
31
  end
34
32
 
35
- def dsfr_number_field(attribute, opts = {})
36
- dsfr_input_field(attribute, :number_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
37
37
  end
38
38
 
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)
42
- end
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
+ )
43
49
  end
44
50
 
45
51
  def dsfr_input_field(attribute, input_kind, opts = {})
46
52
  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, :label, :data)),
51
- dsfr_error_message(attribute)
52
- ]
53
- )
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
+ ])
54
58
  end
55
59
  end
56
60
 
57
61
  def dsfr_file_field(attribute, opts = {})
58
- @template.content_tag(:div, class: upload_group_classes(attribute, opts), data: opts[:data]) do
59
- @template.safe_join(
60
- [
61
- dsfr_label_with_hint(attribute, opts.except(:class)),
62
- file_field(attribute, class: "fr-upload", **opts.except(:class, :hint, :label, :data)),
63
- dsfr_error_message(attribute)
64
- ].compact
65
- )
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
+ ])
66
68
  end
67
69
  end
68
70
 
69
71
  def dsfr_check_box(attribute, opts = {}, checked_value = "1", unchecked_value = "0")
70
- @template.content_tag(:div, class: "fr-fieldset__element") do
71
- @template.content_tag(:div, class: "fr-checkbox-group") do
72
- @template.safe_join(
73
- [
74
- check_box(attribute, opts, checked_value, unchecked_value),
75
- dsfr_label_with_hint(attribute, opts)
76
- ]
77
- )
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
+ ])
78
78
  end
79
79
  end
80
80
  end
81
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
+
82
111
  def dsfr_select(attribute, choices, input_options: {}, **opts)
83
- @template.content_tag(:div, class: "fr-select-group") do
84
- @template.safe_join(
85
- [
86
- dsfr_label_with_hint(attribute, opts),
87
- dsfr_select_tag(attribute, choices, **opts, **(input_options)),
88
- dsfr_error_message(attribute)
89
- ]
90
- )
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
+ ])
91
118
  end
92
119
  end
93
120
 
94
121
  def dsfr_select_tag(attribute, choices, opts)
95
- 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)
96
126
  end
97
127
 
98
128
  def dsfr_radio_buttons(attribute, choices, legend: nil, hint: nil, **opts)
99
129
  legend_content = @template.safe_join([
100
130
  legend || @object.class.human_attribute_name(attribute),
101
- hint ? hint_tag(hint) : nil
102
- ].compact)
103
- @template.content_tag(:fieldset, class: "fr-fieldset") do
104
- @template.safe_join(
105
- [
106
- @template.content_tag(:legend, legend_content, class: "fr-fieldset__legend--regular fr-fieldset__legend"),
107
- choices.map { |c| dsfr_radio_option(attribute, value: c[:value], label_text: c[:label], hint: c[:hint], checked: c[:checked], **opts) }
108
- ]
109
- )
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
+ ])
110
147
  end
111
148
  end
112
149
 
113
150
  def dsfr_radio_option(attribute, value:, label_text:, hint:, checked:, rich: false, **opts)
114
- @template.content_tag(:div, class: "fr-fieldset__element") do
115
- classes = rich ? "fr-radio-group fr-radio-rich" : "fr-radio-group"
116
- @template.content_tag(:div, class: classes) do
117
- @template.safe_join(
118
- [
119
- radio_button(attribute, value, checked:, **opts),
120
- label([ attribute, value ].join("_").to_sym) do
121
- @template.safe_join(
122
- [
123
- label_text,
124
- hint.present? ? @template.content_tag(:span, hint, class: "fr-hint-text") : nil
125
- ]
126
- )
127
- end
128
- ]
129
- )
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
+ ])
130
157
  end
131
158
  end
132
159
  end
133
160
 
134
161
  def dsfr_label_with_hint(attribute, opts = {})
135
- label_class = "fr-label #{opts[:class]}"
136
- label(attribute, class: label_class) do
137
- label_and_tags = [ label_value(attribute, opts) ]
138
- label_and_tags.push(required_tag) if opts[:required] && display_required_tags
139
- label_and_tags.push(hint_tag(opts[:hint])) if opts[:hint]
140
-
141
- @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
+ ])
142
168
  end
143
169
  end
144
170
 
145
171
  def required_tag
146
- @template.content_tag(:span, "*", class: "fr-text-error")
172
+ @template.tag.span("*", class: "fr-text-error")
147
173
  end
148
174
 
149
175
  def dsfr_error_message(attr)
150
- return if @object.errors[attr].none?
176
+ return if @object.nil? || @object.errors[attr].none?
151
177
 
152
- @template.content_tag(:p, class: "fr-messages-group") do
178
+ @template.tag.p(class: "fr-messages-group") do
153
179
  safe_join(@object.errors.full_messages_for(attr).map do |msg|
154
- @template.content_tag(:span, msg, class: "fr-message fr-message--error")
180
+ @template.tag.span(msg, class: "fr-message fr-message--error")
155
181
  end)
156
182
  end
157
183
  end
158
184
 
159
185
  def hint_tag(text)
160
- return "" unless text
161
-
162
- @template.content_tag(:span, class: "fr-hint-text") do
163
- text
164
- end
165
-
166
- @template.content_tag(:span, text, class: "fr-hint-text")
186
+ @template.tag.span(text, class: "fr-hint-text") if text
167
187
  end
168
188
 
169
- def join_classes(arr)
170
- arr.compact.join(" ")
171
- end
189
+ def label_value(attribute, opts)
190
+ return opts[:label] if opts[:label]
172
191
 
173
- def input_group_classes(attribute, opts)
174
- join_classes(
175
- [
176
- "fr-input-group",
177
- @object.errors[attribute].any? ? "fr-input-group--error" : nil,
178
- opts[:class]
179
- ]
180
- )
192
+ (@object.try(:object) || @object).class.human_attribute_name(attribute)
181
193
  end
182
194
 
183
- def upload_group_classes(attribute, opts)
184
- join_classes(
185
- [
186
- "fr-upload-group",
187
- @object.errors[attribute].any? ? "fr-upload-group--error" : nil,
188
- opts[:class]
189
- ]
190
- )
191
- end
195
+ private
192
196
 
193
- def label_value(attribute, opts)
194
- return opts[:label] if opts[:label]
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)
195
200
 
196
- (@object.try(:object) || @object).class.human_attribute_name(attribute)
201
+ (@object.send(method) || []).include?(value)
197
202
  end
198
203
  end
199
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.8
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-25 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.13
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)