nuatt-formtastic 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +635 -0
  3. data/lib/formtastic.rb +24 -0
  4. data/lib/formtastic/form_builder.rb +75 -0
  5. data/lib/formtastic/helpers.rb +15 -0
  6. data/lib/formtastic/helpers/buttons_helper.rb +277 -0
  7. data/lib/formtastic/helpers/errors_helper.rb +124 -0
  8. data/lib/formtastic/helpers/fieldset_wrapper.rb +62 -0
  9. data/lib/formtastic/helpers/file_column_detection.rb +16 -0
  10. data/lib/formtastic/helpers/form_helper.rb +221 -0
  11. data/lib/formtastic/helpers/input_helper.rb +357 -0
  12. data/lib/formtastic/helpers/inputs_helper.rb +381 -0
  13. data/lib/formtastic/helpers/reflection.rb +12 -0
  14. data/lib/formtastic/helpers/semantic_form_helper.rb +11 -0
  15. data/lib/formtastic/html_attributes.rb +21 -0
  16. data/lib/formtastic/i18n.rb +32 -0
  17. data/lib/formtastic/inputs.rb +29 -0
  18. data/lib/formtastic/inputs/base.rb +50 -0
  19. data/lib/formtastic/inputs/base/associations.rb +33 -0
  20. data/lib/formtastic/inputs/base/choices.rb +88 -0
  21. data/lib/formtastic/inputs/base/collections.rb +94 -0
  22. data/lib/formtastic/inputs/base/database.rb +17 -0
  23. data/lib/formtastic/inputs/base/errors.rb +58 -0
  24. data/lib/formtastic/inputs/base/fileish.rb +23 -0
  25. data/lib/formtastic/inputs/base/grouped_collections.rb +77 -0
  26. data/lib/formtastic/inputs/base/hints.rb +31 -0
  27. data/lib/formtastic/inputs/base/html.rb +51 -0
  28. data/lib/formtastic/inputs/base/labelling.rb +53 -0
  29. data/lib/formtastic/inputs/base/naming.rb +54 -0
  30. data/lib/formtastic/inputs/base/options.rb +18 -0
  31. data/lib/formtastic/inputs/base/stringish.rb +30 -0
  32. data/lib/formtastic/inputs/base/timeish.rb +125 -0
  33. data/lib/formtastic/inputs/base/validations.rb +125 -0
  34. data/lib/formtastic/inputs/base/wrapping.rb +38 -0
  35. data/lib/formtastic/inputs/boolean_input.rb +87 -0
  36. data/lib/formtastic/inputs/check_boxes_input.rb +169 -0
  37. data/lib/formtastic/inputs/country_input.rb +66 -0
  38. data/lib/formtastic/inputs/date_input.rb +14 -0
  39. data/lib/formtastic/inputs/datetime_input.rb +9 -0
  40. data/lib/formtastic/inputs/email_input.rb +40 -0
  41. data/lib/formtastic/inputs/file_input.rb +42 -0
  42. data/lib/formtastic/inputs/hidden_input.rb +66 -0
  43. data/lib/formtastic/inputs/number_input.rb +72 -0
  44. data/lib/formtastic/inputs/numeric_input.rb +20 -0
  45. data/lib/formtastic/inputs/password_input.rb +40 -0
  46. data/lib/formtastic/inputs/phone_input.rb +41 -0
  47. data/lib/formtastic/inputs/radio_input.rb +146 -0
  48. data/lib/formtastic/inputs/search_input.rb +40 -0
  49. data/lib/formtastic/inputs/select_input.rb +208 -0
  50. data/lib/formtastic/inputs/string_input.rb +34 -0
  51. data/lib/formtastic/inputs/text_input.rb +47 -0
  52. data/lib/formtastic/inputs/time_input.rb +14 -0
  53. data/lib/formtastic/inputs/time_zone_input.rb +48 -0
  54. data/lib/formtastic/inputs/url_input.rb +40 -0
  55. data/lib/formtastic/localized_string.rb +96 -0
  56. data/lib/formtastic/railtie.rb +12 -0
  57. data/lib/formtastic/semantic_form_builder.rb +11 -0
  58. data/lib/formtastic/util.rb +25 -0
  59. data/lib/generators/formtastic/form/form_generator.rb +95 -0
  60. data/lib/generators/formtastic/install/install_generator.rb +23 -0
  61. data/lib/generators/templates/_form.html.erb +7 -0
  62. data/lib/generators/templates/_form.html.haml +5 -0
  63. data/lib/generators/templates/formtastic.css +145 -0
  64. data/lib/generators/templates/formtastic.rb +74 -0
  65. data/lib/generators/templates/formtastic_changes.css +14 -0
  66. data/lib/locale/en.yml +7 -0
  67. data/lib/tasks/verify_rcov.rb +44 -0
  68. metadata +206 -19
