wheelhouse-forms 1.0

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 (89) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +39 -0
  3. data/app/assets/images/wheelhouse-forms/checkbox.png +0 -0
  4. data/app/assets/images/wheelhouse-forms/checkboxes.png +0 -0
  5. data/app/assets/images/wheelhouse-forms/content.png +0 -0
  6. data/app/assets/images/wheelhouse-forms/countries.png +0 -0
  7. data/app/assets/images/wheelhouse-forms/csv.png +0 -0
  8. data/app/assets/images/wheelhouse-forms/custom.png +0 -0
  9. data/app/assets/images/wheelhouse-forms/drag.png +0 -0
  10. data/app/assets/images/wheelhouse-forms/field-set.png +0 -0
  11. data/app/assets/images/wheelhouse-forms/form.png +0 -0
  12. data/app/assets/images/wheelhouse-forms/radio-buttons.png +0 -0
  13. data/app/assets/images/wheelhouse-forms/select.png +0 -0
  14. data/app/assets/images/wheelhouse-forms/states.png +0 -0
  15. data/app/assets/images/wheelhouse-forms/submit-button.png +0 -0
  16. data/app/assets/images/wheelhouse-forms/text-area.png +0 -0
  17. data/app/assets/images/wheelhouse-forms/text-field.png +0 -0
  18. data/app/assets/javascripts/wheelhouse-forms/admin.js +125 -0
  19. data/app/assets/javascripts/wheelhouse-forms/jquery.autogrow.js +50 -0
  20. data/app/assets/javascripts/wheelhouse-forms/jquery.options.js +24 -0
  21. data/app/assets/stylesheets/wheelhouse-forms/admin.css.sass +244 -0
  22. data/app/controllers/forms/forms_controller.rb +9 -0
  23. data/app/controllers/forms/submissions_controller.rb +19 -0
  24. data/app/handlers/forms/form_handler.rb +17 -0
  25. data/app/helpers/forms/forms_helper.rb +36 -0
  26. data/app/helpers/forms/mailer_helper.rb +67 -0
  27. data/app/helpers/forms/submissions_helper.rb +71 -0
  28. data/app/mailers/forms/mailer.rb +11 -0
  29. data/app/models/forms/field_collection.rb +21 -0
  30. data/app/models/forms/fields/checkbox.rb +8 -0
  31. data/app/models/forms/fields/checkboxes.rb +10 -0
  32. data/app/models/forms/fields/content_field.rb +7 -0
  33. data/app/models/forms/fields/countries_dropdown.rb +14 -0
  34. data/app/models/forms/fields/custom_field.rb +8 -0
  35. data/app/models/forms/fields/field.rb +28 -0
  36. data/app/models/forms/fields/field_set.rb +14 -0
  37. data/app/models/forms/fields/optioned_field.rb +17 -0
  38. data/app/models/forms/fields/radio_buttons.rb +10 -0
  39. data/app/models/forms/fields/select_field.rb +10 -0
  40. data/app/models/forms/fields/states_dropdown.rb +15 -0
  41. data/app/models/forms/fields/submit_button.rb +7 -0
  42. data/app/models/forms/fields/text_area.rb +8 -0
  43. data/app/models/forms/fields/text_field.rb +21 -0
  44. data/app/models/forms/form.rb +65 -0
  45. data/app/models/forms/submission.rb +53 -0
  46. data/app/renderers/forms/checkbox_renderer.rb +7 -0
  47. data/app/renderers/forms/checkboxes_renderer.rb +18 -0
  48. data/app/renderers/forms/content_field_renderer.rb +5 -0
  49. data/app/renderers/forms/custom_field_renderer.rb +5 -0
  50. data/app/renderers/forms/field_collection_renderer.rb +13 -0
  51. data/app/renderers/forms/field_renderer.rb +57 -0
  52. data/app/renderers/forms/field_set_renderer.rb +10 -0
  53. data/app/renderers/forms/form_renderer.rb +30 -0
  54. data/app/renderers/forms/labelled_field_renderer.rb +10 -0
  55. data/app/renderers/forms/radio_buttons_renderer.rb +18 -0
  56. data/app/renderers/forms/select_field_renderer.rb +9 -0
  57. data/app/renderers/forms/submit_button_renderer.rb +5 -0
  58. data/app/renderers/forms/text_area_renderer.rb +7 -0
  59. data/app/renderers/forms/text_field_renderer.rb +7 -0
  60. data/app/templates/form.html.haml +9 -0
  61. data/app/templates/form.xhr.haml +4 -0
  62. data/app/templates/form/submission.html.haml +15 -0
  63. data/app/views/forms/forms/_checkbox.haml +3 -0
  64. data/app/views/forms/forms/_checkboxes.haml +10 -0
  65. data/app/views/forms/forms/_content_field.haml +3 -0
  66. data/app/views/forms/forms/_countries_dropdown.haml +4 -0
  67. data/app/views/forms/forms/_custom_field.haml +4 -0
  68. data/app/views/forms/forms/_field_set.haml +16 -0
  69. data/app/views/forms/forms/_radio_buttons.haml +10 -0
  70. data/app/views/forms/forms/_select_field.haml +10 -0
  71. data/app/views/forms/forms/_states_dropdown.haml +4 -0
  72. data/app/views/forms/forms/_submissions.haml +20 -0
  73. data/app/views/forms/forms/_submit_button.haml +2 -0
  74. data/app/views/forms/forms/_text_area.haml +3 -0
  75. data/app/views/forms/forms/_text_field.haml +4 -0
  76. data/app/views/forms/forms/designer.haml +56 -0
  77. data/app/views/forms/forms/edit.html.haml +5 -0
  78. data/app/views/forms/forms/form.haml +22 -0
  79. data/app/views/forms/forms/new.html.haml +4 -0
  80. data/app/views/forms/forms/show.html.haml +4 -0
  81. data/app/views/forms/submissions/form.haml +16 -0
  82. data/app/views/forms/submissions/show.html.haml +4 -0
  83. data/config/countries.yml +245 -0
  84. data/config/locales/en.yml +7 -0
  85. data/config/routes.rb +7 -0
  86. data/config/states.yml +62 -0
  87. data/lib/forms/csv_exporter.rb +41 -0
  88. data/lib/wheelhouse-forms.rb +26 -0
  89. metadata +149 -0
