formtastic 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -311,12 +311,12 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
311
311
 
312
312
  <pre>
313
313
  <% semantic_form_for Post.new do |form| %>
314
- <% inputs do %>
314
+ <% form.inputs do %>
315
315
  <%= form.input :title %> # => :label => "Choose a title...", :hint => "Choose a good title for you post."
316
316
  <%= form.input :body %> # => :label => "Write something...", :hint => "Write something inspiring here."
317
317
  <%= form.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
318
318
  <% end %>
319
- <% buttons do %>
319
+ <% form.buttons do %>
320
320
  <%= form.commit_button %> # => "Create my {{model}}"
321
321
  <% end %>
322
322
  <% end %>
@@ -328,7 +328,7 @@ _Note: Slightly different because Formtastic can't guess how you group fields in
328
328
 
329
329
  <pre>
330
330
  <% semantic_form_for @post do |form| %>
331
- <% inputs :title => :post_details do %> # => :title => "Post details"
331
+ <% form.inputs :title => :post_details do %> # => :title => "Post details"
332
332
  # ...
333
333
  <% end %>
334
334
  # ...
@@ -339,12 +339,12 @@ _Note: Slightly different because Formtastic can't guess how you group fields in
339
339
 
340
340
  <pre>
341
341
  <% semantic_form_for @post do |form| %>
342
- <% inputs do %>
342
+ <% form.inputs do %>
343
343
  <%= form.input :title %> # => :label => "Choose a title...", :hint => "Choose a good title for you post."
344
344
  <%= form.input :body, :hint => false %> # => :label => "Write something..."
345
345
  <%= form.input :section, :label => 'Some section' %> # => :label => 'Some section'
346
346
  <% end %>
347
- <% buttons do %>
347
+ <% form.buttons do %>
348
348
  <%= form.commit_button :dummie %> # => "Launch!"
349
349
  <% end %>
350
350
  <% end %>
@@ -360,12 +360,12 @@ If I18n-lookups is disabled, i.e.:
360
360
 
361
361
  <pre>
362
362
  <% semantic_form_for @post do |form| %>
363
- <% inputs do %>
363
+ <% form.inputs do %>
364
364
  <%= form.input :title, :label => true %> # => :label => "Choose a title..."
365
365
  <%= form.input :body, :label => true %> # => :label => "Write something..."
366
366
  <%= form.input :section, :label => true %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
367
367
  <% end %>
368
- % buttons do %>
368
+ <% form.buttons do %>
369
369
  <%= form.commit_button true %> # => "Save changes" (if we are in edit that is...)
370
370
  <% end %>
371
371
  <% end %>
@@ -416,6 +416,13 @@ h2. Configuration
416
416
  Run @./script/generate formtastic@ to copy a commented out config file into @config/initializers/formtastic.rb@. You can "view the configuration file on GitHub":http://github.com/justinfrench/formtastic/blob/master/generators/formtastic/templates/formtastic.rb
417
417
 
418
418
 
419
+ h2. Custom Inputs
420
+
421
+ If you want to add your own input types to encapsulate your own logic or interface patterns, you can do so by subclassing SemanticFormBuilder and configuring Formtastic to use your custom builder class.
422
+
423
+ @Formtastic::SemanticFormHelper.builder = MyCustomBuilder@
424
+
425
+
419
426
  h2. Status
420
427
 
421
428
  Formtastic has been in active development for about a year. We've just recently jumped to an 0.9 version number, signaling that we consider this a 1.0 release candidate, and that the API won't change significantly for the 1.x series.
@@ -432,7 +439,7 @@ There are none, but...
432
439
 
433
440
  h2. Compatibility
434
441
 
435
- I'm only testing Formtastic with the latest Rails 2.2.x stable release, and it should be fine under Rails 2.3 as well (including nested forms). Patches are welcome to allow backwards compatibility, but I don't have the energy!
442
+ I'm only testing Formtastic with the latest Rails 2.4.x stable release, and it should be fine under Rails 2.3.x as well (including nested forms). Patches are welcome to allow backwards compatibility, but I don't have the energy!
436
443
 
