hungryform 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/Gemfile.lock +9 -1
  4. data/README.md +48 -12
  5. data/Rakefile +3 -1
  6. data/hungryform.gemspec +3 -0
  7. data/lib/hungryform/elements/base/active_element.rb +53 -0
  8. data/lib/hungryform/elements/base/element.rb +57 -0
  9. data/lib/hungryform/elements/base/group.rb +89 -0
  10. data/lib/hungryform/elements/base/hashable.rb +39 -0
  11. data/lib/hungryform/elements/base/options_element.rb +35 -0
  12. data/lib/hungryform/elements/group.rb +5 -3
  13. data/lib/hungryform/elements/html.rb +12 -3
  14. data/lib/hungryform/elements/page.rb +28 -24
  15. data/lib/hungryform/elements/radio_group.rb +17 -0
  16. data/lib/hungryform/elements/select_field.rb +33 -0
  17. data/lib/hungryform/elements/text_area_field.rb +6 -0
  18. data/lib/hungryform/elements/text_field.rb +5 -3
  19. data/lib/hungryform/elements.rb +13 -5
  20. data/lib/hungryform/form.rb +86 -0
  21. data/lib/hungryform/resolver.rb +30 -25
  22. data/lib/hungryform/validator.rb +8 -5
  23. data/lib/hungryform/version.rb +2 -2
  24. data/lib/hungryform.rb +8 -77
  25. data/spec/elements/group_spec.rb +1 -1
  26. data/spec/elements/html_spec.rb +5 -6
  27. data/spec/elements/page_spec.rb +4 -4
  28. data/spec/elements/radio_group_spec.rb +29 -0
  29. data/spec/elements/select_field_spec.rb +47 -0
  30. data/spec/elements/text_area_field_spec.rb +7 -0
  31. data/spec/elements/text_field_spec.rb +2 -2
  32. data/spec/{hungryform_spec.rb → form_spec.rb} +9 -8
  33. data/spec/resolver_spec.rb +1 -1
  34. data/spec/support/shared_active_element.rb +9 -1
  35. data/spec/support/shared_element.rb +9 -1
  36. data/spec/support/shared_group.rb +8 -2
  37. data/spec/support/shared_options_element.rb +36 -0
  38. data/spec/validator_spec.rb +2 -2
  39. metadata +38 -9
  40. data/lib/hungryform/elements/base_active_element.rb +0 -44
  41. data/lib/hungryform/elements/base_element.rb +0 -34
  42. data/lib/hungryform/elements/base_group.rb +0 -76
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c86c2c1ea0c43d499e7e0ace99b385e64f9f907
4
- data.tar.gz: f12a4223b04ba00cf056c3ec9f1ff135b4abb6c5
3
+ metadata.gz: 301fe1318d8aec9203be048d49946604a9c90826
4
+ data.tar.gz: 58eed16ad2bf95ffd8453d6225c32e79234beaf1
5
5
  SHA512:
6
- metadata.gz: 528cf1d7b5809415a13b518526fcef94ab2a48e379e8870b398a582694c230c3222c19ccf603d4f0c51419192a257a123086e552e426a0a92fe862b2e85f8a07
7
- data.tar.gz: 37600c6257d3b50c5b456f8a534c7abde7c14a6024b19aa12d564286684f21f0c847103cf7978865ee38b96e6841f9f0f07a95ed9a3b8c4290d7b06ea85fba0e
6
+ metadata.gz: d4da2b5138ddbbad90956b0bd0fbbbfa885f7ae3d25518951629d8d98c46518d1e81f759f523222e52075c89e69312f6d9417ec5cdd2ff55c2f105c5ef624fb0
7
+ data.tar.gz: 7a443c26c7fe85572cb9a15f70df473f59ccdca5f02aab2ed676ea49f98eebe01cbe428603ea0560ac39edeb73a4e2a7ac24ff82772ee69de471a31c5a7379ca
data/.rspec CHANGED
@@ -1 +1,2 @@
1
+ --format documentation
1
2
  --color
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hungryform (0.0.2)
4
+ hungryform (0.0.4)
5
5
  activesupport
