public
Description: A Rails plugin to improve forms. Includes a better FormBuilder and DRYness for form fields.
Homepage: http://shadowfiend.posterous.com/
Clone URL: git://github.com/Shadowfiend/awesome_fields.git
awesome_fields / lib / awesome_fields / lined_builder.rb
100644 184 lines (165 sloc) 7.415 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
module AwesomeFields
  # This class presents a builder that provides a per-field block-level element.
  # The class also takes care of labeling fields, as well as adding errors to
  # the fields when needed.
  #
  # == Label
  #
  # Each field method that has been redefined in this builder uses the field
  # name as a label by default (converted to the label text by #humanize-ing it.
  # It also takes an optional <tt>:label</tt> parameter that specifies label
  # text other than the humanized field name. Whatever the field, it is placed
  # in label tag that is linked to the generated field when possible.
  #
  # In addition, the <tt>:no_label</tt> option may be passed to omit the label
  # when generating the field.
  #
  # == Wrapping div
  #
  # When producing a field, the field is wrapped with a div whose CSS class is
  # +form_line+. If the field has errors, that class is instead
  # +form_line_with_errors+. This div is, obviously, a block-level element by
  # default, but can be floated or what have you if needed.
  #
  # == Errors
  #
  # As mentioned above, if the field has an error, the wrapping div is given the
  # +form_line_with_errors+ CSS class instead of the regular +form_line+ class.
  # Additionally, the error text is placed within the containing +form_line+ div
  # within its own div, whose class is +field_error+.
  #
  # The error message is assembled in parts: first, the field is checked for
  # errors. If it has more than one error, these are turned into a sentence via
  # a call to +to_sentence+. Then, the error text has the label prepended to it.
  # By default, the label is the field name, so the error text will have the
  # field name, humanized, prepended to it. Finally, a period (.) is appended to
  # the end. This is the error message that is displayed.
  #
  # == Results
  #
  # The resulting structure of a field with errors is:
  #
  # <div class="form_line_with_errors">
  # <div class="field_error">Field should not be blank.</div>
  #
  # <label for="model_field">Field:</label>
  # <input type="text" id="model_field" name="model[field]" value="" />
  # </div>
  #
  # While one without errors has structure:
  #
  # <div class="form_line">
  # <label for="model_field">Field:</label>
  # <input type="text" id="model_field" name="model[field]" value="" />
  # </div>
  #
  # And one with no errors and no label has structure:
  #
  # <div class="form_line">
  # <input type="text" id="model_field" name="model[field]" value="" />
  # </div>
  #
  # == <tt>:long</tt> option
  #
  # All helpers support a <tt>:long</tt> option in case the field is meant to
  # contain `long' content. For most fields, all this means is that the label
  # for the field will receive the CSS class +long+ if that option is set to
  # true.
  #
  # Currently the only exception to this is +text_area+, which makes use of the
  # <tt>:long</tt> option to modify the default rows and columns of the text
  # area. If the text area is not `long', then it has 10 rows and 30 columns. If
  # it is `long', then it has 20 rows and 70 columns.
  #
  # == Submit button
  #
  # This builder also adds a method +submit_button+ (which is aliased to the
  # default +submit+) that wraps the submit button in a div of class
  # +form_buttons+.
  class LinedBuilder < ActionView::Helpers::FormBuilder
    def text_field(label, options = {})
      labeled_field(label, options) { super label, options }
    end
 
    def password_field(label, options = {})
      labeled_field(label, options) { super label, options }
    end
 
    def file_field(label, options = {})
      labeled_field(label, options) { super label, options }
    end
 
    def check_box(label, options = {})
      labeled_field(label, options) { super label, options }
    end
 
    # Takes the additional option <tt>:long</tt> which, if set to true, will, in
    # addition to setting the label's CSS class to +long+ (which happens for all
    # other fields to), change the default rows and columns to 20x70 (instead of
    # the non-long 10x30 defaults).
    def text_area(label, options = {})
      if options[:long]
        options.reverse_merge!({ :rows => 20, :cols => 70 })
      else
        options.reverse_merge!({ :rows => 10, :cols => 30 })
      end
 
      labeled_field(label, options) { super label, options }
    end
 
    # Produces a date select. The date select has a default order of month, day,
    # year. Aliased as +date_field+ for uniformity with other field invocations
    # and for good interoperability with the +AwesomeFields+ field helpers.
    def date_select(label, options = {})
      options.reverse_merge!({ :order => [ :month, :day, :year ] })
 
      labeled_field(label, options) { super label, options }
    end
    alias_method :date_field, :date_select
 
    # Produces a select box. If the html_options contains the <tt>:multiple</tt>
    # option and it is set to true, then the default size is set to 5.
    def select(label, choices, options = {}, html_options = {})
      err = error_on label, options
 
      html_options.reverse_merge!({ :size => 5 }) unless html_options[:multiple].nil?
 
      @template.content_tag( 'div',
        (err ? err : '') + label_tag( label, options ) +
          super,
        :class => err ? 'form_line_with_errors' : 'form_line' )
    end
 
    # Produces a submit button wrapped in a div of class +form_buttons+. The
    # label is turned into a string and humanized, so it can be a string,
    # potentially underscored.
    #
    # Aliased to +submit+, which is the default Rails name for this method.
    def submit_button(label = 'submit', options = {})
      @template.content_tag 'div',
        @template.submit_tag(label.to_s.humanize),
        :class => 'form_buttons'
    end
    alias_method :submit, :submit_button
 
   protected
    # Produces a labeled field given the label, the options, and a block that
   # will generate the appropriate field content.
    def labeled_field(attr, options, &content_gen)
      err = error_on attr, options
      after = options.delete(:after) || ''
 
      @template.content_tag 'div',
        (err ? err : '') + label_tag(attr, options) + content_gen.call + after,
        :class => (err ? 'form_line_with_errors' : 'form_line')
    end
 
    # Produces the appropriate label tag for the given attribute, including
    # returning nothing if the :no_label option was passed and using the :label
    # option instead of the attribute name if it was provided.
    def label_tag(attr_name, options = {})
      return '' if options.delete(:no_label)
 
      @template.content_tag 'label',
        "#{(options.delete(:label) || attr_name.to_s.humanize)}:",
        :for => "#{@object_name}_#{attr_name}",
        :class => (options[:long] ? 'long' : '')
    end
 
    # Produces a formatted version of the error on the given attribute. If a
    # label was provided, includes that instead of the attribute name.
    # Prefixes the error with the label or the attribute name and postfixes it
    # with a `.'. Returns a div string with the class field_error.
    def error_on(attr, options)
      errors = @object.errors[attr]
      return nil unless errors
 
      errors = errors.to_sentence if errors.respond_to?(:to_sentence)
 
      @template.content_tag 'div',
        "#{options[:label] || attr.to_s.humanize} #{errors}.",
        :class => 'field_error'
    end
  end