437
444
 
438
445
  h2. Got TextMate?
@@ -444,27 +451,12 @@ Well...there's a TextMate-bundle in town, dedicated to make usage of Formtastic
444
451
 
445
452
  h2. Contributors
446
453
 
447
- Formtastic is maintained by "Justin French":http://justinfrench.com and "José Valim":http://github.com/josevalim, but it wouldn't be as awesome as it is today if it weren't for the wonderful contributions of these fine, fine coders.
448
-
449
- * "Jeff Smick":http://github.com/sprsquish
450
- * "Tien Dung":http://github.com/tiendung
451
- * "Mark Mansour":http://stateofflux.com
452
- * "Andy Pearson":http://github.com/andypearson
453
- * "negonicrac":http://github.com/negonicrac
454
- * "Xavier Shay":http://rhnh.net
455
- * "Pat Allan":http://github.com/freelancing-god
456
- * "Gareth Townsend":http://github.com/quamen
457
- * "Sascha Hoellger":http://github.com/mitnal
458
- * "Andrew Carpenter":http://github.com/andrewcarpenter
459
- * "Jack Dempsey":http://github.com/jackdempsey/
460
- * "Greg Fitzgerald":http://github.com/gregf/
461
- * "Hector E. Gomez Morales":http://github.com/hectoregm
462
- * "Ben Hamill":http://blog.benhamill.com/
463
- * "Simon Chiu":http://github.com/tolatomeow
464
- * "Bin Dong":http://github.com/dongbin
465
-
466
-
467
- h2. Hey, join the Google group!
454
+ Formtastic is maintained by "Justin French":http://justinfrench.com, "José Valim":http://github.com/josevalim and "Jonas Grimfelt":http://github.com/grimen, but it wouldn't be as awesome as it is today without help from over 30 contributors.
455
+
456
+ @git shortlog -n -s --no-merges@
457
+
458
+
459
+ h2. Google Group
468
460
 
469
461
  Please join the "Formtastic Google Group":http://groups.google.com.au/group/formtastic, especially if you'd like to talk about a new feature, or report a bug.
470
462
 
data/Rakefile CHANGED
@@ -13,25 +13,15 @@ begin
13
13
  HOMEPAGE = "http://github.com/justinfrench/formtastic/tree/master"
14
14
  INSTALL_MESSAGE = %q{
15
15
  ========================================================================
16
-
17
16
  Thanks for installing Formtastic!
18
-
17
+ ------------------------------------------------------------------------
19
18
  You can now (optionally) run the generater to copy some stylesheets and
20
19
  a config initializer into your application:
21
-
22
20
  ./script/generate formtastic
23
-
24
- The following files will be added:
25
-
26
- RAILS_ROOT/public/stylesheets/formtastic.css
27
- RAILS_ROOT/public/stylesheets/formtastic_changes.css
28
- RAILS_ROOT/config/initializers/formtastic.rb
29
-
30
- Find out more and get involved:
31
21
 
22
+ Find out more and get involved:
32
23
  http://github.com/justinfrench/formtastic
33
24
  http://groups.google.com.au/group/formtastic
34
-
35
25
  ========================================================================
36
26
  }
37
27
 
@@ -45,3 +45,7 @@
45
45
  # Default value: false. Overridden for specific fields by setting value to true,
46
46
  # i.e. :label => true, or :hint => true (or opposite depending on initialized value)
47
47
  # Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
48
+
49
+ # You can add custom inputs or override parts of Formtastic by subclassing SemanticFormBuilder and
50
+ # specifying that class here. Defaults to SemanticFormBuilder.
51
+ # Formtastic::SemanticFormHelper.builder = MyCustomBuilder
data/lib/formtastic.rb CHANGED
@@ -25,17 +25,7 @@ module Formtastic #:nodoc:
25
25
  I18N_SCOPES = [ '{{model}}.{{action}}.{{attribute}}',
26
26
  '{{model}}.{{attribute}}',
27
27
  '{{attribute}}']