6
6
 
7
7
  GEM
@@ -13,10 +13,16 @@ GEM
13
13
  minitest (~> 5.1)
14
14
  thread_safe (~> 0.1)
15
15
  tzinfo (~> 1.1)
16
+ coderay (1.1.0)
16
17
  diff-lcs (1.2.5)
17
18
  i18n (0.6.11)
18
19
  json (1.8.1)
20
+ method_source (0.8.2)
19
21
  minitest (5.4.2)
22
+ pry (0.9.12.6)
23
+ coderay (~> 1.0)
24
+ method_source (~> 0.8)
25
+ slop (~> 3.4)
20
26
  rake (10.3.2)
21
27
  rspec (3.1.0)
22
28
  rspec-core (~> 3.1.0)
@@ -30,6 +36,7 @@ GEM
30
36
  rspec-mocks (3.1.3)
31
37
  rspec-support (~> 3.1.0)
32
38
  rspec-support (3.1.2)
39
+ slop (3.6.0)
33
40
  thread_safe (0.3.4)
34
41
  tzinfo (1.2.2)
35
42
  thread_safe (~> 0.1)
@@ -40,5 +47,6 @@ PLATFORMS
40
47
  DEPENDENCIES
41
48
  bundler (~> 1.6)
42
49
  hungryform!
50
+ pry
43
51
  rake
44
52
  rspec (~> 3.0)
data/README.md CHANGED
@@ -1,27 +1,28 @@
1
1
  # HungryForm
2
2
 
