case_form 0.0.3

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 (88) hide show
  1. data/CHANGELOG.rdoc +1 -0
  2. data/MIT-LICENSE.rdoc +20 -0
  3. data/Manifest +86 -0
  4. data/README.rdoc +0 -0
  5. data/Rakefile +30 -0
  6. data/TODO.rdoc +7 -0
  7. data/case_form.gemspec +31 -0
  8. data/lib/case_form.rb +88 -0
  9. data/lib/case_form/associations.rb +50 -0
  10. data/lib/case_form/buttons.rb +175 -0
  11. data/lib/case_form/core_ext/form_helper.rb +54 -0
  12. data/lib/case_form/core_ext/layout_helper.rb +16 -0
  13. data/lib/case_form/core_ext/sentence_error.rb +38 -0
  14. data/lib/case_form/element.rb +40 -0
  15. data/lib/case_form/element/base.rb +95 -0
  16. data/lib/case_form/element/button.rb +64 -0
  17. data/lib/case_form/element/error.rb +54 -0
  18. data/lib/case_form/element/errors/complex_error.rb +107 -0
  19. data/lib/case_form/element/errors/simple_error.rb +76 -0
  20. data/lib/case_form/element/fieldset.rb +35 -0
  21. data/lib/case_form/element/hint.rb +54 -0
  22. data/lib/case_form/element/input.rb +106 -0
  23. data/lib/case_form/element/inputs/collection/checkbox_input.rb +36 -0
  24. data/lib/case_form/element/inputs/collection/radio_input.rb +27 -0
  25. data/lib/case_form/element/inputs/collection/select_input.rb +22 -0
  26. data/lib/case_form/element/inputs/collection_input.rb +89 -0
  27. data/lib/case_form/element/inputs/datetime/date_input.rb +45 -0
  28. data/lib/case_form/element/inputs/datetime/date_time_input.rb +50 -0
  29. data/lib/case_form/element/inputs/datetime/time_input.rb +34 -0
  30. data/lib/case_form/element/inputs/datetime/time_zone_input.rb +24 -0
  31. data/lib/case_form/element/inputs/file_input.rb +13 -0
  32. data/lib/case_form/element/inputs/hidden_input.rb +17 -0
  33. data/lib/case_form/element/inputs/number_input.rb +42 -0
  34. data/lib/case_form/element/inputs/search_input.rb +32 -0
  35. data/lib/case_form/element/inputs/string_input.rb +18 -0
  36. data/lib/case_form/element/inputs/text_input.rb +19 -0
  37. data/lib/case_form/element/label.rb +52 -0
  38. data/lib/case_form/element/nested_model.rb +105 -0
  39. data/lib/case_form/element/nested_models/handle.rb +18 -0
  40. data/lib/case_form/element/nested_models/handles/destructor_handle.rb +47 -0
  41. data/lib/case_form/element/nested_models/handles/generator_handle.rb +55 -0
  42. data/lib/case_form/element_ext/associationable.rb +54 -0
  43. data/lib/case_form/element_ext/columnable.rb +21 -0
  44. data/lib/case_form/element_ext/naming.rb +17 -0
  45. data/lib/case_form/element_ext/validationable.rb +13 -0
  46. data/lib/case_form/errors.rb +189 -0
  47. data/lib/case_form/form_builder.rb +11 -0
  48. data/lib/case_form/inputs.rb +1095 -0
  49. data/lib/case_form/labels.rb +102 -0
  50. data/lib/case_form/version.rb +6 -0
  51. data/lib/generators/case_form/install_generator.rb +33 -0
  52. data/lib/generators/case_form/templates/case_form.rb +63 -0
  53. data/lib/generators/case_form/templates/javascripts/jquery.case_form.js +10 -0
  54. data/lib/generators/case_form/templates/javascripts/prototype.case_form.js +0 -0
  55. data/lib/generators/case_form/templates/locales/en.yml +28 -0
  56. data/lib/generators/case_form/templates/locales/pl.yml +28 -0
  57. data/lib/generators/case_form/templates/stylesheets/stylesheet.css +93 -0
  58. data/lib/generators/case_form/templates/stylesheets/stylesheet_changes.css +1 -0
  59. data/lib/generators/case_form/uninstall_generator.rb +30 -0
  60. data/rails/init.rb +1 -0
  61. data/test/element/button_test.rb +85 -0
  62. data/test/element/errors/complex_error_test.rb +140 -0
  63. data/test/element/errors/simple_error_test.rb +92 -0
  64. data/test/element/fieldset_test.rb +28 -0
  65. data/test/element/hint_test.rb +81 -0
  66. data/test/element/input_test.rb +197 -0
  67. data/test/element/inputs/collection/checkbox_input_test.rb +176 -0
  68. data/test/element/inputs/collection/radio_input_test.rb +156 -0
  69. data/test/element/inputs/collection/select_input_test.rb +152 -0
  70. data/test/element/inputs/datetime/date_input_test.rb +160 -0
  71. data/test/element/inputs/datetime/datetime_input_test.rb +227 -0
  72. data/test/element/inputs/datetime/time_input_test.rb +72 -0
  73. data/test/element/inputs/datetime/time_zone_input_test.rb +42 -0
  74. data/test/element/inputs/file_input_test.rb +13 -0
  75. data/test/element/inputs/hidden_input_test.rb +13 -0
  76. data/test/element/inputs/number_input_test.rb +50 -0
  77. data/test/element/inputs/search_input_test.rb +13 -0
  78. data/test/element/inputs/string_input_test.rb +33 -0
  79. data/test/element/inputs/text_input_test.rb +13 -0
  80. data/test/element/label_test.rb +62 -0
  81. data/test/element/nested_model_test.rb +163 -0
  82. data/test/element/nested_models/handles/destructor_handle_test.rb +35 -0
  83. data/test/element/nested_models/handles/generator_handle_test.rb +27 -0
  84. data/test/form_builder_test.rb +25 -0
  85. data/test/form_helper_test.rb +15 -0
  86. data/test/lib/models.rb +268 -0
  87. data/test/test_helper.rb +74 -0
  88. metadata +235 -0