28
-
29
- # Keeps simple mappings in a hash
30
- INPUT_MAPPINGS = {
31
- :string => :text_field,
32
- :password => :password_field,
33
- :numeric => :text_field,
34
- :text => :text_area,
35
- :file => :file_field
36
- }
37
- STRING_MAPPINGS = [ :string, :password, :numeric ]
38
-
28
+
39
29
  attr_accessor :template
40
30
 
41
31
  # Returns a suitable form input for the given +method+, using the database column information
@@ -88,7 +78,6 @@ module Formtastic #:nodoc:
88
78
 
89
79
  html_class = [ options[:as], (options[:required] ? :required : :optional) ]
90
80
  html_class << 'error' if @object && @object.respond_to?(:errors) && !@object.errors[method.to_sym].blank?
91
- html_class << method.to_s
92
81
 
93
82
  wrapper_html = options.delete(:wrapper_html) || {}
94
83
  wrapper_html[:id] ||= generate_html_id(method)
@@ -299,12 +288,6 @@ module Formtastic #:nodoc:
299
288
  if @object
300
289
  key = @object.new_record? ? :create : :update
301
290
  object_name = @object.class.human_name
302
-
303
- if key == :update
304
- # Note: Fallback on :save-key (deprecated), :update makes more sense in the REST-world.
305
- fallback_text = ::I18n.t(:save, :model => object_name, :default => "Save {{model}}", :scope => [:formtastic])
306
- ::ActiveSupport::Deprecation.warn "Formtastic I18n: Key 'formtastic.save' is now deprecated in favor 'formtastic.update'."
307
- end
308
291
  else
309
292
  key = :submit
310
293
  object_name = @object_name.to_s.send(@@label_str_method)
@@ -392,8 +375,8 @@ module Formtastic #:nodoc:
392
375
  end
393
376
  end
394
377
 
395
- # Generates error messages for the given method. Errors can be shown as list
396
- # or as sentence. If :none is set, no error is shown.
378
+ # Generates error messages for the given method. Errors can be shown as list,
379
+ # as sentence or just the first error can be displayed. If :none is set, no error is shown.
397
380
  #
398
381
  # This method is also aliased as errors_on, so you can call on your custom
399
382
  # inputs as well:
@@ -404,7 +387,7 @@ module Formtastic #:nodoc:
404
387
  # end
405
388
  #
406
389
  def inline_errors_for(method, options=nil) #:nodoc:
407
- return nil unless @object && @object.respond_to?(:errors) && [:sentence, :list].include?(@@inline_errors)
390
+ return nil unless @object && @object.respond_to?(:errors) && [:sentence, :list, :first].include?(@@inline_errors)
408
391
 
409
392
  errors = @object.errors[method.to_sym]
410
393
  send("error_#{@@inline_errors}", Array(errors)) unless errors.blank?
@@ -491,24 +474,42 @@ module Formtastic #:nodoc:
491
474
  if_condition ? !!condition : !condition
492
475
  end
493
476
 
494
- # A method that deals with most of inputs (:string, :password, :file,
495
- # :textarea and :numeric). :select, :radio, :boolean and :datetime inputs
496
- # are not handled by this method, since they need more detailed approach.
497
- #
498
- # If input_html is given as option, it's passed down to the input.
499
- #
500
- def input_simple(type, method, options)
477
+ def basic_input_helper(form_helper_method, type, method, options)
501
478
  html_options = options.delete(:input_html) || {}
502
- html_options = default_string_options(method, type).merge(html_options) if STRING_MAPPINGS.include?(type)
479
+ html_options = default_string_options(method, type).merge(html_options) if [:numeric, :string, :password].include?(type)
503
480
 