3
+ [![Build Status](https://travis-ci.org/andrba/hungryform.svg?branch=master)](https://travis-ci.org/andrba/hungryform)
4
+
3
5
  HungryForm is a gem for managing multiple page forms. The main purpose of this gem is to give developers an easy DSL to build complex forms.
4
6
 
5
7
  ## Usage
6
8
 
7
9
  ```ruby
8
- require 'hungryform'
9
-
10
- form = HungryForm.new do
10
+ form = HungryForm::Form.new do
11
11
  page :first do
12
- text_field :first_name
13
- text_field :last_name
12
+ text_field :first_name, required: true
13
+ text_field :last_name, required: true
14
14
  end
15
15
  page :second, do
16
16
  text_field :address
17
+ select_field :gender, options: { "M" => "Male", "F" => "Female" }, required: true
17
18
  end
18
19
  page :third do
19
20
  text_field :occupation
20
21
 
21
22
  # Show this group only when the occupation field is not empty
22
- group :employment_history, visible: false, dependency: '{"SET": "third_occupation"}' do
23
+ group :employment_history, dependency: '{"SET": "third_occupation"}' do
23
24
  html :before, value: "Employment history over the last 5 years"
24
- text_field :history, value: "Default value"
25
+ text_area :history, value: "Default value"
25
26
  end
26
27
  end
27
28
  end
@@ -38,7 +39,7 @@ params = {
38
39
  "third_employment_history_history" => "John's employment hisotory"
39
40
  }
40
41
 
41
- form = HungryForm.new :params => params do
42
+ form = HungryForm::Form.new :params => params do
42
43
  ...
43
44
  end
44
45
 
@@ -85,7 +86,7 @@ Each element of HungryForm, including pages and groups, can have a dependency pa
85
86
  If the dependency is resolved positively it makes the element visible. Otherwise the element will be hidden and not required. It is allowed to use element names or params keys as parameters inside expressions.
86
87
 
87
88
  ```ruby
88
- HungryForm.new do
89
+ HungryForm::Form.new do
89
90
  page :about do
90
91
  text_field :age
91
92
  text_field :favourite_alcohol, required: true, dependency: '{"GT": ["about_age", "18"]}'
@@ -106,11 +107,11 @@ text_field :name, required: true
106
107
  text_field :email, validation: ->(el) { "is unexpected email" unless el.value == "me@yahoo.com" }
107
108
  ```
108
109
 
109
- You can extend the list of validation rules by opening the HungryForm::Validator singleton class and creating your own validation methods:
110
+ You can extend the list of validation rules by creating your own validation methods:
110
111
 
111
112
  ```ruby
112
- class HungryForm
113
- class Validator
113
+ module HungryForm
114
+ module Validator
114
115
  class << self
115
116
  def my_validation_method(element, rule)
116
117
  "is not #{rule}" unless element.value == rule
@@ -123,6 +124,41 @@ end
123
124
  text_field :vegetable, value: "tomato", my_validation_method: "potato" # => is not potato
124
125
  ```
125
126
 
127
+ ## Custom form fields
128
+ You can create your own field type by adding a new class into the HungryForm::Elements module. There are three base classes that you can choose to inherit from:
129
+
130
+ - Base::Element - use this class when you don't need the field to have a value and validation. As an example it can be used for text/html output
131
+ - Base::ActiveElement - use this class when you need the field to have a value and validation
132
+ - Base::OptionsElement - this class inherits the Base::ActiveElement. Use it when you need to create an element with an options hash, like a dropdown
133
+
134
+ ```ruby
135
+ module HungryForm
136
+ module Elements
137
+ class MyField < Base::ActiveElement
138
+ attr_accessor :my_param
139
+
140
+ hashable :my_param
141
+
142
+ def initialize(name, parent, resolver, options = {}, &block)
143
+ self.my_param = options[:my_param] || true
144
+
145
+ super
146
+ end
147
+
148
+ def valid?
149
+ self.value == 'valid_value'
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ form = HungryForm::Form.new do
156
+ page :main do
157
+ my_field :my_field_name, my_param: "Param Value"
158
+ end
159
+ end
160
+ ```
161
+
126
162
  ## Contributing
127
163
 
128
164
  1. Fork it ( https://github.com/andrba/hungryform/fork )
data/Rakefile CHANGED
@@ -1,2 +1,4 @@
1
1
  require "bundler/gem_tasks"
2
-
2
+ require 'rspec/core/rake_task'
3
+ task :default => :spec
4
+ RSpec::Core::RakeTask.new
data/hungryform.gemspec CHANGED
@@ -17,8 +17,11 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
+ spec.required_ruby_version = '>= 1.9.2'
21
+
20
22
  spec.add_development_dependency "bundler", "~> 1.6"
21
23
  spec.add_development_dependency "rake"
22
24
  spec.add_development_dependency "rspec", "~> 3.0"
25
+ spec.add_development_dependency 'pry'
23
26
  spec.add_dependency "activesupport"
24
27
  end
@@ -0,0 +1,53 @@
1
+ module HungryForm
2
+ module Elements::Base
3
+ # The ActiveElement class is used as a base class for all
4
+ # form fields that can contain values and be validated
5
+ class ActiveElement < Element
6
+ attr_accessor :error, :value, :required
7
+ alias_method :required?, :required
8
+
9
+ hashable :required, :value, :error
10
+
11
+ def initialize(name, parent, resolver, attributes = {}, &block)
12
+ super
13
+
14
+ clear_error
15
+
16
+ # Leave only the attributes that are being methods of the HungryForm::Validator class
17
+ @validation_rules = @attributes.select { |key, _| HungryForm::Validator.respond_to?(key) }
18
+
19
+ if parent.visible?
20
+ self.required = @attributes[:required] || false
21
+ else
22
+ self.required = false
23
+ end
24
+
25
+ set_value
26
+ end
27
+
28
+ def valid?
29
+ clear_error
30
+ is_valid = true
31
+ return true unless visible?
32
+
33
+ @validation_rules.each do |key, rule|
34
+ error = HungryForm::Validator.send(key, self, rule) || ''
35
+ unless error.empty?
36
+ is_valid = false
37
+ break
38
+ end
39
+ end
40
+
41
+ is_valid
42
+ end
43
+
44
+ def set_value
45
+ self.value = resolver.params.key?(name) ? resolver.params[name] : @attributes[:value]
46
+ end
47
+
48
+ def clear_error
49
+ self.error = ''
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,57 @@
1
+ module HungryForm
2
+ module Elements::Base
3
+ # The Element class is used in every form element. It contains the attrs
4
+ # and methods used by all form elements, such as name, visible, dependency etc
5
+ class Element
6
+ include HungryForm::Elements::Base::Hashable
7
+ attr_accessor :name, :placeholders, :resolver, :visible, :label, :dependency
8
+ alias_method :visible?, :visible
9
+
10
+ hashable :visible, :dependency, :name, :label
11
+
12
+ def initialize(name, parent, resolver, attributes = {})
13
+ @attributes = attributes.dup
14
+
15
+ @placeholders ||= {}
16
+ @resolver = resolver
17
+
18
+ self.dependency ||= @attributes[:dependency]
19
+
20
+ # The element is visible if no visible parameter passed or
21
+ # visible param equals true and the dependency is resolved positively
22
+ self.visible = @attributes.key?(:visible) ? @attributes[:visible] : true
23
+
24
+ if dependency
25
+ json_dependency = ::JSON.parse(dependency)
26
+ self.visible &&= resolver.resolve_dependency(json_dependency)
27
+ end
28
+
29
+ # An element's name is prefixed with all parents names up to the page
30
+ self.name = resolver.get_value(name, self)
31
+ self.name = "#{parent.name}_#{name}" unless parent.nil?
32
+
33
+ # Label can be created from name if there is no label given
34
+ if @attributes[:label]
35
+ self.label = resolver.get_value(@attributes[:label], self)
36
+ else
37
+ self.label = resolver.get_value(name, self).humanize
38
+ end
39
+ end
40
+
41
+ def method_missing(method_name, *args, &block)
42
+ # Check if an option exists
43
+ if method_name.to_s[-1] == '?'
44
+ return @attributes.key?(method_name.to_s[0..-2].to_sym)
45
+ end
46
+
47
+ # Return an attribute
48
+ return @attributes[method_name] if @attributes.key?(method_name)
49
+ super
50
+ end
51
+
52
+ def respond_to_missing?(method_name, include_private = false)
53
+ @attributes.key?(method_name) || super
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,89 @@
1
+ module HungryForm
2
+ module Elements::Base
3
+ # A base group object is used to handle nested elements.
4
+ # Nested element can be a regular BaseElement or BaseActiveElement,
5
+ # as well as a BaseGroup element.
6
+ #
7
+ # The following sample has three BaseGroup elements (page, group and nested group)
8
+ # that define a structure of a single form page
9
+ #
10
+ # page :about do
11
+ # group :about_yourself do
12
+ # html :about, value: "Tell us about yourself"
13
+ # group :address do
14
+ # text_field :street
15
+ # text_field :city
16
+ # end
17
+ # group :contact do
18
+ # text_field :phone
19
+ # end
20
+ # end
21
+ # end
22
+ class Group < Element
23
+ attr_accessor :elements, :errors
24
+
25
+ hashable :elements
26
+
27
+ def initialize(name, parent, resolver, attributes = {}, &block)
28
+ unless block_given?
29
+ fail HungryFormException, 'No group structure block given'
30
+ end
31
+
32
+ super
33
+
34
+ @elements = []
35
+ @errors = {}
36
+
37
+ instance_eval(&block)
38
+ end
39
+
40
+ # Validates an entire group. If a group consists of nested groups
41
+ # they will be validated recursively
42
+ def valid?
43
+ errors.clear
44
+ is_valid = true
45
+
46
+ elements.each do |el|
47
+ next if el.valid?
48
+
49
+ is_valid = false
50
+ case el
51
+ when BaseActiveElement
52
+ errors[el.name] = el.error
53
+ when BaseGroupObject
54
+ errors.merge!(el.errors)
55
+ end
56
+ end
57
+
58
+ is_valid
59
+ end
60
+
61
+ def invalid?
62
+ !valid?
63
+ end
64
+
65
+ def to_hash
66
+ super.merge(elements: elements.map(&:to_hash))
67
+ end
68
+
69
+ def method_missing(method_name, *args, &block)
70
+ # Find a matching class
71
+ klass = HungryForm::Elements.constants.find { |c| Class === HungryForm::Elements.const_get(c) && c.to_s.underscore.to_sym == method_name }
72
+ return super if klass.nil?
73
+
74
+ # Create a new element based on a symbol provided and push it into the group elements array
75
+ element = HungryForm::Elements.const_get(klass).send(:new, args[0], self, @resolver, *(args[1..-1]), &block)
76
+ elements << element
77
+
78
+ # Resolver keeps a hash of all elements of the form
79
+ @resolver.elements[element.name] = element
80
+
81
+ element
82
+ end
83
+
84
+ def respond_to_missing?(method_name, include_private = false)
85
+ HungryForm::Elements.constants.any? { |c| Class === HungryForm::Elements.const_get(c) && c.to_s.underscore.to_sym == method_name } || super
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,39 @@
1
+ module HungryForm
2
+ module Elements::Base
3
+ # This module adds hashing capabilities to form elements.
4
+ # Do not include this module in your classes. It is already
5
+ # included in the base_element class.
6
+ #
7
+ # Sample usage:
8
+ #
9
+ # class MyField
10
+ # attr_accessor :param1, :param2
11
+ # hashable :param1, :param2
12
+ # ...
13
+ # end
14
+ #
15
+ # Any instance of MyField class will have the "to_hash" method
16
+ # that will contain only the accessor/reader params defined in
17
+ # the hashable macro.
18
+ module Hashable
19
+ def self.included(base)
20
+ base.extend ClassMethods
21
+ base.class_attribute :hashable_attributes
22
+ base.hashable_attributes = []
23
+ end
24
+
25
+ def to_hash
26
+ hash = self.class.hashable_attributes.each_with_object({}) do |param, h|
27
+ h[param] = send(param) unless send(param).nil?
28
+ end
29
+ hash.merge(_type: self.class.name.demodulize)
30
+ end
31
+
32
+ module ClassMethods
33
+ def hashable(*params)
34
+ self.hashable_attributes = hashable_attributes.dup.concat params
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ module HungryForm
2
+ module Elements::Base
3
+ # The BaseOptionsElement class can be used as a base class for
4
+ # fields with options, such as select or radio
5
+ class OptionsElement < ActiveElement
6
+ attr_accessor :options
7
+
8
+ hashable :options
9
+
10
+ def initialize(name, parent, resolver, attributes = {}, &block)
11
+ if attributes.key?(:options)
12
+ self.options = attributes[:options].dup
13
+ else
14
+ fail HungryFormException, "No options provided for #{name}"
15
+ end
16
+
17
+ unless options.is_a?(Hash)
18
+ self.options = resolver.get_value(options, self)
19
+ end
20
+
21
+ super
22
+ end
23
+
24
+ # Sets a value of the element
25
+ # Checks the value from the resolver params against the available options
26
+ def set_value
27
+ if resolver.params.key?(name) && options.key?(resolver.params[name])
28
+ self.value = resolver.params[name]
29
+ else
30
+ self.value = @attributes[:value]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,4 +1,6 @@
1
- class HungryForm
2
- class Group < BaseGroup
1
+ module HungryForm
2
+ module Elements
3
+ class Group < Base::Group
4
+ end
3
5
  end
4
- end
6
+ end
@@ -1,4 +1,13 @@
1
- class HungryForm
2
- class Html < BaseElement
1
+ module HungryForm
2
+ module Elements
3
+ class Html < Base::Element
4
+ attr_accessor :value
5
+ hashable :value
6
+
7
+ def initialize(name, parent, resolver, attributes = {}, &block)
8
+ super
9
+ self.value = @attributes[:value] || ''
10
+ end
11
+ end
3
12
  end
4
- end
13
+ end
@@ -1,27 +1,31 @@
1
- class HungryForm
2
- # Page is a main element of a form.
3
- # Each page can include a page structure, defined in the block
4
- #
5
- # A sample page could look like this:
6
- # page :about do
7
- # html :before, value: "Please fill out the following fields"
8
- # text_field :first_name
9
- # text_field :last_name
10
- # end
11
- #
12
- # The only required argument of a page is its name.
13
- # You can specify a title and a label in the options like this:
14
- #
15
- # page :about, title: "About me", label: "About"
16
- #
17
- # If there is no title or label specified, they will be created
18
- # from the :name argument
19
- class Page < BaseGroup
20
- attr_accessor :title
1
+ module HungryForm
2
+ module Elements
3
+ # Page is a main element of a form.
4
+ # Each page can include a page structure, defined in the block
5
+ #
6
+ # A sample page could look like this:
7
+ # page :about do
8
+ # html :before, value: "Please fill out the following fields"
9
+ # text_field :first_name
10
+ # text_field :last_name
11
+ # end
12
+ #
13
+ # The only required argument of a page is its name.
14
+ # You can specify a title and a label in the attributes like this:
15
+ #
16
+ # page :about, title: "About me", label: "About"
17
+ #
18
+ # If there is no title or label specified, they will be created
19
+ # from the :name argument
20
+ class Page < Base::Group
21
+ attr_accessor :title
21
22
 
22
- def initialize(name, parent, resolver, options = {}, &block)
23
- super
24
- self.title = self.title || self.label
23
+ hashable :title
24
+
25
+ def initialize(name, parent, resolver, attributes = {}, &block)
26
+ super
27
+ self.title ||= label
28
+ end
25
29
  end
26
30
  end
27
- end
31
+ end
@@ -0,0 +1,17 @@
1
+ module HungryForm
2
+ module Elements
3
+ class RadioGroup < Base::OptionsElement
4
+ # Sets a value of the element
5
+ # Checks the value from the resolver params against the available options
6
+ def set_value
7
+ if resolver.params.key?(name)
8
+ if options.keys.include?(resolver.params[name])
9
+ self.value = resolver.params[name]
10
+ end
11
+ else
12
+ self.value = @attributes[:value]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module HungryForm
2
+ module Elements
3
+ class SelectField < Base::OptionsElement
4
+ attr_accessor :multiple
5
+ alias_method :multiple?, :multiple
6
+
7
+ hashable :multiple
8
+
9
+ def initialize(name, parent, resolver, attributes = {}, &block)
10
+ self.multiple = attributes[:multiple] || false
11
+ super
12
+ end
13
+
14
+ # Sets a value of the element
15
+ # Checks the value from the resolver params against the available options
16
+ def set_value
17
+ if resolver.params.key?(name)
18
+
19
+ # Check if all values present in the options
20
+ if multiple?
21
+ acceptable_values = (resolver.params[name] - options.keys).empty?
22
+ else
23
+ acceptable_values = options.keys.include?(resolver.params[name])
24
+ end
25
+
26
+ self.value = resolver.params[name] if acceptable_values
27
+ else
28
+ self.value = @attributes[:value]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ module HungryForm
2
+ module Elements
3
+ class TextAreaField < Base::ActiveElement
4
+ end
5
+ end
6
+ end
@@ -1,4 +1,6 @@
1
- class HungryForm
2
- class TextField < BaseActiveElement
1
+ module HungryForm
2
+ module Elements
3
+ class TextField < Base::ActiveElement
4
+ end
3
5
  end
4
- end
6
+ end
@@ -1,9 +1,17 @@
1
- require_relative 'elements/base_element'
2
- require_relative 'elements/base_active_element'
3
- require_relative 'elements/base_options_element'
4
- require_relative 'elements/base_group'
1
+ module HungryForm
2
+ module Elements
3
+ end
4
+ end
5
+
6
+ require_relative 'elements/base/hashable'
7
+ require_relative 'elements/base/element'
8
+ require_relative 'elements/base/active_element'
9
+ require_relative 'elements/base/options_element'
10
+ require_relative 'elements/base/group'
5
11
  require_relative 'elements/page'
6
12
  require_relative 'elements/group'
7
13
  require_relative 'elements/html'
8
14
  require_relative 'elements/text_field'
9
- require_relative 'elements/select'
15
+ require_relative 'elements/select_field'
16
+ require_relative 'elements/text_area_field'
17
+ require_relative 'elements/radio_group'