@@ -0,0 +1,54 @@
1
+ module CaseForm
2
+ module FormHelper
3
+ @@default_field_error_proc = nil
4
+
5
+ # Override the default ActiveRecordHelper behaviour of wrapping the input.
6
+ # This gets taken care of semantically by adding an error class to the wrapper tag
7
+ # containing the input.
8
+ FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
9
+ html_tag
10
+ end
11
+
12
+ def with_custom_field_error_proc(&block)
13
+ @@default_field_error_proc = ::ActionView::Base.field_error_proc
14
+ ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
15
+ result = yield
16
+ ::ActionView::Base.field_error_proc = @@default_field_error_proc
17
+ result
18
+ end
19
+
20
+ # Define case_form_for and case_fields_for
21
+ [:form_for, :fields_for].each do |helper|
22
+ class_eval <<-METHOD, __FILE__, __LINE__
23
+ def case_#{helper}(record_or_name_or_array, *args, &block)
24
+ options = args.extract_options!
25
+ options[:builder] = CaseForm::FormBuilder
26
+ css_class = case record_or_name_or_array
27
+ when String, Symbol then record_or_name_or_array.to_s
28
+ when Array then dom_class(record_or_name_or_array.last)
29
+ else dom_class(record_or_name_or_array)
30
+ end
31
+ options[:html] ||= {}
32
+ options[:html][:class] = "case_form \#{css_class} \#{options[:html][:class]}".strip
33
+
34
+ with_custom_field_error_proc do
35
+ #{helper}(record_or_name_or_array, *(args << options), &block)
36
+ end
37
+ end
38
+ METHOD
39
+ end
40
+
41
+ # Define remote_case_form_for
42
+ class_eval <<-METHOD
43
+ def remote_case_form_for(record_or_name_or_array, *args, &block)
44
+ options = args.extract_options!
45
+ options[:remote] = true
46
+
47
+ case_form_for(record_or_name_or_array, *(args << options), &block)
48
+ end
49
+ METHOD
50
+ end
51
+ end
52
+
53
+ # Include CaseForm
54
+ ActionView::Helpers.send :include, CaseForm::FormHelper
@@ -0,0 +1,16 @@
1
+ # coding: utf-8
2
+ module CaseForm
3
+ module LayoutHelper
4
+ def case_form_stylesheet_link_tag
5
+ stylesheet_link_tag("case_form_changes", "case_form")
6
+ end
7
+ alias_method :case_form_stylesheet, :case_form_stylesheet_link_tag
8
+
9
+ def case_form_javascript_include_tag
10
+ javascript_include_tag("case_form")
11
+ end
12
+ alias_method :case_form_js, :case_form_javascript_include_tag
13
+ end
14
+ end
15
+
16
+ ActionView::Base.send :include, CaseForm::LayoutHelper
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ module CaseForm
3
+ module SentenceError
4
+ # Returns all the full error messages as a sentences in an array.
5
+ #
6
+ # class User
7
+ # validates_presence_of :password, :address, :email
8
+ # validates_length_of :password, :in => 5..30
9
+ # validates_confirmation_of :password
10
+ # end
11
+ #
12
+ # user = User.create(:address => '123 First St.')
13
+ # user.errors.full_sentences # =>
14
+ # ["Password is too short (minimum is 5 characters), can't be blank and should match confirmation"]
15
+ #
16
+ def full_sentences(options={})
17
+ full_sentences = []
18
+
19
+ keys.each do |attribute|
20
+ if attribute == :base
21
+ self[attribute].each {|m| full_sentences << m }
22
+ else
23
+ attr_name = attribute.to_s.gsub('.', '_').humanize
24
+ attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
25
+
26
+ messages = Array.wrap(self[attribute])
27
+ messages.collect! { |m| I18n.t(:"errors.format", :message => m, :attribute => nil).strip }
28
+
29
+ full_sentences << (attr_name + " " + messages.to_sentence(options))
30
+ end
31
+ end
32
+
33
+ full_sentences
34
+ end
35
+ end
36
+ end
37
+
38
+ ActiveModel::Errors.send :include, CaseForm::SentenceError
@@ -0,0 +1,40 @@
1
+
2
+
3
+ # coding: utf-8
4
+ module CaseForm
5
+ module Element
6
+ autoload :Base, 'case_form/element/base'
7
+
8
+ autoload :Fieldset, 'case_form/element/fieldset'
9
+
10
+ autoload :Input, 'case_form/element/input'
11
+ autoload :StringInput, 'case_form/element/inputs/string_input'
12
+ autoload :TextInput, 'case_form/element/inputs/text_input'
13
+ autoload :HiddenInput, 'case_form/element/inputs/hidden_input'
14
+ autoload :SearchInput, 'case_form/element/inputs/search_input'
15
+ autoload :FileInput, 'case_form/element/inputs/file_input'
16
+ autoload :DateTimeInput, 'case_form/element/inputs/datetime/date_time_input'
17
+ autoload :DateInput, 'case_form/element/inputs/datetime/date_input'
18
+ autoload :TimeInput, 'case_form/element/inputs/datetime/time_input'
19
+ autoload :TimeZoneInput, 'case_form/element/inputs/datetime/time_zone_input'
20
+ autoload :NumberInput, 'case_form/element/inputs/number_input'
21
+ autoload :CollectionInput, 'case_form/element/inputs/collection_input'
22
+ autoload :SelectInput, 'case_form/element/inputs/collection/select_input'
23
+ autoload :CheckboxInput, 'case_form/element/inputs/collection/checkbox_input'
24
+ autoload :RadioInput, 'case_form/element/inputs/collection/radio_input'
25
+
26
+ autoload :Label, 'case_form/element/label'
27
+ autoload :Hint, 'case_form/element/hint'
28
+
29
+ autoload :Button, 'case_form/element/button'
30
+
31
+ autoload :Error, 'case_form/element/error'
32
+ autoload :SimpleError, 'case_form/element/errors/simple_error'
33
+ autoload :ComplexError, 'case_form/element/errors/complex_error'
34
+
35
+ autoload :NestedModel, 'case_form/element/nested_model'
36
+ autoload :Handle, 'case_form/element/nested_models/handle'
37
+ autoload :GeneratorHandle, 'case_form/element/nested_models/handles/generator_handle'
38
+ autoload :DestructorHandle, 'case_form/element/nested_models/handles/destructor_handle'
39
+ end
40
+ end
@@ -0,0 +1,95 @@
1
+ require 'case_form/element_ext/naming'
2
+ require 'case_form/element_ext/columnable'
3
+ require 'case_form/element_ext/validationable'
4
+ require 'case_form/element_ext/associationable'
5
+
6
+ # coding: utf-8
7
+ module CaseForm
8
+ module Element
9
+ class Base
10
+ include ElementExt::Naming
11
+
12
+ HTML_OPTIONS = [:id, :class, :style, :readonly, :disabled, :type, :name,
13
+ :autofocus, :placeholder, :required, :multiple, :checked, :selected,
14
+ :for, :min, :max, :step, :pattern, :size, :maxlength, :cols, :rows]
15
+
16
+ class_inheritable_accessor :allowed_options
17
+ self.allowed_options = [:custom, :id, :class, :style]
18
+
19
+ attr_accessor :builder, :options
20
+
21
+ def initialize(builder, options={})
22
+ @builder, @options = builder, options.symbolize_keys!
23
+ validate_options
24
+ default_options
25
+ end
26
+
27
+ private
28
+ def validate_options #:nodoc same as assert_valid_keys for Hash
29
+ allowed = self.class.allowed_options.flatten
30
+ banned = options.keys - allowed
31
+ raise(ArgumentError, "Unknown option(s): #{banned.join(', ')}. Available options: #{allowed.join(', ')}") unless banned.empty?
32
+ end
33
+
34
+ def default_options
35
+ options ||= {}
36
+ options[:class] ||= []
37
+ options[:id] ||= []
38
+ end
39
+
40
+ def wrapper_options
41
+ wrapper_options = {}
42
+ wrapper_options[:class] = []
43
+ wrapper_options
44
+ end
45
+
46
+ def html_options
47
+ options.slice(*HTML_OPTIONS).merge(custom_options)
48
+ end
49
+
50
+ def custom_options
51
+ custom_options = {}
52
+ return custom_options unless options.has_key?(:custom)
53
+ raise(ArgumentError, "Invalid :custom options! Custom options should be hash with key and value.") unless options[:custom].is_a?(Hash)
54
+ options.delete(:custom).each { |key, value| custom_options[:"data-#{key}"] = value }
55
+ custom_options
56
+ end
57
+
58
+ def object_name
59
+ @builder.object_name
60
+ end
61
+
62
+ def object
63
+ @builder.object
64
+ end
65
+
66
+ def template
67
+ builder.template
68
+ end
69
+
70
+ def required?
71
+ if options.has_key?(:required)
72
+ options[:required]
73
+ elsif respond_to?(:validationable?) && validationable? && method_validations.any? { |v| v.kind == :presence }
74
+ true
75
+ elsif respond_to?(:columnable?) && object_column.present? && object_column.null == false
76
+ true
77
+ else
78
+ CaseForm.all_fields_required
79
+ end
80
+ end
81
+
82
+ def new?
83
+ object.new_record?
84
+ end
85
+
86
+ def action
87
+ new? ? :new : :edit
88
+ end
89
+
90
+ def wrapper_tag
91
+ CaseForm.wrapper_tag
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,64 @@
1
+ # coding: utf-8
2
+ module CaseForm
3
+ module Element
4
+ class Button < Base
5
+ self.allowed_options << [:disabled, :text, :as]
6
+
7
+ # Generate button with defined text and HTML options
8
+ #
9
+ def generate
10
+ template.content_tag wrapper_tag, button, wrapper_options
11
+ end
12
+
13
+ private
14
+ # Distribute default options for button.
15
+ #
16
+ def default_options
17
+ options[:as] ||= :commit
18
+ options[:class] ||= [:button]
19
+ options[:id] ||= "#{builder.object_name}_#{button_type}"
20
+ options[:type] ||= button_type
21
+ options[:name] ||= button_type
22
+ end
23
+
24
+ # Distribute wrapper options for button.
25
+ #
26
+ def wrapper_options
27
+ wrapper_options = super
28
+ wrapper_options[:class] << :element
29
+ wrapper_options
30
+ end
31
+
32
+ # Create submit button
33
+ #
34
+ def button
35
+ builder.submit(text, html_options)
36
+ end
37
+
38
+ # Generate button text.
39
+ #
40
+ def text
41
+ options.delete(:text) || translated_text
42
+ end
43
+
44
+ # Translate button text
45
+ def translated_text
46
+ lookups = []
47
+ lookups << :"#{builder.object_name}.#{button_type}"
48
+ lookups << :"#{button_type}"
49
+ lookups << default_button_text
50
+ I18n.t(lookups.shift, :scope => :"case_form.buttons", :default => lookups)
51
+ end
52
+
53
+ # Default button text
54
+ def default_button_text
55
+ button_type == :submit ? (new? ? "Create" : "Update") : "Reset"
56
+ end
57
+
58
+ # Button type (available: :commit and :reset)
59
+ def button_type
60
+ options[:as]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,54 @@
1
+ module CaseForm
2
+ module Element
3
+ class Error < Base
4
+ self.allowed_options << [:tag, :as, :connector, :last_connector]
5
+
6
+ private
7
+ # Defined error HTML tag
8
+ #
9
+ def error_tag
10
+ options.delete(:tag) || CaseForm.error_tag
11
+ end
12
+
13
+ # Defined error type
14
+ #
15
+ # == Allowed types:
16
+ # * :sentence
17
+ # * :list
18
+ #
19
+ def error_type
20
+ options.delete(:as) || CaseForm.error_type
21
+ end
22
+
23
+ # Defined error connector (default ', ')
24
+ #
25
+ def error_connector
26
+ options.delete(:connector) || CaseForm.error_connector
27
+ end
28
+
29
+ # Defined last error connector (default ' and '). Used in sentence with many errors on one method.
30
+ #
31
+ def last_error_connector
32
+ options.delete(:last_connector) || CaseForm.last_error_connector
33
+ end
34
+
35
+ # Type of error
36
+ def error_messages
37
+ list? ? translated_list : translated_sentence
38
+ end
39
+
40
+ def list?
41
+ error_type == :list
42
+ end
43
+
44
+ def sentence?
45
+ error_type == :sentence
46
+ end
47
+
48
+ # OrderedHash with all errors
49
+ def errors
50
+ object.errors
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,107 @@
1
+ module CaseForm
2
+ module Element
3
+ class ComplexError < Error
4
+ self.allowed_options << [:header_tag, :header_message, :message_tag, :message]
5
+
6
+ # Generate all error messages as HTML <ul> list with optional header and message.
7
+ #
8
+ def generate
9
+ contents = []
10
+ contents << template.content_tag(header_tag, header_message) unless options[:header_message] == false
11
+ contents << template.content_tag(message_tag, message) unless options[:message] == false
12
+ contents << template.content_tag(:ul, error_messages)
13
+
14
+ template.content_tag(error_tag, contents.join.html_safe, html_options)
15
+ end
16
+
17
+ private
18
+ # Distribute default options for complex errors.
19
+ #
20
+ def default_options
21
+ options[:class] ||= [:errors]
22
+ options[:id] ||= "#{object_name}_errors"
23
+ end
24
+
25
+ # Error messages as list.
26
+ #
27
+ # == Example:
28
+ #
29
+ # # object.errors = { :name => ['can't be blank', 'should be uniq'],
30
+ # :price => 'should be more than 10' }
31
+ #
32
+ # <ul>
33
+ # <li>Name can't be blank</li>
34
+ # <li>Name should be uniq</li>
35
+ # <li>Price should be more than 10</li>
36
+ # </ul>
37
+ #
38
+ def translated_list
39
+ (errors.full_messages.collect { |msg| template.content_tag :li, msg }).join.html_safe
40
+ end
41
+
42
+ # Error messages as sentence.
43
+ #
44
+ # == Example:
45
+ #
46
+ # # object.errors = { :name => ['can't be blank', 'should be uniq'],
47
+ # :price => 'should be more than 10' }
48
+ #
49
+ # <ul>
50
+ # <li>Name can't be blank and should be uniq</li>
51
+ # <li>Price should be more than 10</li>
52
+ # </ul>
53
+ #
54
+ def translated_sentence
55
+ (errors.full_sentences(:words_connector => error_connector,
56
+ :last_word_connector => last_error_connector,
57
+ :two_words_connector => last_error_connector).collect { |msg| template.content_tag :li, msg }).join.html_safe
58
+ end
59
+
60
+ # Defined error header message HTML tag.
61
+ #
62
+ def header_tag
63
+ options.delete(:header_tag) || CaseForm.complex_error_header_tag
64
+ end
65
+
66
+ # Defined error header message from option or I18n.
67
+ #
68
+ def header_message
69
+ options.delete(:header_message) || translated_header_message
70
+ end
71
+
72
+ # Translated error header message by I18n.
73
+ #
74
+ def translated_header_message
75
+ lookups = []
76
+ lookups << :"activemodel.errors.template.header_message"
77
+ lookups << :"activerecord.errors.template.header_message"
78
+ lookups << :"case_form.errors.template.header_message"
79
+ lookups << "Some errors prohibited this object from being saved"
80
+ I18n.t(lookups.shift, :default => lookups)
81
+ end
82
+
83
+ # Defined error message HTML tag.
84
+ #
85
+ def message_tag
86
+ options.delete(:message_tag) || CaseForm.complex_error_message_tag
87
+ end
88
+
89
+ # Defined error message from option or I18n.
90
+ #
91
+ def message
92
+ options.delete(:message) || translated_message
93
+ end
94
+
95
+ # Translated error message by I18n.
96
+ #
97
+ def translated_message
98
+ lookups = []
99
+ lookups << :"activemodel.errors.template.message"
100
+ lookups << :"activerecord.errors.template.message"
101
+ lookups << :"case_form.errors.template.message"
102
+ lookups << "There were problems with the following fields:"
103
+ I18n.t(lookups.shift, :default => lookups)
104
+ end
105
+ end
106
+ end
107
+ end