504
481
  self.label(method, options_for_label(options)) +
505
- self.send(INPUT_MAPPINGS[type], method, html_options)
482
+ self.send(form_helper_method, method, html_options)
483
+ end
484
+
485
+ # Outputs a label and standard Rails text field inside the wrapper.
486
+ def string_input(method, options)
487
+ basic_input_helper(:text_field, :string, method, options)
488
+ end
489
+
490
+ # Outputs a label and standard Rails password field inside the wrapper.
491
+ def password_input(method, options)
492
+ basic_input_helper(:password_field, :password, method, options)
493
+ end
494
+
495
+ # Outputs a label and standard Rails text field inside the wrapper.
496
+ def numeric_input(method, options)
497
+ basic_input_helper(:text_field, :numeric, method, options)
498
+ end
499
+
500
+ # Ouputs a label and standard Rails text area inside the wrapper.
501
+ def text_input(method, options)
502
+ basic_input_helper(:text_area, :text, method, options)
503
+ end
504
+
505
+ # Outputs a label and a standard Rails file field inside the wrapper.
506
+ def file_input(method, options)
507
+ basic_input_helper(:file_field, :file, method, options)
506
508
  end
507
509
 
508
510
  # Outputs a hidden field inside the wrapper, which should be hidden with CSS.
509
511
  # Additionals options can be given and will be sent straight to hidden input
510
512
  # element.
511
- #
512
513
  def hidden_input(method, options)
513
514
  self.hidden_field(method, set_options(options))
514
515
  end
@@ -585,6 +586,13 @@ module Formtastic #:nodoc:
585
586
  # f.input :author, :value_method => :login
586
587
  # f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }
587
588
  #
589
+ # You can pre-select a specific option value by passing in the :selected option.
590
+ #
591
+ # Examples:
592
+ #
593
+ # f.input :author, :selected => current_user.id
594
+ # f.input :author, :value_method => :login, :selected => current_user.login
595
+ #
588
596
  # You can pass html_options to the select tag using :input_html => {}
589
597
  #
590
598
  # Examples:
@@ -594,8 +602,21 @@ module Formtastic #:nodoc:
594
602
  # By default, all select inputs will have a blank option at the top of the list. You can add
595
603
  # a prompt with the :prompt option, or disable the blank option with :include_blank => false.
596
604
  #
605
+ #
606
+ # You can group the options in optgroup elements by passing the :group_by option
607
+ # (Note: only tested for belongs_to relations)
608
+ #
609
+ # Examples:
610
+ #
611
+ # f.input :author, :group_by => :continent
612
+ #
613
+ # All the other options should work as expected. If you want to call a custom method on the
614
+ # group item. You can include the option:group_label_method
615
+ # Examples:
616
+ #
617
+ # f.input :author, :group_by => :continents, :group_label_method => :something_different
618
+ #
597
619
  def select_input(method, options)
598
- collection = find_collection_for_column(method, options)
599
620
  html_options = options.delete(:input_html) || {}
600
621
  options = set_include_blank(options)
601
622
 
@@ -608,7 +629,25 @@ module Formtastic #:nodoc:
608
629
 
609
630
  input_name = generate_association_input_name(method)
610
631
  self.label(method, options_for_label(options).merge(:input_name => input_name)) +
611
- self.select(input_name, collection, set_options(options), html_options)
632
+
633
+ if options[:group_by]
634
+ # The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
635
+ # The formtastic user however shouldn't notice this too much.
636
+ raw_collection = find_raw_collection_for_column(method, options.reverse_merge(:find_options => { :include => options[:group_by] }))
637
+ label, value = detect_label_and_value_method!(raw_collection)
638
+ group_collection = raw_collection.map { |option| option.send(options[:group_by]) }.uniq
639
+ group_label_method = options[:group_label_method] || detect_label_method(group_collection)
640
+ group_collection = group_collection.sort_by { |group_item| group_item.send(group_label_method) }
641
+
642
+ # Here comes the monster with 8 arguments
643
+ self.grouped_collection_select(input_name, group_collection,
644
+ method.to_s.pluralize, group_label_method,
645
+ value, label,
646
+ set_options(options), html_options)
647
+ else
648
+ collection = find_collection_for_column(method, options)
649
+ self.select(input_name, collection, set_options(options), html_options)
650
+ end
612
651
  end
