dynamic_fieldsets 0.0.2
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.
- data/CHANGELOG +4 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +157 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +69 -0
- data/VERSION +1 -0
- data/app/controllers/dynamic_fieldsets/fields_controller.rb +75 -0
- data/app/controllers/dynamic_fieldsets/fieldset_associators_controller.rb +24 -0
- data/app/controllers/dynamic_fieldsets/fieldsets_controller.rb +89 -0
- data/app/helpers/dynamic_fieldsets/fields_helper.rb +19 -0
- data/app/helpers/dynamic_fieldsets_helper.rb +207 -0
- data/app/models/dynamic_fieldsets/field.rb +85 -0
- data/app/models/dynamic_fieldsets/field_default.rb +14 -0
- data/app/models/dynamic_fieldsets/field_html_attribute.rb +15 -0
- data/app/models/dynamic_fieldsets/field_option.rb +18 -0
- data/app/models/dynamic_fieldsets/field_record.rb +11 -0
- data/app/models/dynamic_fieldsets/fieldset.rb +57 -0
- data/app/models/dynamic_fieldsets/fieldset_associator.rb +76 -0
- data/app/views/dynamic_fieldsets/fields/_field_default_fields.html.erb +7 -0
- data/app/views/dynamic_fieldsets/fields/_field_html_attribute_fields.html.erb +8 -0
- data/app/views/dynamic_fieldsets/fields/_field_option_fields.html.erb +6 -0
- data/app/views/dynamic_fieldsets/fields/_form.html.erb +81 -0
- data/app/views/dynamic_fieldsets/fields/edit.html.erb +6 -0
- data/app/views/dynamic_fieldsets/fields/index.html.erb +29 -0
- data/app/views/dynamic_fieldsets/fields/new.html.erb +5 -0
- data/app/views/dynamic_fieldsets/fields/show.html.erb +70 -0
- data/app/views/dynamic_fieldsets/fieldset_associators/index.html.erb +18 -0
- data/app/views/dynamic_fieldsets/fieldset_associators/show.html.erb +26 -0
- data/app/views/dynamic_fieldsets/fieldsets/_form.html.erb +37 -0
- data/app/views/dynamic_fieldsets/fieldsets/children.html.erb +45 -0
- data/app/views/dynamic_fieldsets/fieldsets/edit.html.erb +6 -0
- data/app/views/dynamic_fieldsets/fieldsets/index.html.erb +31 -0
- data/app/views/dynamic_fieldsets/fieldsets/new.html.erb +5 -0
- data/app/views/dynamic_fieldsets/fieldsets/show.html.erb +31 -0
- data/config/routes.rb +9 -0
- data/dynamic_fieldsets.gemspec +195 -0
- data/lib/dynamic_fieldsets/config.rb +0 -0
- data/lib/dynamic_fieldsets/dynamic_fieldsets_in_model.rb +164 -0
- data/lib/dynamic_fieldsets/engine.rb +4 -0
- data/lib/dynamic_fieldsets/railtie.rb +13 -0
- data/lib/dynamic_fieldsets.rb +2 -0
- data/lib/generators/dynamic_fieldsets/install_generator.rb +44 -0
- data/lib/generators/dynamic_fieldsets/templates/config.rb +0 -0
- data/lib/generators/dynamic_fieldsets/templates/migrations/install_migration.rb +74 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/information_forms_controller.rb +85 -0
- data/spec/dummy/app/helpers/application_helper.rb +5 -0
- data/spec/dummy/app/helpers/information_forms_helper.rb +2 -0
- data/spec/dummy/app/models/information_form.rb +3 -0
- data/spec/dummy/app/views/information_forms/_form.html.erb +22 -0
- data/spec/dummy/app/views/information_forms/edit.html.erb +6 -0
- data/spec/dummy/app/views/information_forms/index.html.erb +23 -0
- data/spec/dummy/app/views/information_forms/new.html.erb +5 -0
- data/spec/dummy/app/views/information_forms/show.html.erb +11 -0
- data/spec/dummy/app/views/layouts/application.html.erb +15 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +35 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +60 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20110726215814_create_dynamic_fieldsets_tables.rb +74 -0
- data/spec/dummy/db/migrate/20110727210451_create_information_forms.rb +13 -0
- data/spec/dummy/db/schema.rb +83 -0
- data/spec/dummy/features/field.feature +54 -0
- data/spec/dummy/features/fieldset.feature +68 -0
- data/spec/dummy/features/fieldset_associator.feature +20 -0
- data/spec/dummy/features/step_definitions/debugging_steps.rb +8 -0
- data/spec/dummy/features/step_definitions/field_steps.rb +63 -0
- data/spec/dummy/features/step_definitions/fieldset_associator_steps.rb +11 -0
- data/spec/dummy/features/step_definitions/fieldset_steps.rb +57 -0
- data/spec/dummy/features/step_definitions/web_steps.rb +214 -0
- data/spec/dummy/features/support/env.rb +15 -0
- data/spec/dummy/features/support/paths.rb +46 -0
- data/spec/dummy/features/support/selectors.rb +39 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/jquery.min.js +166 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/public/stylesheets/scaffold.css +56 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dynamic_fieldsets_helper_spec.rb +254 -0
- data/spec/dynamic_fieldsets_in_model_spec.rb +175 -0
- data/spec/dynamic_fieldsets_spec.rb +7 -0
- data/spec/integration/navigation_spec.rb +9 -0
- data/spec/models/field_default_spec.rb +26 -0
- data/spec/models/field_html_attribute_spec.rb +30 -0
- data/spec/models/field_option_spec.rb +52 -0
- data/spec/models/field_record_spec.rb +44 -0
- data/spec/models/field_spec.rb +169 -0
- data/spec/models/fieldset_associator_spec.rb +128 -0
- data/spec/models/fieldset_spec.rb +169 -0
- data/spec/reports/SPEC-ActsAsMultipartForm.xml +9 -0
- data/spec/reports/SPEC-MultipartForm-InProgressForm-validations.xml +47 -0
- data/spec/reports/SPEC-MultipartForm-InProgressForm.xml +7 -0
- data/spec/reports/SPEC-Navigation.xml +9 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/field_default_helper.rb +12 -0
- data/spec/support/field_helper.rb +16 -0
- data/spec/support/field_html_attribute_helper.rb +14 -0
- data/spec/support/field_option_helper.rb +13 -0
- data/spec/support/field_record_helper.rb +12 -0
- data/spec/support/fieldset_associator_helper.rb +12 -0
- data/spec/support/fieldset_helper.rb +28 -0
- metadata +328 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
module DynamicFieldsetsHelper
|
|
2
|
+
include ActionView::Helpers
|
|
3
|
+
|
|
4
|
+
# Builds HTML for the provided field.
|
|
5
|
+
# @param [FieldsetAssociator] fsa parent FieldsetAssociator
|
|
6
|
+
# @param [Field] field The Field to render
|
|
7
|
+
# @param [Array] values Saved values for the field
|
|
8
|
+
# @return [Array] The HTML elements for the field
|
|
9
|
+
def field_renderer(fsa, field, values = [], form_type)
|
|
10
|
+
if form_type == "form"
|
|
11
|
+
return field_form_renderer(fsa, field, values)
|
|
12
|
+
else
|
|
13
|
+
return field_show_renderer(fsa, field, values)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Builds HTML for the provided field for a show page.
|
|
19
|
+
# @param [FieldsetAssociator] fsa parent FieldsetAssociator
|
|
20
|
+
# @param [Field] field The Field to render
|
|
21
|
+
# @param [Array] values Saved values for the field
|
|
22
|
+
# @return [Array] The HTML elements for the field
|
|
23
|
+
def field_show_renderer(fsa, field, values = [])
|
|
24
|
+
lines = []
|
|
25
|
+
lines.push "<div class='dynamic_fieldsets_field'>"
|
|
26
|
+
lines.push "<div class='dynamic_fieldsets_field_label'>#{field.label}</div>"
|
|
27
|
+
lines.push "<div class='dynamic_fieldsets_field_value'>"
|
|
28
|
+
if values
|
|
29
|
+
if field.field_type == "multiple_select" || field.field_type == "checkboxes"
|
|
30
|
+
values.each do |value|
|
|
31
|
+
lines.push value.to_s + "<br />"
|
|
32
|
+
end
|
|
33
|
+
elsif field.field_type == "select" || field.field_type == "radio"
|
|
34
|
+
lines.push values.to_s
|
|
35
|
+
else
|
|
36
|
+
lines.push values
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
lines.push "No answer given"
|
|
40
|
+
end
|
|
41
|
+
lines.push "</div>"
|
|
42
|
+
return lines
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Builds HTML for the provided field for a form.
|
|
47
|
+
# @param [FieldsetAssociator] fsa parent FieldsetAssociator
|
|
48
|
+
# @param [Field] field The Field to render
|
|
49
|
+
# @param [Array] values Saved values for the field
|
|
50
|
+
# @return [Array] The HTML elements for the field
|
|
51
|
+
def field_form_renderer(fsa, field, values = [])
|
|
52
|
+
classes = "#{field.field_type} "
|
|
53
|
+
classes += ( field.required ? 'required' : 'optional' )
|
|
54
|
+
|
|
55
|
+
field_markup = ["<li class='#{classes}' id='field-input-#{field.id}'>"]
|
|
56
|
+
field_markup.push "<label for='field-#{field.id}'>"
|
|
57
|
+
field_markup.push "#{field.label}"
|
|
58
|
+
field_markup.push "<abbr title='required'>*</abbr>" if field.required?
|
|
59
|
+
field_markup.push "</label>"
|
|
60
|
+
|
|
61
|
+
attrs = { :id => "field-#{field.id}" }
|
|
62
|
+
field.field_html_attributes.each{ |a| attrs.merge({ a.attribute_name.to_sym => a.value}) } if !field.field_html_attributes.empty?
|
|
63
|
+
|
|
64
|
+
case field.field_type.to_sym
|
|
65
|
+
when :select
|
|
66
|
+
selected = populate(field,values).to_i # should return the ID of the saved or default option
|
|
67
|
+
field_markup.push select_tag "fsa-#{fsa.id}[field-#{field.id}]", options_from_collection_for_select( field.options, :id, :name, selected ), attrs
|
|
68
|
+
|
|
69
|
+
when :multiple_select
|
|
70
|
+
attrs.merge! multiple: 'multiple'
|
|
71
|
+
opts = populate( field, values )
|
|
72
|
+
opts = [opts] if !opts.is_a? Array
|
|
73
|
+
selected = opts.map( &:to_i ) if !opts.empty? # array of option IDs, saved > default
|
|
74
|
+
field_markup.push select_tag "fsa-#{fsa.id}[field-#{field.id}]", options_from_collection_for_select( field.options, :id, :name, selected ), attrs
|
|
75
|
+
|
|
76
|
+
when :radio
|
|
77
|
+
field_markup.push "<div id='field-#{field.id}'>"
|
|
78
|
+
field.options.each do |option|
|
|
79
|
+
attrs[:id] = "field-#{field.id}-#{option.name.parameterize}"
|
|
80
|
+
these_attrs = attrs
|
|
81
|
+
these_attrs = attrs.merge checked: true if populate(field,values).to_i.eql? option.id
|
|
82
|
+
field_markup.push "<label for='#{attrs[:id]}'>"
|
|
83
|
+
field_markup.push radio_button "fsa-#{fsa.id}", "field-#{field.id}", option.id, these_attrs
|
|
84
|
+
field_markup.push "#{option.name}"
|
|
85
|
+
field_markup.push "</label>"
|
|
86
|
+
end
|
|
87
|
+
field_markup.push "</div>"
|
|
88
|
+
|
|
89
|
+
when :checkbox
|
|
90
|
+
field_markup.push "<div id='field-#{field.id}'>"
|
|
91
|
+
opts = populate( field, values )
|
|
92
|
+
checked = []
|
|
93
|
+
checked = opts.map( &:to_i ) if !opts.empty? # array of option IDs, saved > default
|
|
94
|
+
field.options.each do |option|
|
|
95
|
+
attrs[:id] = "field-#{field.id}-#{option.name.underscore}"
|
|
96
|
+
attrs.merge! checked: true if checked.include? option.id
|
|
97
|
+
field_markup.push "<label for='#{attrs[:id]}'>"
|
|
98
|
+
field_markup.push check_box "fsa-#{fsa.id}", "field-#{field.id}", attrs
|
|
99
|
+
field_markup.push "#{option.name}"
|
|
100
|
+
field_markup.push "</label>"
|
|
101
|
+
end
|
|
102
|
+
field_markup.push "</div>"
|
|
103
|
+
|
|
104
|
+
when :textfield
|
|
105
|
+
attrs.merge!( {:value => populate( field, values )} )
|
|
106
|
+
field_markup.push text_field "fsa-#{fsa.id}", "field-#{field.id}", attrs
|
|
107
|
+
|
|
108
|
+
when :textarea
|
|
109
|
+
attrs.merge! cols: '40' if !attrs.include? :cols
|
|
110
|
+
attrs.merge! rows: '20' if !attrs.include? :rows
|
|
111
|
+
attrs.merge! name: "fsa-#{fsa.id}[field-#{field.id}]"
|
|
112
|
+
field_markup.push "<textarea>"
|
|
113
|
+
# attrs ...
|
|
114
|
+
field_markup.push populate( field, values )
|
|
115
|
+
field_markup.push "</textarea>"
|
|
116
|
+
|
|
117
|
+
when :date
|
|
118
|
+
date_options = { date_separator: '/',
|
|
119
|
+
add_month_numbers: true,
|
|
120
|
+
start_year: Time.now.year - 70 }
|
|
121
|
+
setdate = populate( field, values ) # date string if saved or default
|
|
122
|
+
date_options.merge! default: Time.parse( setdate ) if !setdate.empty?
|
|
123
|
+
# attrs.reject!{ |k| k.eql? :id }
|
|
124
|
+
field_markup.push date_select "fsa-#{fsa.id}", "field-#{field.id}", date_options, attrs
|
|
125
|
+
|
|
126
|
+
when :datetime
|
|
127
|
+
date_options = { add_month_numbers: true,
|
|
128
|
+
start_year: Time.now.year - 70 }
|
|
129
|
+
setdate = populate( field, values ) # datetime string if saved or default
|
|
130
|
+
date_options.merge! default: Time.parse( setdate ) if !setdate.empty?
|
|
131
|
+
# attrs.reject!{ |k| k.eql? :id }
|
|
132
|
+
field_markup.push datetime_select "fsa-#{fsa.id}", "field-#{field.id}", date_options, attrs
|
|
133
|
+
|
|
134
|
+
when :instruction
|
|
135
|
+
field_markup.push "<p>#{field.label}</p>"
|
|
136
|
+
|
|
137
|
+
end # case field.field_type
|
|
138
|
+
|
|
139
|
+
field_markup.push "</li>"
|
|
140
|
+
return field_markup
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Builds HTML for the provided fieldset and its children.
|
|
144
|
+
# @param [FieldsetAssociator] fsa parent FieldsetAssociator
|
|
145
|
+
# @param [Field] fieldset The Fieldset to render
|
|
146
|
+
# @param [Hash] values Stored values for the fieldset
|
|
147
|
+
# @return [Array] The HTML elements for the fieldset
|
|
148
|
+
def fieldset_renderer(fsa, fieldset, values, form_type)
|
|
149
|
+
lines = ["<div id='fieldset-#{fieldset.id}' class='inputs'>"]
|
|
150
|
+
lines.push "<ol>"
|
|
151
|
+
fieldset.children.each do |child|
|
|
152
|
+
if child.is_a? DynamicFieldsets::Field then
|
|
153
|
+
lines += field_renderer( fsa, child, values[child.id], form_type )
|
|
154
|
+
else # child.is_a? Fieldset
|
|
155
|
+
lines += fieldset_renderer( fsa, child, values, form_type )
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
lines.push "</ol>"
|
|
159
|
+
lines.push "</div>"
|
|
160
|
+
return lines
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Build HTML for a specific dynamic fieldset on a show page
|
|
164
|
+
# @param [FieldsetAssociator] The fieldset associator for the dynamic fieldset to render
|
|
165
|
+
# @return [String] The HTML for the entire dynamic fieldset
|
|
166
|
+
def dynamic_fieldset_show_renderer(fsa)
|
|
167
|
+
return dynamic_fieldset_renderer(fsa, "show")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Build HTML for a specific dynamic fieldset on a form page
|
|
171
|
+
# @param [FieldsetAssociator] The fieldset associator for the dynamic fieldset to render
|
|
172
|
+
# @return [String] The HTML for the entire dynamic fieldset
|
|
173
|
+
def dynamic_fieldset_form_renderer(fsa)
|
|
174
|
+
return dynamic_fieldset_renderer(fsa, "form")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Builds HTML for a specific dynamic fieldset in a form.
|
|
178
|
+
# @param [FieldsetAssociator] The fieldset associator for the dynamic fieldset to render
|
|
179
|
+
# @return [String] The HTML for the entire dynamic fieldset
|
|
180
|
+
def dynamic_fieldset_renderer(fsa, form_type)
|
|
181
|
+
rendered_dynamic_fieldset = ""
|
|
182
|
+
fieldset_renderer( fsa, fsa.fieldset, fsa.field_values, form_type ).each do |line|
|
|
183
|
+
rendered_dynamic_fieldset += line + "\n"
|
|
184
|
+
end
|
|
185
|
+
return rendered_dynamic_fieldset.html_safe
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Gives precedence to saved values; returns default values if empty
|
|
189
|
+
# @param [Field] field Field to populate
|
|
190
|
+
# @param [String] value Possibly saved values
|
|
191
|
+
# @return The saved or default value(s)
|
|
192
|
+
# I know this is messy; this is what happens when we are past deadline.
|
|
193
|
+
def populate(field, value)
|
|
194
|
+
if value.nil? || (value.is_a?(Array) && value.empty?)
|
|
195
|
+
if field.field_defaults.length == 0
|
|
196
|
+
return ""
|
|
197
|
+
elsif field.field_defaults.length > 1
|
|
198
|
+
return field.field_defaults.collect{ |d| d[:value] }
|
|
199
|
+
else
|
|
200
|
+
return field.field_defaults.first.value
|
|
201
|
+
end
|
|
202
|
+
else
|
|
203
|
+
return value
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module DynamicFieldsets
|
|
2
|
+
# Base class for various fieldtypes, i.e. questions
|
|
3
|
+
#
|
|
4
|
+
# @author Jeremiah Hemphill, Ethan Pemble
|
|
5
|
+
class Field < ActiveRecord::Base
|
|
6
|
+
# Relations
|
|
7
|
+
belongs_to :fieldset
|
|
8
|
+
has_many :field_options
|
|
9
|
+
accepts_nested_attributes_for :field_options, :allow_destroy => true
|
|
10
|
+
|
|
11
|
+
has_many :field_defaults
|
|
12
|
+
accepts_nested_attributes_for :field_defaults, :allow_destroy => true
|
|
13
|
+
|
|
14
|
+
has_many :field_html_attributes
|
|
15
|
+
accepts_nested_attributes_for :field_html_attributes, :allow_destroy => true
|
|
16
|
+
|
|
17
|
+
# Validations
|
|
18
|
+
validates_presence_of :name
|
|
19
|
+
validates_presence_of :label
|
|
20
|
+
validates_presence_of :field_type
|
|
21
|
+
validates_presence_of :order_num
|
|
22
|
+
validates_inclusion_of :enabled, :in => [true, false]
|
|
23
|
+
validates_inclusion_of :required, :in => [true, false]
|
|
24
|
+
validate :has_field_options, :field_type_in_field_types
|
|
25
|
+
|
|
26
|
+
# validates inclusion of wasn't working so I made it a custom validation
|
|
27
|
+
# refactor later when I figure out how rails works
|
|
28
|
+
def field_type_in_field_types
|
|
29
|
+
if !Field.field_types.include?(self.field_type)
|
|
30
|
+
self.errors.add(:field_type, "The field type must be one of the available field types.")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Custom validation for fields with multiple options on update
|
|
35
|
+
def has_field_options
|
|
36
|
+
if options? && self.field_options.empty?
|
|
37
|
+
self.errors.add(:field_options, "This field must have options")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @returns [Array] An array of allowable field types
|
|
42
|
+
def self.field_types
|
|
43
|
+
["select", "multiple_select", "checkbox", "radio", "textfield", "textarea", "date", "datetime", "instruction"]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @returns [Array] An array of field types that use options
|
|
47
|
+
def self.option_field_types
|
|
48
|
+
["select", "multiple_select", "checkbox", "radio"]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Boolean] True if the field is of type 'select', 'multiple_select', 'radio', or 'checkbox'
|
|
52
|
+
def options?
|
|
53
|
+
Field.option_field_types.include? self.field_type
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [FieldOptions] Returns all field options that are enabled
|
|
57
|
+
def options
|
|
58
|
+
return self.field_options.reject{ |option| !option.enabled }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Boolean] False if field_default.value is empty
|
|
62
|
+
def has_defaults?
|
|
63
|
+
return self.field_defaults.length > 0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @return [Array] Alias for field_defaults
|
|
67
|
+
def defaults
|
|
68
|
+
if options?
|
|
69
|
+
return self.field_defaults
|
|
70
|
+
else
|
|
71
|
+
return nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [String] Alias for field_defaults.first
|
|
76
|
+
def default
|
|
77
|
+
if options?
|
|
78
|
+
return nil
|
|
79
|
+
else
|
|
80
|
+
return self.field_defaults.first
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module DynamicFieldsets
|
|
2
|
+
# Base class for various field_defaults,
|
|
3
|
+
# A text field would have a single default value
|
|
4
|
+
# While a multiple select could have multiple default values
|
|
5
|
+
#
|
|
6
|
+
# @authors Scott Sampson, Jeremiah Hemphill, Ethan Pemble
|
|
7
|
+
class FieldDefault < ActiveRecord::Base
|
|
8
|
+
#relations
|
|
9
|
+
belongs_to :field
|
|
10
|
+
|
|
11
|
+
#validations
|
|
12
|
+
validates_presence_of :value
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module DynamicFieldsets
|
|
2
|
+
# Base class for various field_html_attributes,
|
|
3
|
+
# an example of an html attribute is {attribute => 'class',value => 'required'}
|
|
4
|
+
# Any field can have more than one html attribute
|
|
5
|
+
#
|
|
6
|
+
# @authors Scott Sampson, Jeremiah Hemphill, Ethan Pemble
|
|
7
|
+
class FieldHtmlAttribute < ActiveRecord::Base
|
|
8
|
+
#relations
|
|
9
|
+
belongs_to :field
|
|
10
|
+
|
|
11
|
+
#validations
|
|
12
|
+
validates_presence_of :attribute_name
|
|
13
|
+
validates_presence_of :value
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module DynamicFieldsets
|
|
2
|
+
# Base class for various field_options,
|
|
3
|
+
# field options are used for fields with a set of answers to choose from
|
|
4
|
+
# the field types that need options are select, checkbox, or radio
|
|
5
|
+
#
|
|
6
|
+
# @authors Scott Sampson, Jeremiah Hemphill, Ethan Pemble
|
|
7
|
+
class FieldOption < ActiveRecord::Base
|
|
8
|
+
#relations
|
|
9
|
+
belongs_to :field
|
|
10
|
+
|
|
11
|
+
#validations
|
|
12
|
+
validates_presence_of :name
|
|
13
|
+
validates_inclusion_of :enabled, :in => [true, false]
|
|
14
|
+
|
|
15
|
+
# @return [Array] Scope: enabled field options
|
|
16
|
+
scope :enabled, :conditions => { :enabled => true }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module DynamicFieldsets
|
|
2
|
+
# Stores a single record's answer to a field in a fieldset
|
|
3
|
+
# Fields with multiple answers should have multiple records in this model
|
|
4
|
+
class FieldRecord < ActiveRecord::Base
|
|
5
|
+
belongs_to :field
|
|
6
|
+
belongs_to :fieldset_associator
|
|
7
|
+
|
|
8
|
+
validates_presence_of :field, :fieldset_associator
|
|
9
|
+
validates_exclusion_of :value, :in => [nil]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module DynamicFieldsets
|
|
2
|
+
# Stores a collection of fields and other fieldsets
|
|
3
|
+
#
|
|
4
|
+
# @author Jeremiah Hemphill, Ethan Pemble
|
|
5
|
+
class Fieldset < ActiveRecord::Base
|
|
6
|
+
# Relations
|
|
7
|
+
has_many :fieldset_associators
|
|
8
|
+
belongs_to :parent_fieldset, :class_name => "Fieldset", :foreign_key => "parent_fieldset_id"
|
|
9
|
+
has_many :child_fieldsets, :class_name => "Fieldset", :foreign_key => "parent_fieldset_id"
|
|
10
|
+
has_many :fields
|
|
11
|
+
|
|
12
|
+
# Validations
|
|
13
|
+
validates_presence_of :name
|
|
14
|
+
validates_presence_of :description
|
|
15
|
+
validates_presence_of :nkey
|
|
16
|
+
validates_uniqueness_of :nkey
|
|
17
|
+
validates_presence_of :order_num, :if => lambda { !self.root? }
|
|
18
|
+
validate :cannot_be_own_parent
|
|
19
|
+
|
|
20
|
+
# looks recursively up the parent_fieldset value to check if it sees itself
|
|
21
|
+
def cannot_be_own_parent
|
|
22
|
+
parent = self.parent_fieldset
|
|
23
|
+
while !parent.nil?
|
|
24
|
+
if parent == self
|
|
25
|
+
self.errors.add(:parent_fieldset, "Parent fieldsets must not create a cycle.")
|
|
26
|
+
parent = nil
|
|
27
|
+
else
|
|
28
|
+
parent = parent.parent_fieldset
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Array] Scope: parent-less fieldsets
|
|
34
|
+
scope :roots, :conditions => ["parent_fieldset_id IS NULL"]
|
|
35
|
+
|
|
36
|
+
# @return [Array] An array of name, id pairs to be used in select tags
|
|
37
|
+
def self.parent_fieldset_list
|
|
38
|
+
all.collect { |f| [f.name, f.id] }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Boolean] True if fieldset has no parent
|
|
42
|
+
def root?
|
|
43
|
+
return parent_fieldset.nil?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The collected descendents of a fieldset. This group is sorted first by order number,
|
|
47
|
+
# then alphabetically by name in the case of duplicate order numbers.
|
|
48
|
+
# @return [Array] Ordered collection of descendent fields and fieldsets.
|
|
49
|
+
def children
|
|
50
|
+
collected_children = []
|
|
51
|
+
fields.reject{|f| !f.enabled}.each{ |field| collected_children.push field }
|
|
52
|
+
child_fieldsets.each{ |fieldset| collected_children.push fieldset }
|
|
53
|
+
return collected_children.sort_by{ |child| [child.order_num, child.name] }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module DynamicFieldsets
|
|
2
|
+
class FieldsetAssociator < ActiveRecord::Base
|
|
3
|
+
belongs_to :fieldset
|
|
4
|
+
has_many :field_records
|
|
5
|
+
|
|
6
|
+
validates_presence_of :fieldset_id, :fieldset_model_id, :fieldset_model_type, :fieldset_model_name
|
|
7
|
+
validate :unique_fieldset_model_name_per_polymorphic_fieldset_model
|
|
8
|
+
|
|
9
|
+
def unique_fieldset_model_name_per_polymorphic_fieldset_model
|
|
10
|
+
FieldsetAssociator.where(:fieldset_model_id => self.fieldset_model_id, :fieldset_model_type => self.fieldset_model_id, :fieldset_model_name => self.fieldset_model_name).each do |fsa|
|
|
11
|
+
if fsa.id != self.id
|
|
12
|
+
self.errors.add(:fieldset_model_name, "A duplicate Field Model, Field Model Name pair has been found.")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Scope to find a fieldset associator based on information from the fieldset model
|
|
18
|
+
#
|
|
19
|
+
# Arguments
|
|
20
|
+
# fieldset: The nkey of the fieldset
|
|
21
|
+
# fieldset_model_id: The id of the fieldset model
|
|
22
|
+
# fieldset_model_type: The class name of the fieldset model
|
|
23
|
+
# fieldset_model_name: The named fieldset in the model
|
|
24
|
+
#
|
|
25
|
+
# @params [Hash] args A hash of arguments for the scope
|
|
26
|
+
# @retursn [Array] An array of fieldset associators that match the arguments
|
|
27
|
+
def self.find_by_fieldset_model_parameters(args)
|
|
28
|
+
fieldset = Fieldset.find_by_nkey(args[:fieldset])
|
|
29
|
+
where(
|
|
30
|
+
:fieldset_id => fieldset.id,
|
|
31
|
+
:fieldset_model_id => args[:fieldset_model_id],
|
|
32
|
+
:fieldset_model_type => args[:fieldset_model_type],
|
|
33
|
+
:fieldset_model_name => args[:fieldset_model_name])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns a hash of field record values
|
|
37
|
+
# Fun nonintuitive stuff here
|
|
38
|
+
#
|
|
39
|
+
# The hash keys are field ids
|
|
40
|
+
# The hash values are field_record values or field_records ids depending on the field type
|
|
41
|
+
# The hash values are usually strings but sometimes arrays
|
|
42
|
+
# If a field that expects a single value has multiple values, it will
|
|
43
|
+
# choose one to use arbitrarily
|
|
44
|
+
#
|
|
45
|
+
# multiple_select: [option_ids,]
|
|
46
|
+
# checkbox: [option_ids,]
|
|
47
|
+
# select: option_id
|
|
48
|
+
# radio: option_id
|
|
49
|
+
# textfield: "value"
|
|
50
|
+
# textarea: "value"
|
|
51
|
+
# date: "value"
|
|
52
|
+
# datetime: "value"
|
|
53
|
+
# instruction: "value"
|
|
54
|
+
#
|
|
55
|
+
# @return [Hash] A hash of field record values associated with field ids
|
|
56
|
+
def field_values
|
|
57
|
+
output = {}
|
|
58
|
+
self.field_records.each do |record|
|
|
59
|
+
if record.field.field_type == "checkbox" || record.field.field_type == "multiple_select"
|
|
60
|
+
output[record.field.id] = [] unless output[record.field.id].is_a?(Array)
|
|
61
|
+
# note record.id array
|
|
62
|
+
# collect?
|
|
63
|
+
output[record.field.id].push record.value.to_i
|
|
64
|
+
elsif record.field.field_type == "radio" || record.field.field_type == "select"
|
|
65
|
+
# note record.id
|
|
66
|
+
output[record.field.id] = record.value.to_i
|
|
67
|
+
else
|
|
68
|
+
# note record.value
|
|
69
|
+
output[record.field.id] = record.value
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
return output
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<%= form_for(@field) do |f| %>
|
|
2
|
+
<% if @field.errors.any? %>
|
|
3
|
+
<div id="error_explanation">
|
|
4
|
+
<h2><%= pluralize(@field.errors.count, "error") %> prohibited this field from being saved:</h2>
|
|
5
|
+
|
|
6
|
+
<ul>
|
|
7
|
+
<% @field.errors.full_messages.each do |msg| %>
|
|
8
|
+
<li><%= msg %></li>
|
|
9
|
+
<% end %>
|
|
10
|
+
</ul>
|
|
11
|
+
</div>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<div class="field">
|
|
15
|
+
<%= f.label :fieldset_id %><br />
|
|
16
|
+
<%= f.select :fieldset_id, options_for_select(DynamicFieldsets::Fieldset.parent_fieldset_list), :include_blank => "Choose One" %>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="field">
|
|
19
|
+
<%= f.label :name %><br />
|
|
20
|
+
<%= f.text_field :name %>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="field">
|
|
23
|
+
<%= f.label :label %><br />
|
|
24
|
+
<%= f.text_field :label %>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="field">
|
|
28
|
+
<%= f.label :field_type %><br />
|
|
29
|
+
<%= f.select :field_type, options_for_select(DynamicFieldsets::Field.field_types, @field.field_type), :include_blank => "Choose One" %>
|
|
30
|
+
</div>
|
|
31
|
+
<% @field.field_options.each do |field_option| %>
|
|
32
|
+
<%= f.fields_for :field_options, field_option do |field_option_form| %>
|
|
33
|
+
<%= render :partial => "field_option_fields", :locals => {:f => field_option_form} %>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
36
|
+
<p><%= link_to_add_fields "Add Field Option", f, :field_options %></p>
|
|
37
|
+
|
|
38
|
+
<div class="field">
|
|
39
|
+
<%= f.label :required %><br />
|
|
40
|
+
<%= f.check_box :required %>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="field">
|
|
43
|
+
<%= f.label :enabled %><br />
|
|
44
|
+
<%= f.check_box :enabled %>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="field">
|
|
47
|
+
<%= f.label :order_num, "Order Number" %><br />
|
|
48
|
+
<%= f.text_field :order_num %>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<% @field.field_defaults.each do |field_default| %>
|
|
52
|
+
<%= f.fields_for :field_defaults, field_default do |field_default_form| %>
|
|
53
|
+
<%= render :partial => "field_default_fields", :locals => {:f => field_default_form} %>
|
|
54
|
+
<% end %>
|
|
55
|
+
<% end %>
|
|
56
|
+
<p><%= link_to_add_fields "Add Default Value", f, :field_defaults %></p>
|
|
57
|
+
|
|
58
|
+
<% @field.field_html_attributes.each do |field_html_attribute| %>
|
|
59
|
+
<%= f.fields_for :field_html_attributes, field_html_attribute do |field_html_attribute_form| %>
|
|
60
|
+
<%= render :partial => "field_html_attribute_fields", :locals => {:f => field_html_attribute_form} %>
|
|
61
|
+
<% end %>
|
|
62
|
+
<% end %>
|
|
63
|
+
<p><%= link_to_add_fields "Add Html Attribute", f, :field_html_attributes %></p>
|
|
64
|
+
|
|
65
|
+
<div class="actions">
|
|
66
|
+
<%= f.submit %>
|
|
67
|
+
</div>
|
|
68
|
+
<% end %>
|
|
69
|
+
|
|
70
|
+
<script>
|
|
71
|
+
function remove_fields(link) {
|
|
72
|
+
$(link).prev("input[type=hidden]").val("1");
|
|
73
|
+
$(link).closest(".fields").hide();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function add_fields(link, association, content) {
|
|
77
|
+
var new_id = new Date().getTime();
|
|
78
|
+
var regexp = new RegExp("new_" + association, "g");
|
|
79
|
+
$(link).parent().before(content.replace(regexp, new_id));
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<h1>Listing fields</h1>
|
|
2
|
+
|
|
3
|
+
<table>
|
|
4
|
+
<tr>
|
|
5
|
+
<th>Fieldset</th>
|
|
6
|
+
<th>Name</th>
|
|
7
|
+
<th>Type</th>
|
|
8
|
+
<th>Order num</th>
|
|
9
|
+
<th></th>
|
|
10
|
+
<th></th>
|
|
11
|
+
<th></th>
|
|
12
|
+
</tr>
|
|
13
|
+
|
|
14
|
+
<% @fields.each do |field| %>
|
|
15
|
+
<tr>
|
|
16
|
+
<td><%= field.fieldset.name if field.fieldset %></td>
|
|
17
|
+
<td><%= field.name %></td>
|
|
18
|
+
<td><%= field.field_type %></td>
|
|
19
|
+
<td><%= field.order_num %></td>
|
|
20
|
+
<td><%= link_to 'Show', dynamic_fieldsets_field_path(field) %></td>
|
|
21
|
+
<td><%= link_to 'Edit', edit_dynamic_fieldsets_field_path(field) %></td>
|
|
22
|
+
<td><%= link_to 'Destroy', dynamic_fieldsets_field_path(field), :confirm => 'Are you sure?', :method => :delete %></td>
|
|
23
|
+
</tr>
|
|
24
|
+
<% end %>
|
|
25
|
+
</table>
|
|
26
|
+
|
|
27
|
+
<br />
|
|
28
|
+
|
|
29
|
+
<%= link_to 'New Field', new_dynamic_fieldsets_field_path %>
|