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.
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 %>