nuatt-formtastic 0.2.2 → 0.2.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 (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