613
652
  alias :boolean_select_input :select_input
614
653
 
@@ -678,6 +717,11 @@ module Formtastic #:nodoc:
678
717
  # f.input :author, :as => :radio, :value_method => :full_name
679
718
  # f.input :author, :as => :radio, :value_method => :login
680
719
  # f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }
720
+ #
721
+ # You can force a particular radio button in the collection to be checked with the :selected option. Example:
722
+ #
723
+ # f.input :subscribe_to_newsletter, :as => :radio, :selected => true
724
+ # f.input :subscribe_to_newsletter, :as => :radio, :collection => ["Yeah!", "Nope!"], :selected => "Nope!"
681
725
  #
682
726
  # Finally, you can set :value_as_class => true if you want the li wrapper around each radio
683
727
  # button / label combination to contain a class with the value of the radio button (useful for
@@ -692,6 +736,7 @@ module Formtastic #:nodoc:
692
736
  list_item_content = collection.map do |c|
693
737
  label = c.is_a?(Array) ? c.first : c
694
738
  value = c.is_a?(Array) ? c.last : c
739
+ html_options[:checked] = options.delete(:selected) unless options[:selected].blank?
695
740
 
696
741
  li_content = template.content_tag(:label,
697
742
  "#{self.radio_button(input_name, value, html_options)} #{label}",
@@ -945,20 +990,8 @@ module Formtastic #:nodoc:
945
990
  end
946
991
 
947
992
  # Generates an input for the given method using the type supplied with :as.
948
- #
949
- # If the input is included in INPUT_MAPPINGS, it uses input_simple
950
- # implementation which maps most of the inputs. All others have specific
951
- # code and then a proper handler should be called (like radio_input) for
952
- # :radio types.
953
- #
954
993
  def inline_input_for(method, options)
955
- input_type = options.delete(:as)
956
-
957
- if INPUT_MAPPINGS.key?(input_type)
958
- input_simple(input_type, method, options)
959
- else
960
- send("#{input_type}_input", method, options)
961
- end
994
+ send("#{options.delete(:as)}_input", method, options)
962
995
  end
963
996
 
964
997
  # Generates hints for the given method using the text supplied in :hint.
@@ -985,6 +1018,12 @@ module Formtastic #:nodoc:
985
1018
  template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
986
1019
  end
987
1020
 
1021
+ # Creates an error sentence containing only the first error
1022
+ #
1023
+ def error_first(errors) #:nodoc:
1024
+ template.content_tag(:p, errors.first.untaint, :class => 'inline-errors')
1025
+ end
1026
+
988
1027
  # Generates the required or optional string. If the value set is a proc,
989
1028
  # it evaluates the proc first.
990
1029
  #
@@ -1100,26 +1139,43 @@ module Formtastic #:nodoc:
1100
1139
  # appropriate label and value.
1101
1140
  #
1102
1141
  def find_collection_for_column(column, options)
1142
+ collection = find_raw_collection_for_column(column, options)
1143
+
1144
+ # Return if we have an Array of strings, fixnums or arrays
1145
+ return collection if collection.instance_of?(Array) &&
1146
+ [Array, Fixnum, String, Symbol].include?(collection.first.class)
1147
+
1148
+ label, value = detect_label_and_value_method!(collection, options)
1149
+
1150
+ collection.map { |o| [send_or_call(label, o), send_or_call(value, o)] }
1151
+ end
1152
+
1153
+ # As #find_collection_for_column but returns the collection without mapping the label and value
1154
+ #
1155
+ def find_raw_collection_for_column(column, options) #:nodoc:
1103
1156
  reflection = find_reflection(column)
1104
1157
 
1105
1158
  collection = if options[:collection]
1106
1159
  options.delete(:collection)
1107
1160
  elsif reflection
1108
- reflection.klass.find(:all)
1161
+ reflection.klass.find(:all, options[:find_options] || {})
1109
1162
  else
1110
1163
  create_boolean_collection(options)
1111
1164
  end
1112
1165
 
1113
1166
  collection = collection.to_a if collection.is_a?(Hash)
1114
1167
 
1115
- # Return if we have an Array of strings, fixnums or arrays
1116
- return collection if collection.instance_of?(Array) &&
1117
- [Array, Fixnum, String, Symbol].include?(collection.first.class)
1118
-
1119
- label = options.delete(:label_method) || detect_label_method(collection)
1168
+ collection
1169
+ end
1170
+
1171
+ # Detects the label and value methods from a collection values set in
1172
+ # @@collection_label_methods. It will use and delete
1173
+ # the options :label_method and :value_methods when present
1174
+ #
1175
+ def detect_label_and_value_method!(collection_or_instance, options = {}) #:nodoc
1176
+ label = options.delete(:label_method) || detect_label_method(collection_or_instance)
1120
1177
  value = options.delete(:value_method) || :id
1121
-
1122
- collection.map { |o| [send_or_call(label, o), send_or_call(value, o)] }
1178
+ [label, value]
1123
1179
  end
1124
1180
 
1125
1181
  # Detected the label collection method when none is supplied using the
@@ -1318,8 +1374,8 @@ module Formtastic #:nodoc:
1318
1374
  # <% end %>
1319
1375
  #
1320
1376
  # The above examples use a resource-oriented style of form_for() helper where only the @post
1321
- # object is given as an argument, but the generic style is also supported if you really want it,
1322
- # as is forms with inline objects (Post.new) rather than objects with instance variables (@post):
1377
+ # object is given as an argument, but the generic style is also supported, as are forms with
1378
+ # inline objects (Post.new) rather than objects with instance variables (@post):
1323
1379
  #
1324
1380
  # <% semantic_form_for :post, @post, :url => posts_path do |f| %>
1325
1381
  # ...
@@ -1328,14 +1384,6 @@ module Formtastic #:nodoc:
1328
1384
  # <% semantic_form_for :post, Post.new, :url => posts_path do |f| %>
1329
1385
  # ...
1330
1386
  # <% end %>
1331
- #
1332
- # The shorter, resource-oriented style is most definitely preferred, and has recieved the most
1333
- # testing to date.
1334
- #
1335
- # Please note: Although it's possible to call Rails' built-in form_for() helper without an
1336
- # object, all semantic forms *must* have an object (either Post.new or @post), as Formtastic
1337
- # has too many dependencies on an ActiveRecord object being present.
1338
- #
1339
1387
  module SemanticFormHelper
1340
1388
  @@builder = Formtastic::SemanticFormBuilder
1341
1389
  mattr_accessor :builder
@@ -1350,7 +1398,7 @@ module Formtastic #:nodoc:
1350
1398
  html_tag
1351
1399
  end
1352
1400
 
1353
- def use_custom_field_error_proc(&block)
1401
+ def with_custom_field_error_proc(&block)
1354
1402
  @@default_field_error_proc = ::ActionView::Base.field_error_proc
1355
1403
  ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
1356
1404
  result = yield
@@ -1374,7 +1422,7 @@ module Formtastic #:nodoc:
1374
1422
  end
1375
1423
  options[:html][:class] = class_names.join(" ")
1376
1424
 
1377
- use_custom_field_error_proc do
1425
+ with_custom_field_error_proc do
1378
1426
  #{meth}(record_or_name_or_array, *(args << options), &proc)
1379
1427
  end
1380
1428
  end