dynamic_fieldsets 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/CHANGELOG +4 -0
  2. data/Gemfile +21 -0
  3. data/Gemfile.lock +157 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +19 -0
  6. data/Rakefile +69 -0
  7. data/VERSION +1 -0
  8. data/app/controllers/dynamic_fieldsets/fields_controller.rb +75 -0
  9. data/app/controllers/dynamic_fieldsets/fieldset_associators_controller.rb +24 -0
  10. data/app/controllers/dynamic_fieldsets/fieldsets_controller.rb +89 -0
  11. data/app/helpers/dynamic_fieldsets/fields_helper.rb +19 -0
  12. data/app/helpers/dynamic_fieldsets_helper.rb +207 -0
  13. data/app/models/dynamic_fieldsets/field.rb +85 -0
  14. data/app/models/dynamic_fieldsets/field_default.rb +14 -0
  15. data/app/models/dynamic_fieldsets/field_html_attribute.rb +15 -0
  16. data/app/models/dynamic_fieldsets/field_option.rb +18 -0
  17. data/app/models/dynamic_fieldsets/field_record.rb +11 -0
  18. data/app/models/dynamic_fieldsets/fieldset.rb +57 -0
  19. data/app/models/dynamic_fieldsets/fieldset_associator.rb +76 -0
  20. data/app/views/dynamic_fieldsets/fields/_field_default_fields.html.erb +7 -0
  21. data/app/views/dynamic_fieldsets/fields/_field_html_attribute_fields.html.erb +8 -0
  22. data/app/views/dynamic_fieldsets/fields/_field_option_fields.html.erb +6 -0
  23. data/app/views/dynamic_fieldsets/fields/_form.html.erb +81 -0
  24. data/app/views/dynamic_fieldsets/fields/edit.html.erb +6 -0
  25. data/app/views/dynamic_fieldsets/fields/index.html.erb +29 -0
  26. data/app/views/dynamic_fieldsets/fields/new.html.erb +5 -0
  27. data/app/views/dynamic_fieldsets/fields/show.html.erb +70 -0
  28. data/app/views/dynamic_fieldsets/fieldset_associators/index.html.erb +18 -0
  29. data/app/views/dynamic_fieldsets/fieldset_associators/show.html.erb +26 -0
  30. data/app/views/dynamic_fieldsets/fieldsets/_form.html.erb +37 -0
  31. data/app/views/dynamic_fieldsets/fieldsets/children.html.erb +45 -0
  32. data/app/views/dynamic_fieldsets/fieldsets/edit.html.erb +6 -0
  33. data/app/views/dynamic_fieldsets/fieldsets/index.html.erb +31 -0
  34. data/app/views/dynamic_fieldsets/fieldsets/new.html.erb +5 -0
  35. data/app/views/dynamic_fieldsets/fieldsets/show.html.erb +31 -0
  36. data/config/routes.rb +9 -0
  37. data/dynamic_fieldsets.gemspec +195 -0
  38. data/lib/dynamic_fieldsets/config.rb +0 -0
  39. data/lib/dynamic_fieldsets/dynamic_fieldsets_in_model.rb +164 -0
  40. data/lib/dynamic_fieldsets/engine.rb +4 -0
  41. data/lib/dynamic_fieldsets/railtie.rb +13 -0
  42. data/lib/dynamic_fieldsets.rb +2 -0
  43. data/lib/generators/dynamic_fieldsets/install_generator.rb +44 -0
  44. data/lib/generators/dynamic_fieldsets/templates/config.rb +0 -0
  45. data/lib/generators/dynamic_fieldsets/templates/migrations/install_migration.rb +74 -0
  46. data/spec/dummy/Rakefile +7 -0
  47. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  48. data/spec/dummy/app/controllers/information_forms_controller.rb +85 -0
  49. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  50. data/spec/dummy/app/helpers/information_forms_helper.rb +2 -0
  51. data/spec/dummy/app/models/information_form.rb +3 -0
  52. data/spec/dummy/app/views/information_forms/_form.html.erb +22 -0
  53. data/spec/dummy/app/views/information_forms/edit.html.erb +6 -0
  54. data/spec/dummy/app/views/information_forms/index.html.erb +23 -0
  55. data/spec/dummy/app/views/information_forms/new.html.erb +5 -0
  56. data/spec/dummy/app/views/information_forms/show.html.erb +11 -0
  57. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  58. data/spec/dummy/config/application.rb +45 -0
  59. data/spec/dummy/config/boot.rb +10 -0
  60. data/spec/dummy/config/database.yml +22 -0
  61. data/spec/dummy/config/environment.rb +5 -0
  62. data/spec/dummy/config/environments/development.rb +26 -0
  63. data/spec/dummy/config/environments/production.rb +49 -0
  64. data/spec/dummy/config/environments/test.rb +35 -0
  65. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  66. data/spec/dummy/config/initializers/inflections.rb +10 -0
  67. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  68. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  69. data/spec/dummy/config/initializers/session_store.rb +8 -0
  70. data/spec/dummy/config/locales/en.yml +5 -0
  71. data/spec/dummy/config/routes.rb +60 -0
  72. data/spec/dummy/config.ru +4 -0
  73. data/spec/dummy/db/migrate/20110726215814_create_dynamic_fieldsets_tables.rb +74 -0
  74. data/spec/dummy/db/migrate/20110727210451_create_information_forms.rb +13 -0
  75. data/spec/dummy/db/schema.rb +83 -0
  76. data/spec/dummy/features/field.feature +54 -0
  77. data/spec/dummy/features/fieldset.feature +68 -0
  78. data/spec/dummy/features/fieldset_associator.feature +20 -0
  79. data/spec/dummy/features/step_definitions/debugging_steps.rb +8 -0
  80. data/spec/dummy/features/step_definitions/field_steps.rb +63 -0
  81. data/spec/dummy/features/step_definitions/fieldset_associator_steps.rb +11 -0
  82. data/spec/dummy/features/step_definitions/fieldset_steps.rb +57 -0
  83. data/spec/dummy/features/step_definitions/web_steps.rb +214 -0
  84. data/spec/dummy/features/support/env.rb +15 -0
  85. data/spec/dummy/features/support/paths.rb +46 -0
  86. data/spec/dummy/features/support/selectors.rb +39 -0
  87. data/spec/dummy/public/404.html +26 -0
  88. data/spec/dummy/public/422.html +26 -0
  89. data/spec/dummy/public/500.html +26 -0
  90. data/spec/dummy/public/favicon.ico +0 -0
  91. data/spec/dummy/public/javascripts/application.js +2 -0
  92. data/spec/dummy/public/javascripts/jquery.min.js +166 -0
  93. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  94. data/spec/dummy/public/stylesheets/scaffold.css +56 -0
  95. data/spec/dummy/script/rails +6 -0
  96. data/spec/dynamic_fieldsets_helper_spec.rb +254 -0
  97. data/spec/dynamic_fieldsets_in_model_spec.rb +175 -0
  98. data/spec/dynamic_fieldsets_spec.rb +7 -0
  99. data/spec/integration/navigation_spec.rb +9 -0
  100. data/spec/models/field_default_spec.rb +26 -0
  101. data/spec/models/field_html_attribute_spec.rb +30 -0
  102. data/spec/models/field_option_spec.rb +52 -0
  103. data/spec/models/field_record_spec.rb +44 -0
  104. data/spec/models/field_spec.rb +169 -0
  105. data/spec/models/fieldset_associator_spec.rb +128 -0
  106. data/spec/models/fieldset_spec.rb +169 -0
  107. data/spec/reports/SPEC-ActsAsMultipartForm.xml +9 -0
  108. data/spec/reports/SPEC-MultipartForm-InProgressForm-validations.xml +47 -0
  109. data/spec/reports/SPEC-MultipartForm-InProgressForm.xml +7 -0
  110. data/spec/reports/SPEC-Navigation.xml +9 -0
  111. data/spec/spec_helper.rb +37 -0
  112. data/spec/support/field_default_helper.rb +12 -0
  113. data/spec/support/field_helper.rb +16 -0
  114. data/spec/support/field_html_attribute_helper.rb +14 -0
  115. data/spec/support/field_option_helper.rb +13 -0
  116. data/spec/support/field_record_helper.rb +12 -0
  117. data/spec/support/fieldset_associator_helper.rb +12 -0
  118. data/spec/support/fieldset_helper.rb +28 -0
  119. 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,7 @@