@@ -0,0 +1,381 @@
1
+ module Formtastic
2
+ module Helpers
3
+
4
+ # {#inputs} is used to wrap a series of form items in a `<fieldset>` and `<ol>`, with each item
5
+ # in the list containing the markup representing a single {#input}.
6
+ #
7
+ # {#inputs} is usually called with a block containing a series of {#input} methods:
8
+ #
9
+ # <%= semantic_form_for @post do |f| %>
10
+ # <%= f.inputs do %>
11
+ # <%= f.input :title %>
12
+ # <%= f.input :body %>
13
+ # <% end %>
14
+ # <% end %>
15
+ #
16
+ # The HTML output will be something like:
17
+ #
18
+ # <form class="formtastic" method="post" action="...">
19
+ # <fieldset>
20
+ # <ol>
21
+ # <li class="string required" id="post_title_input">
22
+ # ...
23
+ # </li>
24
+ # <li class="text required" id="post_body_input">
25
+ # ...
26
+ # </li>
27
+ # </ol>
28
+ # </fieldset>
29
+ # </form>
30
+ #
31
+ # It's important to note that the `semantic_form_for` and {#inputs} blocks wrap the
32
+ # standard Rails `form_for` helper and FormBuilder, so you have full access to every standard
33
+ # Rails form helper, with any HTML markup and ERB syntax, allowing you to "break free" from
34
+ # Formtastic when it doesn't suit:
35
+ #
36
+ # <%= semantic_form_for @post do |f| %>
37
+ # <%= f.inputs do %>
38
+ # <%= f.input :title %>
39
+ # <li>
40
+ # <%= f.text_area :body %>
41
+ # <li>
42
+ # <% end %>
43
+ # <% end %>
44
+ #
45
+ # @see Formtastic::Helpers::InputHelper#input
46
+ module InputsHelper
47
+ include Formtastic::Helpers::FieldsetWrapper
48
+ include Formtastic::LocalizedString
49
+
50
+ # Which columns to skip when automatically rendering a form without any fields specified.
51
+ SKIPPED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
52
+
53
+
54
+ # {#inputs} creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
55
+ # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
56
+ # or with a list of fields (accepting all default arguments and options). These two examples
57
+ # are functionally equivalent:
58
+ #
59
+ # # With a block:
60
+ # <% semantic_form_for @post do |form| %>
61
+ # <% f.inputs do %>
62
+ # <%= f.input :title %>
63
+ # <%= f.input :body %>
64
+ # <% end %>
65
+ # <% end %>
66
+ #
67
+ # # With a list of fields (short hand syntax):
68
+ # <% semantic_form_for @post do |form| %>
69
+ # <%= f.inputs :title, :body %>
70
+ # <% end %>
71
+ #
72
+ # # Output:
73
+ # <form ...>
74
+ # <fieldset class="inputs">
75
+ # <ol>
76
+ # <li class="string">...</li>
77
+ # <li class="text">...</li>
78
+ # </ol>
79
+ # </fieldset>
80
+ # </form>
81
+ #
82
+ # **Quick Forms**
83
+ #
84
+ # Quick, scaffolding-style forms can be easily rendered for rapid early development if called
85
+ # without a block or a field list. In the case an input is rendered for **most** columns in
86
+ # the model's database table (like Rails' scaffolding) plus inputs for some model associations.
87
+ #
88
+ # In this case, all inputs are rendered with default options and arguments. You'll want more
89
+ # control than this in a production application, but it's a great way to get started, then
90
+ # come back later to customise the form with a field list or a block of inputs. Example:
91
+ #
92
+ # <% semantic_form_for @post do |form| %>
93
+ # <%= f.inputs %>
94
+ # <% end %>
95
+ #
96
+ # **Nested Attributes**
97
+ #
98
+ # One of the most complicated parts of Rails forms comes when nesting the inputs for
99
+ # attrinbutes on associated models. Formtastic can take the pain away for many (but not all)
100
+ # situations.
101
+ #
102
+ # Given the following models:
103
+ #
104
+ # # Models
105
+ # class User < ActiveRecord::Base
106
+ # has_one :profile
107
+ # accepts_nested_attributes_for :profile
108
+ # end
109
+ # class Profile < ActiveRecord::Base
110
+ # belongs_to :user
111
+ # end
112
+ #
113
+ # Formtastic provides a helper called `semantic_fields_for`, which wraps around Rails' built-in
114
+ # `fields_for` helper, allowing you to combine Rails-style nested fields with Formtastic inputs:
115
+ #
116
+ # <% semantic_form_for @user do |form| %>
117
+ # <%= f.inputs :name, :email %>
118
+ #
119
+ # <% f.semantic_fields_for :profile do |profile| %>
120
+ # <% profile.inputs do %>
121
+ # <%= profile.input :biography %>
122
+ # <%= profile.input :twitter_name %>
123
+ # <%= profile.input :shoe_size %>
124
+ # <% end %>
125
+ # <% end %>
126
+ # <% end %>
127
+ #
128
+ # {#inputs} also provides a DSL similar to `semantic_fields_for` to reduce the lines of code a
129
+ # little:
130
+ #
131
+ # <% semantic_form_for @user do |f| %>
132
+ # <%= f.inputs :name, :email %>
133
+ #
134
+ # <% f.inputs :for => :profile do %>
135
+ # <%= profile.input :biography %>
136
+ # <%= profile.input :twitter_name %>
137
+ # <%= profile.input :shoe_size %>
138
+ # <% end %>
139
+ # <% end %>
140
+ #
141
+ # The `:for` option also works with short hand syntax:
142
+ #
143
+ # <% semantic_form_for @post do |form| %>
144
+ # <%= f.inputs :name, :email %>
145
+ # <%= f.inputs :biography, :twitter_name, :shoe_size, :for => :profile %>
146
+ # <% end %>
147
+ #
148
+ # {#inputs} will always create a new `<fieldset>` wrapping, so only use it when it makes sense
149
+ # in the document structure and semantics (using `semantic_fields_for` otherwise).
150
+ #
151
+ # All options except `:name`, `:title` and `:for` will be passed down to the fieldset as HTML
152
+ # attributes (id, class, style, etc).
153
+ #
154
+ # When nesting `inputs()` inside another `inputs()` block, the nested content will
155
+ # automatically be wrapped in an `<li>` tag to preserve the HTML validity (a `<fieldset>`
156
+ # cannot be a direct descendant of an `<ol>`.
157
+ #
158
+ #
159
+ # @option *args :for [Symbol, ActiveModel, Array]
160
+ # The contents of this option is passed down to Rails' fields_for() helper, so it accepts the same values.
161
+ #
162
+ # @option *args :name [String]
163
+ # The optional name passed into the `<legend>` tag within the fieldset (alias of `:title`)
164
+ #
165
+ # @option *args :title [String]
166
+ # The optional name passed into the `<legend>` tag within the fieldset (alias of `:name`)
167
+ #
168
+ #
169
+ # @example Quick form: Render a scaffold-like set of inputs for automatically guessed attributes and simple associations on the model, with all default arguments and options
170
+ # <% semantic_form_for @post do |form| %>
171
+ # <%= f.inputs %>
172
+ # <% end %>
173
+ #
174
+ # @example Short hand: Render inputs for a named set of attributes and simple associations on the model, with all default arguments and options
175
+ # <% semantic_form_for @post do |form| %>
176
+ # <%= f.inputs, :title, :body, :user, :categories %>
177
+ # <% end %>
178
+ #
179
+ # @example Block: Render inputs for attributes and simple associations with full control over arguments and options
180
+ # <% semantic_form_for @post do |form| %>
181
+ # <%= f.inputs do %>
182
+ # <%= f.input :title ... %>
183
+ # <%= f.input :body ... %>
184
+ # <%= f.input :user ... %>
185
+ # <%= f.input :categories ... %>
186
+ # <% end %>
187
+ # <% end %>
188
+ #
189
+ # @example Multiple blocks: Render inputs in multiple fieldsets
190
+ # <% semantic_form_for @post do |form| %>
191
+ # <%= f.inputs do %>
192
+ # <%= f.input :title ... %>
193
+ # <%= f.input :body ... %>
194
+ # <% end %>
195
+ # <%= f.inputs do %>
196
+ # <%= f.input :user ... %>
197
+ # <%= f.input :categories ... %>
198
+ # <% end %>
199
+ # <% end %>
200
+ #
201
+ # @example Provide text for the `<legend>` to name a fieldset (with a block)
202
+ # <% semantic_form_for @post do |form| %>
203
+ # <%= f.inputs :name => 'Write something:' do %>
204
+ # <%= f.input :title ... %>
205
+ # <%= f.input :body ... %>
206
+ # <% end %>
207
+ # <%= f.inputs do :name => 'Advanced options:' do %>
208
+ # <%= f.input :user ... %>
209
+ # <%= f.input :categories ... %>
210
+ # <% end %>
211
+ # <% end %>
212
+ #
213
+ # @example Provide text for the `<legend>` to name a fieldset (with short hand)
214
+ # <% semantic_form_for @post do |form| %>
215
+ # <%= f.inputs :title, :body, :name => 'Write something:'%>
216
+ # <%= f.inputs :user, :cateogies, :name => 'Advanced options:' %>
217
+ # <% end %>
218
+ #
219
+ # @example Inputs for nested attributes (don't forget `accepts_nested_attributes_for` in your model, see Rails' `fields_for` documentation)
220
+ # <% semantic_form_for @user do |form| %>
221
+ # <%= f.inputs do %>
222
+ # <%= f.input :name ... %>
223
+ # <%= f.input :email ... %>
224
+ # <% end %>
225
+ # <%= f.inputs :for => :profile do |profile| %>
226
+ # <%= profile.input :user ... %>
227
+ # <%= profile.input :categories ... %>
228
+ # <% end %>
229
+ # <% end %>
230
+ #
231
+ # @example Inputs for nested record (don't forget `accepts_nested_attributes_for` in your model, see Rails' `fields_for` documentation)
232
+ # <% semantic_form_for @user do |form| %>
233
+ # <%= f.inputs do %>
234
+ # <%= f.input :name ... %>
235
+ # <%= f.input :email ... %>
236
+ # <% end %>
237
+ # <%= f.inputs :for => @user.profile do |profile| %>
238
+ # <%= profile.input :user ... %>
239
+ # <%= profile.input :categories ... %>
240
+ # <% end %>
241
+ # <% end %>
242
+ #
243
+ # @example Inputs for nested record with a different name (don't forget `accepts_nested_attributes_for` in your model, see Rails' `fields_for` documentation)
244
+ # <% semantic_form_for @user do |form| %>
245
+ # <%= f.inputs do %>
246
+ # <%= f.input :name ... %>
247
+ # <%= f.input :email ... %>
248
+ # <% end %>
249
+ # <%= f.inputs :for => [:user_profile, @user.profile] do |profile| %>
250
+ # <%= profile.input :user ... %>
251
+ # <%= profile.input :categories ... %>
252
+ # <% end %>
253
+ # <% end %>
254
+ #
255
+ # @example Nesting {#inputs} blocks requires an extra `<li>` tag for valid markup
256
+ # <% semantic_form_for @user do |form| %>
257
+ # <%= f.inputs do %>
258
+ # <%= f.input :name ... %>
259
+ # <%= f.input :email ... %>
260
+ # <li>
261
+ # <%= f.inputs :for => [:user_profile, @user.profile] do |profile| %>
262
+ # <%= profile.input :user ... %>
263
+ # <%= profile.input :categories ... %>
264
+ # <% end %>
265
+ # </li>
266
+ # <% end %>
267
+ # <% end %>
268
+ def inputs(*args, &block)
269
+ wrap_it = @already_in_an_inputs_block ? true : false
270
+ @already_in_an_inputs_block = true
271
+
272
+ title = field_set_title_from_args(*args)
273
+ html_options = args.extract_options!
274
+ html_options[:class] ||= "inputs"
275
+ html_options[:name] = title
276
+
277
+ out = begin
278
+ if html_options[:for] # Nested form
279
+ inputs_for_nested_attributes(*(args << html_options), &block)
280
+ elsif block_given?
281
+ field_set_and_list_wrapping(*(args << html_options), &block)
282
+ else
283
+ legend = args.shift if args.first.is_a?(::String)
284
+ args = default_columns_for_object if @object && args.empty?
285
+ contents = fieldset_contents_from_column_list(args)
286
+ args.unshift(legend) if legend.present?
287
+ field_set_and_list_wrapping(*((args << html_options) << contents))
288
+ end
289
+ end
290
+
291
+ out = template.content_tag(:li, out) if wrap_it
292
+ @already_in_an_inputs_block = false
293
+ out
294
+ end
295
+
296
+ protected
297
+
298
+ def default_columns_for_object
299
+ cols = association_columns(:belongs_to)
300
+ cols += content_columns
301
+ cols -= SKIPPED_COLUMNS
302
+ cols.compact
303
+ end
304
+
305
+ def fieldset_contents_from_column_list(columns)
306
+ columns.collect do |method|
307
+ if @object && (@object.class.reflect_on_association(method.to_sym) && @object.class.reflect_on_association(method.to_sym).options[:polymorphic] == true)
308
+ raise PolymorphicInputWithoutCollectionError.new("Please provide a collection for :#{method} input (you'll need to use block form syntax). Inputs for polymorphic associations can only be used when an explicit :collection is provided.")
309
+ end
310
+ input(method.to_sym)
311
+ end
312
+ end
313
+
314
+ # Collects association columns (relation columns) for the current form object class. Skips
315
+ # polymorphic associations because we can't guess which class to use for an automatically
316
+ # generated input.
317
+ def association_columns(*by_associations) #:nodoc:
318
+ if @object.present? && @object.class.respond_to?(:reflections)
319
+ @object.class.reflections.collect do |name, association_reflection|
320
+ if by_associations.present?
321
+ if by_associations.include?(association_reflection.macro) && association_reflection.options[:polymorphic] != true
322
+ name
323
+ end
324
+ else
325
+ name
326
+ end
327
+ end.compact
328
+ else
329
+ []
330
+ end
331
+ end
332
+
333
+ # Collects content columns (non-relation columns) for the current form object class.
334
+ def content_columns #:nodoc:
335
+ model_name.constantize.content_columns.collect { |c| c.name.to_sym }.compact rescue []
336
+ end
337
+
338
+ # Deals with :for option when it's supplied to inputs methods. Additional
339
+ # options to be passed down to :for should be supplied using :for_options
340
+ # key.
341
+ #
342
+ # It should raise an error if a block with arity zero is given.
343
+ def inputs_for_nested_attributes(*args, &block) #:nodoc:
344
+ options = args.extract_options!
345
+ args << options.merge!(:parent => { :builder => self, :for => options[:for] })
346
+
347
+ fields_for_block = if block_given?
348
+ raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
349
+ 'but the block does not accept any argument.' if block.arity <= 0
350
+ lambda do |f|
351
+ contents = f.inputs(*args){ block.call(f) }
352
+ template.concat(contents)
353
+ end
354
+ else
355
+ lambda do |f|
356
+ contents = f.inputs(*args)
357
+ template.concat(contents)
358
+ end
359
+ end
360
+
361
+ fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
362
+ semantic_fields_for(*fields_for_args, &fields_for_block)
363
+ end
364
+
365
+ def field_set_title_from_args(*args) #:nodoc:
366
+ options = args.extract_options!
367
+ options[:name] ||= options.delete(:title)
368
+ title = options[:name]
369
+
370
+ if title.blank?
371
+ valid_name_classes = [::String, ::Symbol]
372
+ valid_name_classes.delete(::Symbol) if !block_given? && (args.first.is_a?(::Symbol) && content_columns.include?(args.first))
373
+ title = args.shift if valid_name_classes.any? { |valid_name_class| args.first.is_a?(valid_name_class) }
374
+ end
375
+ title = localized_string(title, title, :title) if title.is_a?(::Symbol)
376
+ title
377
+ end
378
+
379
+ end
380
+ end
381
+ end
@@ -0,0 +1,12 @@
1
+ module Formtastic
2
+ module Helpers
3
+ # @private
4
+ module Reflection
5
+ # If an association method is passed in (f.input :author) try to find the
6
+ # reflection object.
7
+ def reflection_for(method) #:nodoc:
8
+ @object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ module Formtastic
2
+ # Quick hack/shim for anything expecting the old SemanticFormHelper module.
3
+ # TODO remove from 2.0 with a helpful upgrade path/warning.
4
+ # @private
5
+ module SemanticFormHelper
6
+ include Formtastic::Helpers::FormHelper
7
+ @@builder = Formtastic::Helpers::FormHelper.builder
8
+ @@default_form_class = Formtastic::Helpers::FormHelper.default_form_class
9
+ mattr_accessor :builder, :default_form_class
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module Formtastic
2
+ # @private
3
+ module HtmlAttributes
4
+
5
+ protected
6
+
7
+ def humanized_attribute_name(method)
8
+ if @object && @object.class.respond_to?(:human_attribute_name)
9
+ humanized_name = @object.class.human_attribute_name(method.to_s)
10
+ if humanized_name == method.to_s.send(:humanize)
11
+ method.to_s.send(label_str_method)
12
+ else
13
+ humanized_name
14
+ end
15
+ else
16
+ method.to_s.send(label_str_method)
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ module Formtastic
4
+ # @private
5
+ module I18n
6
+
7
+ DEFAULT_SCOPE = [:formtastic].freeze
8
+ DEFAULT_VALUES = YAML.load_file(File.expand_path("../../locale/en.yml", __FILE__))["en"]["formtastic"].freeze
9
+ SCOPES = [
10
+ '%{model}.%{nested_model}.%{action}.%{attribute}',
11
+ '%{model}.%{action}.%{attribute}',
12
+ '%{model}.%{nested_model}.%{attribute}',
13
+ '%{model}.%{attribute}',
14
+ '%{nested_model}.%{attribute}',
15
+ '%{attribute}'
16
+ ]
17
+
18
+ class << self
19
+
20
+ def translate(*args)
21
+ key = args.shift.to_sym
22
+ options = args.extract_options!
23
+ options.reverse_merge!(:default => DEFAULT_VALUES[key])
24
+ options[:scope] = [DEFAULT_SCOPE, options[:scope]].flatten.compact
25
+ ::I18n.translate(key, *(args << options))
26
+ end
27
+ alias :t :translate
28
+
29
+ end
30
+
31
+ end
32
+ end