Shadowfiend / awesome_fields

A Rails plugin to improve forms. Includes a better FormBuilder and DRYness for form fields.

This URL has Read+Write access

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