govuk_elements_form_builder 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b788800d15d8ae1a07b12e3582f03a1b5a6f5cba
4
+ data.tar.gz: 97aea32625fa53528d7650eb02438e3370855dce
5
+ SHA512:
6
+ metadata.gz: 427bb61c76c85ccdfd06771bd0bd7165591cba1cd3bc5f8d69fb74cf0eb9bf9a7d25793b4fd9f76986625ef66ff55776eb1f446071a529c2713c6df29a3e69ac
7
+ data.tar.gz: b7a125879ee2b133bf44b56c236455826b6b81d534964e9f64ed972272d5e126b91afc13192f79ada47218f4a5e3ed5f12d9de804a351c3a9f47f99e548aeba2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Alistair Laing
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'GovukElementsFormBuilder'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
@@ -0,0 +1,161 @@
1
+ module GovukElementsErrorsHelper
2
+
3
+ class << self
4
+ include ActionView::Context
5
+ include ActionView::Helpers::TagHelper
6
+ end
7
+
8
+ def self.error_summary object, heading, description
9
+ return unless errors_exist? object
10
+ error_summary_div do
11
+ error_summary_heading(heading) +
12
+ error_summary_description(description) +
13
+ error_summary_list(object)
14
+ end
15
+ end
16
+
17
+ def self.errors_exist? object
18
+ errors_present?(object) || child_errors_present?(object)
19
+ end
20
+
21
+ def self.child_errors_present? object
22
+ attributes(object).any? { |child| errors_exist?(child) }
23
+ end
24
+
25
+ def self.attributes object
26
+ child_objects = attribute_objects object
27
+ nested_child_objects = child_objects.map { |o| attributes(o) }
28
+ (child_objects + nested_child_objects).flatten
29
+ end
30
+
31
+ def self.attribute_objects object
32
+ object.
33
+ instance_variables.
34
+ map { |var| instance_variable(object, var) }.
35
+ compact
36
+ end
37
+
38
+ def self.child_to_parent object, parents={}
39
+ attribute_objects(object).each do |child|
40
+ parents[child] = object
41
+ child_to_parent child, parents
42
+ end
43
+ parents
44
+ end
45
+
46
+ def self.instance_variable object, var
47
+ field = var.to_s.sub('@','').to_sym
48
+ object.send(field) if object.respond_to?(field)
49
+ end
50
+
51
+ def self.errors_present? object
52
+ object && object.respond_to?(:errors) && object.errors.present?
53
+ end
54
+
55
+ def self.children_with_errors object
56
+ attributes(object).select { |child| errors_present?(child) }
57
+ end
58
+
59
+ def self.error_summary_div &block
60
+ content_tag(:div,
61
+ class: 'error-summary',
62
+ role: 'group',
63
+ aria: {
64
+ labelledby: 'error-summary-heading'
65
+ },
66
+ tabindex: '-1') do
67
+ yield block
68
+ end
69
+ end
70
+
71
+ def self.error_summary_heading text
72
+ content_tag :h1,
73
+ text,
74
+ id: 'error-summary-heading',
75
+ class: 'heading-medium error-summary-heading'
76
+ end
77
+
78
+ def self.error_summary_description text
79
+ content_tag :p, text
80
+ end
81
+
82
+ def self.error_summary_list object
83
+ content_tag(:ul, class: 'error-summary-list') do
84
+ child_to_parents = child_to_parent(object)
85
+ messages = error_summary_messages(object, child_to_parents)
86
+
87
+ messages << children_with_errors(object).map do |child|
88
+ error_summary_messages(child, child_to_parents)
89
+ end
90
+
91
+ messages.flatten.join('').html_safe
92
+ end
93
+ end
94
+
95
+ def self.error_summary_messages object, child_to_parents
96
+ object.errors.keys.map do |attribute|
97
+ error_summary_message object, attribute, child_to_parents
98
+ end
99
+ end
100
+
101
+ def self.error_summary_message object, attribute, child_to_parents
102
+ messages = object.errors.full_messages_for attribute
103
+ messages.map do |message|
104
+ object_prefixes = object_prefixes object, child_to_parents
105
+ link = link_to_error(object_prefixes, attribute)
106
+ message.sub! default_label(attribute), localized_label(object_prefixes, attribute)
107
+ content_tag(:li, content_tag(:a, message, href: link))
108
+ end
109
+ end
110
+
111
+ def self.link_to_error object_prefixes, attribute
112
+ ['#error', *object_prefixes, attribute].join('_')
113
+ end
114
+
115
+ def self.default_label attribute
116
+ attribute.to_s.humanize.capitalize
117
+ end
118
+
119
+ def self.localized_label object_prefixes, attribute
120
+ object_key = object_prefixes.shift
121
+ object_prefixes.each { |prefix| object_key += "[#{prefix}]" }
122
+ key = "#{object_key}.#{attribute}"
123
+ I18n.t(key,
124
+ default: default_label(attribute),
125
+ scope: 'helpers.label').presence
126
+ end
127
+
128
+ def self.parents_list object, child_to_parents
129
+ if parent = child_to_parents[object]
130
+ [].tap do |parents|
131
+ while parent
132
+ parents.unshift parent
133
+ parent = child_to_parents[parent]
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ def self.object_prefixes object, child_to_parents
140
+ parents = parents_list object, child_to_parents
141
+
142
+ if parents.present?
143
+ root = parents.shift
144
+ prefixes = [underscore_name(root)]
145
+ parents.each { |p| prefixes << "#{underscore_name p}_attributes" }
146
+ prefixes << "#{underscore_name object}_attributes"
147
+ else
148
+ prefixes = [underscore_name(object)]
149
+ end
150
+ end
151
+
152
+ def self.underscore_name object
153
+ object.class.name.underscore
154
+ end
155
+
156
+ private_class_method :error_summary_div
157
+ private_class_method :error_summary_heading
158
+ private_class_method :error_summary_description
159
+ private_class_method :error_summary_messages
160
+
161
+ end
@@ -0,0 +1,6 @@
1
+ require 'govuk_elements_form_builder/version'
2
+ require 'govuk_elements_form_builder/form_builder'
3
+ require_relative '../app/helpers/govuk_elements_errors_helper'
4
+
5
+ module GovukElementsFormBuilder
6
+ end
@@ -0,0 +1,267 @@
1
+ module GovukElementsFormBuilder
2
+ class FormBuilder < ActionView::Helpers::FormBuilder
3
+
4
+ ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
5
+ add_error_to_html_tag! html_tag, instance
6
+ end
7
+
8
+ delegate :content_tag, :tag, :safe_join, to: :@template
9
+ delegate :errors, to: :@object
10
+
11
+ # Ensure fields_for yields a GovukElementsFormBuilder.
12
+ def fields_for record_name, record_object = nil, fields_options = {}, &block
13
+ super record_name, record_object, fields_options.merge(builder: self.class), &block
14
+ end
15
+
16
+ %i[
17
+ email_field
18
+ password_field
19
+ number_field
20
+ phone_field
21
+ range_field
22
+ search_field
23
+ telephone_field
24
+ text_area
25
+ text_field
26
+ url_field
27
+ ].each do |method_name|
28
+ define_method(method_name) do |attribute, *args|
29
+ content_tag :div, class: form_group_classes(attribute), id: form_group_id(attribute) do
30
+ options = args.extract_options!
31
+ set_field_classes! options
32
+
33
+ label = label(attribute, class: "form-label")
34
+ add_hint :label, label, attribute
35
+ (label + super(attribute, options.except(:label)) ).html_safe
36
+ end
37
+ end
38
+ end
39
+
40
+ def radio_button_fieldset attribute, options={}
41
+ content_tag :div,
42
+ class: form_group_classes(attribute),
43
+ id: form_group_id(attribute) do
44
+ content_tag :fieldset, fieldset_options(attribute, options) do
45
+ safe_join([
46
+ fieldset_legend(attribute),
47
+ radio_inputs(attribute, options)
48
+ ], "\n")
49
+ end
50
+ end
51
+ end
52
+
53
+ def check_box_fieldset legend_key, attributes, options={}
54
+ content_tag :div,
55
+ class: form_group_classes(attributes),
56
+ id: form_group_id(attributes) do
57
+ content_tag :fieldset, fieldset_options(attributes, options) do
58
+ safe_join([
59
+ fieldset_legend(legend_key),
60
+ check_box_inputs(attributes)
61
+ ], "\n")
62
+ end
63
+ end
64
+ end
65
+
66
+ def collection_select method, collection, value_method, text_method, options = {}, *args
67
+
68
+ content_tag :div, class: form_group_classes(method), id: form_group_id(method) do
69
+
70
+ html_options = args.extract_options!
71
+ set_field_classes! html_options
72
+
73
+ label = label(method, class: "form-label")
74
+ add_hint :label, label, method
75
+
76
+ (label+ super(method, collection, value_method, text_method, options , html_options)).html_safe
77
+ end
78
+
79
+ end
80
+
81
+ private
82
+
83
+ def set_field_classes! options
84
+ text_field_class = "form-control"
85
+ options[:class] = case options[:class]
86
+ when String
87
+ [text_field_class, options[:class]]
88
+ when Array
89
+ options[:class].unshift text_field_class
90
+ else
91
+ options[:class] = text_field_class
92
+ end
93
+ end
94
+
95
+ def check_box_inputs attributes
96
+ attributes.map do |attribute|
97
+ label(attribute, class: 'block-label selection-button-checkbox') do |tag|
98
+ input = check_box(attribute)
99
+ input + localized_label("#{attribute}")
100
+ end
101
+ end
102
+ end
103
+
104
+ def radio_inputs attribute, options
105
+ choices = options[:choices] || [ :yes, :no ]
106
+ choices.map do |choice|
107
+ label(attribute, class: 'block-label selection-button-radio', value: choice) do |tag|
108
+ input = radio_button(attribute, choice)
109
+ input + localized_label("#{attribute}.#{choice}")
110
+ end
111
+ end
112
+ end
113
+
114
+ def fieldset_legend attribute
115
+ legend = content_tag(:legend) do
116
+ tags = [content_tag(
117
+ :span,
118
+ fieldset_text(attribute),
119
+ class: 'form-label-bold'
120
+ )]
121
+
122
+ if error_for? attribute
123
+ tags << content_tag(
124
+ :span,
125
+ error_full_message_for(attribute),
126
+ class: 'error-message'
127
+ )
128
+ end
129
+
130
+ hint = hint_text attribute
131
+ tags << content_tag(:span, hint, class: 'form-hint') if hint
132
+
133
+ safe_join tags
134
+ end
135
+ legend.html_safe
136
+ end
137
+
138
+ def fieldset_options attributes, options
139
+ fieldset_options = {}
140
+ fieldset_options[:class] = 'inline' if options[:inline] == true
141
+ fieldset_options
142
+ end
143
+
144
+ private_class_method def self.add_error_to_html_tag! html_tag, instance
145
+ object_name = instance.instance_variable_get(:@object_name)
146
+ object = instance.instance_variable_get(:@object)
147
+
148
+ case html_tag
149
+ when /^<label/
150
+ add_error_to_label! html_tag, object_name, object
151
+ when /^<input/
152
+ add_error_to_input! html_tag, 'input'
153
+ when /^<textarea/
154
+ add_error_to_input! html_tag, 'textarea'
155
+ else
156
+ html_tag
157
+ end
158
+ end
159
+
160
+ def self.attribute_prefix object_name
161
+ object_name.to_s.tr('[]','_').squeeze('_').chomp('_')
162
+ end
163
+
164
+ def attribute_prefix
165
+ self.class.attribute_prefix(@object_name)
166
+ end
167
+
168
+ def form_group_id attribute
169
+ "error_#{attribute_prefix}_#{attribute}" if error_for? attribute
170
+ end
171
+
172
+ private_class_method def self.add_error_to_label! html_tag, object_name, object
173
+ field = html_tag[/for="([^"]+)"/, 1]
174
+ object_attribute = object_attribute_for field, object_name
175
+ message = error_full_message_for object_attribute, object_name, object
176
+ if message
177
+ html_tag.sub(
178
+ '</label',
179
+ %Q{<span class="error-message" id="error_message_#{field}">#{message}</span></label}
180
+ ).html_safe # sub() returns a String, not a SafeBuffer
181
+ else
182
+ html_tag
183
+ end
184
+ end
185
+
186
+ private_class_method def self.add_error_to_input! html_tag, element
187
+ field = html_tag[/id="([^"]+)"/, 1]
188
+ html_tag.sub(
189
+ element,
190
+ %Q{#{element} aria-describedby="error_message_#{field}"}
191
+ ).html_safe # sub() returns a String, not a SafeBuffer
192
+ end
193
+
194
+ def form_group_classes attributes
195
+ attributes = [attributes] if !attributes.respond_to? :count
196
+ classes = 'form-group'
197
+ classes += ' error' if attributes.find { |a| error_for? a }
198
+ classes
199
+ end
200
+
201
+ def self.error_full_message_for attribute, object_name, object
202
+ message = object.errors.full_messages_for(attribute).first
203
+ message&.sub default_label(attribute), localized_label(attribute, object_name)
204
+ end
205
+
206
+ def error_full_message_for attribute
207
+ self.class.error_full_message_for attribute, @object_name, @object
208
+ end
209
+
210
+ def error_for? attribute
211
+ errors.messages.key?(attribute) && !errors.messages[attribute].empty?
212
+ end
213
+
214
+ private_class_method def self.object_attribute_for field, object_name
215
+ field.to_s.
216
+ sub("#{attribute_prefix(object_name)}_", '').
217
+ to_sym
218
+ end
219
+
220
+ def add_hint tag, element, name
221
+ if hint = hint_text(name)
222
+ hint_span = content_tag(:span, hint, class: 'form-hint')
223
+ element.sub!("</#{tag}>", "#{hint_span}</#{tag}>".html_safe)
224
+ end
225
+ end
226
+
227
+ def fieldset_text attribute
228
+ localized 'helpers.fieldset', attribute, default_label(attribute)
229
+ end
230
+
231
+ def hint_text attribute
232
+ localized 'helpers.hint', attribute, ''
233
+ end
234
+
235
+ def self.default_label attribute
236
+ attribute.to_s.split('.').last.humanize.capitalize
237
+ end
238
+
239
+ def default_label attribute
240
+ self.class.default_label attribute
241
+ end
242
+
243
+ def self.localized_label attribute, object_name
244
+ localized 'helpers.label', attribute, default_label(attribute), object_name
245
+ end
246
+
247
+ def localized_label attribute
248
+ self.class.localized_label attribute, @object_name
249
+ end
250
+
251
+ def self.localized scope, attribute, default, object_name
252
+ key = "#{object_name}.#{attribute}"
253
+ translate key, default, scope
254
+ end
255
+
256
+ def self.translate key, default, scope
257
+ # Passes blank String as default because nil is interpreted as no default
258
+ I18n.translate(key, default: '', scope: scope).presence ||
259
+ I18n.translate("#{key}_html", default: default, scope: scope).html_safe.presence
260
+ end
261
+
262
+ def localized scope, attribute, default
263
+ self.class.localized scope, attribute, default, @object_name
264
+ end
265
+
266
+ end
267
+ end
@@ -0,0 +1,3 @@
1
+ module GovukElementsFormBuilder
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :govuk_elements_form_builder do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: govuk_elements_form_builder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Alistair Laing
8
+ - Rob McKinnon
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-02-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '4.2'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '4.2'
28
+ - !ruby/object:Gem::Dependency
29
+ name: sqlite3
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec-rails
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: guard-rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec-html-matchers
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: htmlbeautifier
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: codeclimate-test-reporter
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: byebug
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Custom Ruby on Rails form builder that generates GOV.UK elements styled
127
+ markup for form inputs, including error validation messages.
128
+ email:
129
+ - Alistair.Laing@Digital.Justice.gov.uk
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - MIT-LICENSE
135
+ - Rakefile
136
+ - app/helpers/govuk_elements_errors_helper.rb
137
+ - lib/govuk_elements_form_builder.rb
138
+ - lib/govuk_elements_form_builder/components/error_summary.rb
139
+ - lib/govuk_elements_form_builder/form_builder.rb
140
+ - lib/govuk_elements_form_builder/version.rb
141
+ - lib/tasks/govuk_elements_form_builder_tasks.rake
142
+ homepage: https://github.com/ministryofjustice/govuk_elements_form_builder
143
+ licenses:
144
+ - MIT
145
+ metadata: {}
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubyforge_project:
162
+ rubygems_version: 2.6.4
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: Ruby on Rails form builder that generates GOV.UK elements styled markup for
166
+ forms.
167
+ test_files: []