dynamic_fieldsets 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 %>
|