1
+ <p class="fields">
2
+ <%= f.label :value, "Default field value" %>
3
+ <%= f.text_field :value %>
4
+ <%= f.hidden_field :_destroy %>
5
+ <%= link_to_function "remove", "remove_fields(this)" %>
6
+ </p>
7
+
@@ -0,0 +1,8 @@
1
+ <p class="fields">
2
+ <%= f.label :attribute_name %>
3
+ <%= f.text_field :attribute_name %>
4
+ <%= f.label :value, %>
5
+ <%= f.text_field :value %>
6
+ <%= f.hidden_field :_destroy %>
7
+ <%= link_to_function "remove", "remove_fields(this)" %>
8
+ </p>
@@ -0,0 +1,6 @@
1
+ <p class="fields">
2
+ <%= f.label :name %>
3
+ <%= f.text_field :name %>
4
+ <%= f.hidden_field :_destroy %>
5
+ <%= link_to_function "remove", "remove_fields(this)" %>
6
+ </p>
@@ -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,6 @@
1
+ <h1>Editing field</h1>
2
+
3
+ <%= render 'form' %>
4
+
5
+ <%= link_to 'Show', @field %> |
6
+ <%= link_to 'Back', dynamic_fieldsets_fields_path %>
@@ -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 %>
@@ -0,0 +1,5 @@
1
+ <h1>New field</h1>
2
+
3
+ <%= render 'form' %>
4
+
5
+ <%= link_to 'Back', dynamic_fieldsets_fields_path %>