@@ -0,0 +1,9 @@
1
+ class Forms::FormsController < Wheelhouse::ResourceController
2
+ self.resource_class = Forms::Form
3
+ manage_site_breadcrumb
4
+
5
+ def version
6
+ resource.revert_to(params[:version].to_i)
7
+ render :edit
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ require_dependency "forms/csv_exporter"
2
+
3
+ class Forms::SubmissionsController < Wheelhouse::ResourceController
4
+ belongs_to :form, :class_name => Forms::Form
5
+ self.resource_class = Forms::Submission
6
+
7
+ manage_site_breadcrumb
8
+ breadcrumb { [parent.label, parent] }
9
+
10
+ actions :show, :destroy
11
+
12
+ respond_to :csv
13
+
14
+ def index
15
+ respond_with(parent) do |format|
16
+ format.csv { render :text => Forms::CsvExporter.new(parent).to_csv }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ class Forms::FormHandler < Wheelhouse::ResourceHandler
2
+ skip_before_filter :verify_authenticity_token
3
+
4
+ before_filter :set_form_context
5
+
6
+ get do
7
+ end
8
+
9
+ post do
10
+ @form.submit(params[:submission])
11
+ end
12
+
13
+ private
14
+ def set_form_context
15
+ @form.view_context = view_context
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ module Forms::FormsHelper
2
+ class FormBuilder < ActionView::Helpers::FormBuilder
3
+ def required_field_checkbox
4
+ @template.content_tag(:label, :class => "required") do
5
+ check_box(:required, :id => nil) + " This field is required"
6
+ end
7
+ end
8
+ end
9
+
10
+ def form_field(field, options={}, &block)
11
+ builder = FormBuilder.new(options[:prefix], field, self, {}, block)
12
+ icon = options[:icon] || field.class.model_name.demodulize.underscore.dasherize
13
+
14
+ content_tag(:div, :class => options[:class], :data => { :index => options[:index] }) do
15
+ concat builder.hidden_field(:type, :value => field.type.demodulize, :id => nil)
16
+
17
+ concat content_tag(:div, image_tag("wheelhouse-forms/#{icon}.png", :alt => field.class.model_name.human, :title => field.class.model_name.human), :class => "drag")
18
+
19
+ concat content_tag(:div, capture(builder, &block), :class => "drag-area")
20
+
21
+ concat link_to("Delete", '#', :class => 'delete')
22
+ end
23
+ end
24
+
25
+ def render_fields(fields, prefix)
26
+ fields.map_with_index do |field, index|
27
+ render(:partial => field.class.partial, :object => field, :locals => { :index => index, :prefix => "#{prefix}[#{index}]" })
28
+ end.join("\n").html_safe
29
+ end
30
+
31
+ def field_template(id, klass)
32
+ jquery_template_tag(id) do
33
+ render :partial => klass.partial, :object => klass.new, :locals => { :index => "${index}", :prefix => "${prefix}" }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,67 @@
1
+ module Forms::MailerHelper
2
+ def render_field(field)
3
+ case field
4
+ when Forms::Fields::FieldSet
5
+ render_fieldset(field)
6
+ when Forms::Fields::CustomField
7
+ render_custom_field(field)
8
+ when Forms::Fields::Checkboxes
9
+ render_array(field, @submission.value_for(field) || ["None given"])
10
+ when Forms::Fields::ContentField
11
+ # Nothing
12
+ else
13
+ render_text_field(field, @submission.value_for(field))
14
+ end
15
+ end
16
+
17
+ private
18
+ def render_fieldset(fieldset)
19
+ content_tag(:div, :class => "fieldset") do
20
+ concat content_tag(:h3, fieldset.legend) if fieldset.legend?
21
+ concat safe_join(fieldset.fields.map { |f| render_field(f) }, "\n")
22
+ nil
23
+ end
24
+ end
25
+
26
+ def render_custom_field(field)
27
+ value = @submission.value_for(field)
28
+
29
+ case value
30
+ when Array
31
+ render_array(field, value)
32
+ when Hash
33
+ render_hash(field, value)
34
+ else
35
+ render_text_field(field, value)
36
+ end
37
+ end
38
+
39
+ def render_array(field, array)
40
+ labelled_field(field) do
41
+ concat content_tag(:ul, safe_join(array.map { |i| content_tag(:li, i) }))
42
+ end
43
+ end
44
+
45
+ def render_hash(field, hash)
46
+ labelled_field(field) do
47
+ concat content_tag(:ul, safe_join(hash.map { |k, v|
48
+ content_tag(:li, content_tag(:strong, k) + ": " + v)
49
+ }))
50
+ end
51
+ end
52
+
53
+ def render_text_field(field, value)
54
+ labelled_field(field) do
55
+ concat field.is_a?(Forms::Fields::TextArea) ? tag(:br) : " "
56
+ concat value
57
+ end
58
+ end
59
+
60
+ def labelled_field(field)
61
+ content_tag(:p, :class => "field") do
62
+ concat content_tag(:strong, field.label + ":")
63
+ yield
64
+ nil
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,71 @@
1
+ module Forms::SubmissionsHelper
2
+ def render_field(field)
3
+ case field
4
+ when Forms::Fields::FieldSet
5
+ render_fieldset(field)
6
+ when Forms::Fields::CustomField
7
+ render_custom_field(field)
8
+ when Forms::Fields::Checkboxes
9
+ labelled_field(field) do
10
+ value = @submission.value_for(field)
11
+ render_array(field, value || [""])
12
+ end
13
+ when Forms::Fields::TextArea
14
+ labelled_field(field) do
15
+ value = @submission.value_for(field)
16
+ form.text_area field.label, :value => value || "", :readonly => true, :rows => 5
17
+ end
18
+ when Forms::Fields::ContentField
19
+ # Nothing
20
+ else
21
+ labelled_field(field) do
22
+ value = @submission.value_for(field)
23
+ form.text_field field.label, :value => value || "", :readonly => true
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+ def labelled_field(field)
30
+ field(field.label, :label => field.label.html_safe) do
31
+ yield
32
+ end
33
+ end
34
+
35
+ def render_fieldset(fieldset)
36
+ content_tag(:div, :class => "shaded") do
37
+ concat content_tag(:h2, fieldset.legend) if fieldset.legend?
38
+ concat safe_join(fieldset.fields.map { |f| render_field(f) })
39
+ nil
40
+ end
41
+ end
42
+
43
+ def render_custom_field(field)
44
+ labelled_field(field) do
45
+ value = @submission.value_for(field)
46
+
47
+ case value
48
+ when Array
49
+ render_array(field, value)
50
+ when Hash
51
+ render_hash(field, value)
52
+ else
53
+ form.text_field field.label, :value => value || "", :readonly => true
54
+ end
55
+ end
56
+ end
57
+
58
+ def render_array(field, array)
59
+ content_tag(:ul, safe_join(array.map { |value|
60
+ content_tag(:li, form.text_field("#{field.label}[]", :value => value || "", :readonly => true))
61
+ }))
62
+ end
63
+
64
+ def render_hash(field, hash)
65
+ content_tag(:div, :class => "inline") do
66
+ safe_join(hash.map { |key, value|
67
+ form.label("#{field.label} #{key}", key) + form.text_field("#{field.label} #{key}", :value => value || "", :readonly => true, :size => nil)
68
+ })
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,11 @@
1
+ class Forms::Mailer < ActionMailer::Base
2
+ default :template_path => "form"
3
+
4
+ def submission(form, submission)
5
+ @form, @submission = form, submission
6
+
7
+ mail(:from => "no-reply@#{form.site.domain}",
8
+ :to => form.recipients,
9
+ :subject => form.subject)
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ class Forms::FieldCollection < MongoModel::Collection[Forms::Fields::Field]
2
+ def map_with_index
3
+ result = []
4
+ each_with_index { |item, index| result[index] = yield(item, index) }
5
+ result
6
+ end
7
+
8
+ def self.cast(collection)
9
+ case collection
10
+ when Hash
11
+ sorted_attrs = collection.sort { |(a, _), (b, _)| a.to_i <=> b.to_i }.map { |_, attrs| attrs }
12
+ super(sorted_attrs)
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def render(submission, template)
19
+ Forms::FieldCollectionRenderer.new(self, submission, template).render
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ module Forms::Fields
2
+ class Checkbox < Field
3
+ property :label, String, :required => true
4
+ property :required, Boolean, :default => false
5
+
6
+ self.renderer = Forms::CheckboxRenderer
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ module Forms::Fields
2
+ class Checkboxes < Field
3
+ include OptionedField
4
+
5
+ property :label, String, :required => true
6
+ property :required, Boolean, :default => false
7
+
8
+ self.renderer = Forms::CheckboxesRenderer
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Forms::Fields
2
+ class ContentField < Field
3
+ property :content, Translated[ActionView::OutputBuffer]
4
+
5
+ self.renderer = Forms::ContentFieldRenderer
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ module Forms::Fields
2
+ class CountriesDropdown < Field
3
+ property :label, String, :default => "Country", :required => true
4
+ property :required, Boolean, :default => false
5
+
6
+ self.renderer = Forms::SelectFieldRenderer
7
+
8
+ def options
9
+ COUNTRIES
10
+ end
11
+
12
+ COUNTRIES = Forms::Plugin.load_yaml("countries.yml")
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ module Forms::Fields
2
+ class CustomField < Field
3
+ property :label, String, :required => true
4
+ property :partial, String
5
+
6
+ self.renderer = Forms::CustomFieldRenderer
7
+ end
8
+ end
@@ -0,0 +1,28 @@
1
+ module Forms::Fields
2
+ class Field < Wheelhouse::EmbeddedResource
3
+ class_attribute :renderer
4
+ self.renderer = Forms::FieldRenderer
5
+
6
+ def render(submission, template)
7
+ self.class.renderer.new(self, submission, template).render
8
+ end
9
+
10
+ def required?
11
+ read_attribute(:required)
12
+ end
13
+
14
+ def self.partial
15
+ name.demodulize.underscore
16
+ end
17
+
18
+ def self.cast(attrs)
19
+ case attrs
20
+ when Hash
21
+ type = attrs.delete('type')
22
+ Forms::Fields.const_get(type).new(attrs)
23
+ else
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ module Forms::Fields
2
+ class FieldSet < Field
3
+ property :legend, String
4
+ property :fields, Forms::FieldCollection
5
+
6
+ validates_associated :fields
7
+
8
+ self.renderer = Forms::FieldSetRenderer
9
+
10
+ def to_ary
11
+ fields
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module Forms::Fields
2
+ module OptionedField
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ property :options, MongoModel::Collection[String]
7
+ end
8
+
9
+ def options=(options)
10
+ write_attribute(:options, options.reject(&:blank?))
11
+ end
12
+
13
+ def options(include_blank = false)
14
+ read_attribute(:options) + (include_blank ? [""] : [])
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module Forms::Fields
2
+ class RadioButtons < Field
3
+ include OptionedField
4
+
5
+ property :label, String, :required => true
6
+ property :required, Boolean, :default => false
7
+
8
+ self.renderer = Forms::RadioButtonsRenderer
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Forms::Fields
2
+ class SelectField < Field
3
+ include OptionedField
4
+
5
+ property :label, String, :required => true
6
+ property :required, Boolean, :default => false
7
+
8
+ self.renderer = Forms::SelectFieldRenderer
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module Forms::Fields
2
+ class StatesDropdown < Field
3
+ property :label, String, :default => "State", :required => true
4
+ property :country, String, :required => true
5
+ property :required, Boolean, :default => false
6
+
7
+ self.renderer = Forms::SelectFieldRenderer
8
+
9
+ def options
10
+ STATES[country]
11
+ end
12
+
13
+ STATES = Forms::Plugin.load_yaml("states.yml").tap { |hash| hash.each { |k, v| hash[k] = v.invert } }
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Forms::Fields
2
+ class SubmitButton < Field
3
+ property :label, String, :default => "Submit", :required => true
4
+
5
+ self.renderer = Forms::SubmitButtonRenderer
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Forms::Fields
2
+ class TextArea < Field
3
+ property :label, String, :required => true
4
+ property :required, Boolean, :default => false
5
+
6
+ self.renderer = Forms::TextAreaRenderer
7
+ end
8
+ end