nofxx-formtastic 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -1,7 +1,11 @@
1
- h1. Formtastic 0.1.3
1
+ h1. Formtastic 0.1.4
2
2
 
3
3
  Formtastic is a Rails FormBuilder DSL (with some other goodies) to make it far easier to create beautiful, semantically rich, syntactically awesome, readily stylable and wonderfully accessible HTML forms in your Rails applications.
4
4
 
5
+ h2. Warning
6
+
7
+ This fork adds jQuery only add/remove nested partial.
8
+
5
9
  h2. The Story
6
10
 
7
11
  One day, I finally had enough, so I opened up my text editor, and wrote a DSL for how I'd like to author forms:
@@ -83,10 +87,10 @@ You can (and should) get it as a gem:
83
87
  And then add it as a dependency in your environment.rb file:
84
88
 
85
89
  <pre>
86
- config.gem "justinfrench-formtastic",
87
- :lib => 'formtastic',
88
- :source => 'http://gems.github.com',
89
- :version => '0.1.3'
90
+ config.gem "justinfrench-formtastic",
91
+ :lib => 'formtastic',
92
+ :source => 'http://gems.github.com',
93
+ :version => '0.1.4'
90
94
  </pre>
91
95
 
92
96
  If you're a little more old school, install it as a plugin:
@@ -95,6 +99,13 @@ If you're a little more old school, install it as a plugin:
95
99
  ./script/plugin install git://github.com/justinfrench/formtastic.git
96
100
  </pre>
97
101
 
102
+ h3. jQuery
103
+
104
+ To use the cliente side add/remove partial, make sure you got:
105
+
106
+ * "jquery.template":http://stanlemon.net/projects/jquery-templates.html
107
+
108
+ Avaiable on the DOM.
98
109
 
99
110
  h2. Usage
100
111
 
@@ -156,18 +167,20 @@ If you want to customize the label text, or render some hint text below the fiel
156
167
  <% end %>
157
168
  </pre>
158
169
 
159
- If you want to customize html elements for any non button inputs you just need
160
- to specify the :input_html options hash.
170
+ If you want to customize html elements for any non button inputs and the li
171
+ wrapper you just need to specify the :input_html and :wrapper_html options hash.
161
172
 
162
173
  <pre>
163
174
  <% semantic_form_for @post do |form| %>
164
175
  <%= form.input :title, :input_html => {:size => 60} %>
165
- <%= form.input :body %>
176
+ <%= form.input :body, :wrapper_html => { :class => 'body_wrapper' } %>
166
177
  <%= form.input :created_at, :input_html => {:disabled => true} %>
167
178
  <%= form.buttons %>
168
179
  <% end %>
169
180
  </pre>
170
181
 
182
+ To customize buttons, :button_html is available.
183
+
171
184
  Nested forms (Rails 2.3) are also supported. You can do it in the Rails way:
172
185
 
173
186
  <pre>
@@ -237,14 +250,14 @@ If you wish, put something like this in config/initializers/formtastic_config.rb
237
250
  # Should all fields be considered "required" by default
238
251
  # Defaults to true, see ValidationReflection notes below
239
252
  Formtastic::SemanticFormBuilder.all_fields_required_by_default = false
240
-
253
+
241
254
  # Set the string that will be appended to the labels/fieldsets which are required
242
255
  # It accepts string or procs and the default is a localized version of
243
256
  # '<abbr title="required">*</abbr>'. In other words, if you configure formtastic.required
244
257
  # in your locale, it will replace the abbr title properly. But if you don't want to use
245
258
  # abbr tag, you can simply give a string as below
246
259
  Formtastic::SemanticFormBuilder.required_string = "(required)"
247
-
260
+
248
261
  # Set the string that will be appended to the labels/fieldsets which are optional
249
262
  # Defaults to an empty string ("") and also accepts procs (see required_string above)
250
263
  Formtastic::SemanticFormBuilder.optional_string = "(optional)"
@@ -329,22 +342,24 @@ h2. Contributors
329
342
  Formtastic wouldn't be as awesome as it is today if it weren't for the wonderful contributions of these fine, fine coders. An extra huge thanks goes out to "José Valim":http://github.com/josevalim for nearly 50 patches.
330
343
 
331
344
  * "Justin French":http://justinfrench.com
345
+ * "José Valim":http://github.com/josevalim
346
+ * "Jeff Smick":http://github.com/sprsquish
347
+ * "Tien Dung":http://github.com/tiendung
348
+ * "Mark Mansour":http://stateofflux.com
349
+ * "Andy Pearson":http://github.com/andypearson
350
+ * "negonicrac":http://github.com/negonicrac
332
351
  * "Xavier Shay":http://rhnh.net
333
- * "Bin Dong":http://github.com/dongbin
334
- * "Ben Hamill":http://blog.benhamill.com/
335
352
  * "Pat Allan":http://github.com/freelancing-god
336
- * "negonicrac":http://github.com/negonicrac
337
- * "Andy Pearson":http://github.com/andypearson
338
- * "Mark Mansour":http://stateofflux.com
339
- * "Tien Dung":http://github.com/tiendung
340
- * "Sascha Hoellger":http://github.com/mitnal
341
- * "Jeff Smick":http://github.com/sprsquish
342
- * "José Valim":http://github.com/josevalim
343
- * "Greg Fitzgerald":http://github.com/gregf/
344
353
  * "Gareth Townsend":http://github.com/quamen
354
+ * "Sascha Hoellger":http://github.com/mitnal
355
+ * "Andrew Carpenter":http://github.com/andrewcarpenter
345
356
  * "Jack Dempsey":http://github.com/jackdempsey/
357
+ * "Greg Fitzgerald":http://github.com/gregf/
358
+ * "Hector E. Gomez Morales":http://github.com/hectoregm
359
+ * "Ben Hamill":http://blog.benhamill.com/
346
360
  * "Simon Chiu":http://github.com/tolatomeow
347
-
361
+ * "Bin Dong":http://github.com/dongbin
362
+ * "Marcos Piccinini":http://github.com/nofxx
348
363
 
349
364
  h2. Hey, join the Google group!
350
365
 
data/Rakefile CHANGED
@@ -20,7 +20,7 @@ begin
20
20
 
21
21
  s.require_path = 'lib'
22
22
  s.autorequire = GEM
23
- s.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{rails,lib,spec}/**/*")
23
+ s.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{rails,lib,generators,spec}/**/*")
24
24
  end
25
25
  rescue LoadError
26
26
  puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
@@ -0,0 +1,21 @@
1
+ class FormtasticStylesheetsGenerator < Rails::Generator::Base
2
+
3
+ def initialize(*runtime_args)
4
+ super
5
+ end
6
+
7
+ def manifest
8
+ record do |m|
9
+ m.directory File.join('public', 'stylesheets')
10
+ m.template 'formtastic.css', File.join('public', 'stylesheets', 'formtastic.css')
11
+ m.template 'formtastic_changes.css', File.join('public', 'stylesheets', 'formtastic_changes.css')
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def banner
18
+ %{Usage: #{$0} #{spec.name}\nCopies formtastic.css and formtastic_changes.css to public/}
19
+ end
20
+
21
+ end
@@ -0,0 +1,119 @@
1
+ /* -------------------------------------------------------------------------------------------------
2
+
3
+ It's *strongly* suggested that you don't modify this file. Instead, load a new stylesheet after
4
+ this one in your layouts (eg formtastic_changes.css) and override the styles to suit your needs.
5
+ This will allow you to update formtastic.css with new releases without clobbering your own changes.
6
+
7
+ This stylesheet forms part of the Formtastic Rails Plugin
8
+ (c) 2008 Justin French
9
+
10
+ --------------------------------------------------------------------------------------------------*/
11
+
12
+
13
+ /* NORMALIZE AND RESET - obviously inspired by Yahoo's reset.css, but scoped to just form.formtastic
14
+ --------------------------------------------------------------------------------------------------*/
15
+ form.formtastic, form.formtastic ul, form.formtastic ol, form.formtastic li, form.formtastic fieldset, form.formtastic legend, form.formtastic input, form.formtastic textarea, form.formtastic select, form.formtastic p { margin:0; padding:0; }
16
+ form.formtastic fieldset { border:0; }
17
+ form.formtastic em, form.formtastic strong { font-style:normal; font-weight:normal; }
18
+ form.formtastic ol, form.formtastic ul { list-style:none; }
19
+ form.formtastic abbr, form.formtastic acronym { border:0; font-variant:normal; }
20
+ form.formtastic input, form.formtastic textarea, form.formtastic select { font-family:inherit; font-size:inherit; font-weight:inherit; }
21
+ form.formtastic input, form.formtastic textarea, form.formtastic select { font-size:100%; }
22
+ form.formtastic legend { color:#000; }
23
+
24
+
25
+ /* FIELDSETS & LISTS
26
+ --------------------------------------------------------------------------------------------------*/
27
+ form.formtastic fieldset { }
28
+ form.formtastic fieldset.inputs { }
29
+ form.formtastic fieldset.buttons { padding-left:25%; }
30
+ form.formtastic fieldset ol { }
31
+
32
+ /* clearfixing the fieldsets */
33
+ form.formtastic fieldset { display: inline-block; }
34
+ form.formtastic fieldset:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
35
+ html[xmlns] form.formtastic fieldset { display: block; }
36
+ * html form.formtastic fieldset { height: 1%; }
37
+
38
+
39
+ /* INPUT LIs
40
+ --------------------------------------------------------------------------------------------------*/
41
+ form.formtastic fieldset ol li { margin-bottom:1.5em; }
42
+
43
+ /* clearfixing the li's */
44
+ form.formtastic fieldset ol li { display: inline-block; }
45
+ form.formtastic fieldset ol li:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
46
+ html[xmlns] form.formtastic fieldset ol li { display: block; }
47
+ * html form.formtastic fieldset ol li { height: 1%; }
48
+
49
+ form.formtastic fieldset ol li.required { }
50
+ form.formtastic fieldset ol li.optional { }
51
+ form.formtastic fieldset ol li.error { }
52
+
53
+
54
+ /* LABELS
55
+ --------------------------------------------------------------------------------------------------*/
56
+ form.formtastic fieldset ol li label { display:block; width:25%; float:left; padding-top:.2em; }
57
+ form.formtastic fieldset ol li li label { line-height:100%; padding-top:0; }
58
+ form.formtastic fieldset ol li li label input { line-height:100%; vertical-align:middle; margin-top:-0.1em;}
59
+
60
+
61
+ /* NESTED FIELDSETS AND LEGENDS (radio and date/time inputs use nested fieldsets)
62
+ --------------------------------------------------------------------------------------------------*/
63
+ form.formtastic fieldset ol li fieldset { position:relative; }
64
+ form.formtastic fieldset ol li fieldset legend { position:absolute; width:25%; padding-top:0.1em; }
65
+ form.formtastic fieldset ol li fieldset legend span { position:absolute; }
66
+ form.formtastic fieldset ol li fieldset ol { float:left; width:74%; margin:0; padding:0 0 0 25%; }
67
+ form.formtastic fieldset ol li fieldset ol li { padding:0; border:0; }
68
+
69
+ /* INLINE HINTS
70
+ --------------------------------------------------------------------------------------------------*/
71
+ form.formtastic fieldset ol li p.inline-hints { color:#666; margin:0.5em 0 0 25%; }
72
+
73
+
74
+ /* INLINE ERRORS
75
+ --------------------------------------------------------------------------------------------------*/
76
+ form.formtastic fieldset ol li p.inline-errors { color:#cc0000; margin:0.5em 0 0 25%; }
77
+ form.formtastic fieldset ol li ul.errors { color:#cc0000; margin:0.5em 0 0 25%; list-style:square; }
78
+ form.formtastic fieldset ol li ul.errors li { padding:0; border:none; display:list-item; }
79
+
80
+
81
+ /* STRING & NUMERIC OVERRIDES
82
+ --------------------------------------------------------------------------------------------------*/
83
+ form.formtastic fieldset ol li.string input { width:74%; }
84
+ form.formtastic fieldset ol li.numeric input { width:74%; }
85
+
86
+
87
+ /* TEXTAREA OVERRIDES
88
+ --------------------------------------------------------------------------------------------------*/
89
+ form.formtastic fieldset ol li.text textarea { width:74%; }
90
+
91
+
92
+ /* CHECKBOX OVERRIDES
93
+ --------------------------------------------------------------------------------------------------*/
94
+ form.formtastic fieldset ol li.boolean label { padding-left:25%; width:auto; }
95
+ form.formtastic fieldset ol li.boolean label input { margin:0 0.5em 0 0.2em; }
96
+
97
+
98
+ /* RADIO OVERRIDES
99
+ --------------------------------------------------------------------------------------------------*/
100
+ form.formtastic fieldset ol li.radio { }
101
+ form.formtastic fieldset ol li.radio fieldset ol { margin-bottom:-0.6em; }
102
+ form.formtastic fieldset ol li.radio fieldset ol li { margin:0.1em 0 0.5em 0; }
103
+ form.formtastic fieldset ol li.radio fieldset ol li label { float:none; width:100%; }
104
+ form.formtastic fieldset ol li.radio fieldset ol li label input { margin-right:0.2em; }
105
+
106
+
107
+ /* DATE & TIME OVERRIDES
108
+ --------------------------------------------------------------------------------------------------*/
109
+ form.formtastic fieldset ol li.date fieldset ol li,
110
+ form.formtastic fieldset ol li.time fieldset ol li,
111
+ form.formtastic fieldset ol li.datetime fieldset ol li { float:left; width:auto; margin:0 .3em 0 0; }
112
+
113
+ form.formtastic fieldset ol li.date fieldset ol li label,
114
+ form.formtastic fieldset ol li.time fieldset ol li label,
115
+ form.formtastic fieldset ol li.datetime fieldset ol li label { display:none; }
116
+
117
+ form.formtastic fieldset ol li.date fieldset ol li label input,
118
+ form.formtastic fieldset ol li.time fieldset ol li label input,
119
+ form.formtastic fieldset ol li.datetime fieldset ol li label input { display:inline; margin:0; padding:0; }
@@ -0,0 +1,10 @@
1
+ /* -------------------------------------------------------------------------------------------------
2
+
3
+ Load this stylesheet after formtastic.css in your layouts to override the CSS to suit your needs.
4
+ This will allow you to update formtastic.css with new releases without clobbering your own changes.
5
+
6
+ For example, to make the inline hint paragraphs a little darker in color than the standard #666:
7
+
8
+ form.formtastic fieldset ol li p.inline-hints { color:#333; }
9
+
10
+ --------------------------------------------------------------------------------------------------*/
data/lib/formtastic.rb CHANGED
@@ -35,7 +35,6 @@ module Formtastic #:nodoc:
35
35
  STRING_MAPPINGS = [ :string, :password, :numeric ]
36
36
 
37
37
  attr_accessor :template
38
- attr_writer :nested_child_index
39
38
 
40
39
  # Returns a suitable form input for the given +method+, using the database column information
41
40
  # and other factors (like the method name) to figure out what you probably want.
@@ -47,6 +46,7 @@ module Formtastic #:nodoc:
47
46
  # * :required - specify if the column is required (true) or not (false)
48
47
  # * :hint - provide some text to hint or help the user provide the correct information for a field
49
48
  # * :input_html - provide options that will be passed down to the generated input
49
+ # * :wrapper_html - provide options that will be passed down to the li wrapper
50
50
  #
51
51
  # Input Types:
52
52
  #
@@ -55,6 +55,7 @@ module Formtastic #:nodoc:
55
55
  # columns all map to a single numeric_input, for example).
56
56
  #
57
57
  # * :select (a select menu for associations) - default to association names
58
+ # * :time_zone (a select menu with time zones)
58
59
  # * :radio (a set of radio inputs for associations) - default to association names
59
60
  # * :password (a password input) - default for :string column types with 'password' in the method name
60
61
  # * :text (a textarea) - default for :text column types
@@ -80,26 +81,22 @@ module Formtastic #:nodoc:
80
81
  options[:required] = method_required?(method, options[:required])
81
82
  options[:as] ||= default_input_type(method)
82
83
 
83
- options[:label] ||= if @object
84
- @object.class.human_attribute_name(method.to_s)
85
- else
86
- method.to_s.send(@@label_str_method)
87
- end
84
+ html_class = [ options[:as], (options[:required] ? :required : :optional) ]
85
+ html_class << 'error' if @object && @object.respond_to?(:errors) && @object.errors.on(method.to_s)
86
+
87
+ wrapper_html = options.delete(:wrapper_html) || {}
88
+ wrapper_html[:id] ||= generate_html_id(method)
89
+ wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')
88
90
 
89
91
  if [:boolean_select, :boolean_radio].include?(options[:as])
90
92
  ::ActiveSupport::Deprecation.warn(":as => :#{options[:as]} is deprecated, use :as => :#{options[:as].to_s[8..-1]} instead", caller[3..-1])
91
93
  end
92
94
 
93
- html_class = [ options[:as], (options[:required] ? :required : :optional) ].join(' ')
94
- html_class << ' error' if @object && @object.errors.on(method.to_s)
95
-
96
- html_id = generate_html_id(method)
97
-
98
95
  list_item_content = @@inline_order.map do |type|
99
96
  send(:"inline_#{type}_for", method, options)
100
97
  end.compact.join("\n")
101
98
 
102
- return template.content_tag(:li, list_item_content, { :id => html_id, :class => html_class })
99
+ return template.content_tag(:li, list_item_content, wrapper_html)
103
100
  end
104
101
 
105
102
  # Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
@@ -233,21 +230,15 @@ module Formtastic #:nodoc:
233
230
  html_options = args.extract_options!
234
231
  html_options[:class] ||= "inputs"
235
232
 
236
- if fields_for_object = html_options.delete(:for)
237
- html_options.merge!(:parent_builder => self)
238
- inputs_for_nested_attributes(fields_for_object, args << html_options,
239
- html_options.delete(:for_options) || {}, &block)
233
+ if html_options[:for]
234
+ inputs_for_nested_attributes(args, html_options, &block)
240
235
  elsif block_given?
241
236
  field_set_and_list_wrapping(html_options, &block)
242
237
  else
243
238
  if @object && args.empty?
244
- # Get all belongs_to association
245
239
  args = @object.class.reflections.map { |n,_| n if _.macro == :belongs_to }
246
-
247
- # Get content columns and remove timestamps columns from it
248
240
  args += @object.class.content_columns.map(&:name)
249
241
  args -= %w[created_at updated_at created_on updated_on]
250
-
251
242
  args.compact!
252
243
  end
253
244
  contents = args.map { |method| input(method.to_sym) }
@@ -285,9 +276,10 @@ module Formtastic #:nodoc:
285
276
  #
286
277
  # <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" />
287
278
  #
288
- def commit_button(value=nil, options = {})
289
- value ||= save_or_create_button_text
290
- template.content_tag(:li, template.submit_tag(value), :class => "commit")
279
+ def commit_button(value=nil, options={})
280
+ value ||= save_or_create_button_text
281
+ button_html = options.delete(:button_html) || {}
282
+ template.content_tag(:li, self.submit(value, button_html), :class => "commit")
291
283
  end
292
284
 
293
285
  # A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
@@ -323,7 +315,14 @@ module Formtastic #:nodoc:
323
315
  # Dinamically add and remove nested forms for a has_many relation.
324
316
  #
325
317
 
326
- # Add a link to remove the associated partial
318
+ # Add a link to remove the associated partial. You should add this on the partial
319
+ #
320
+ # Example:
321
+ #
322
+ # #contacts
323
+ # = f.input :phone
324
+ # = f.remove_link
325
+ #
327
326
  def remove_link(name, *args)
328
327
  options = args.extract_options!
329
328
  css_selector = options.delete(:selector) || ".#{@object.class.name.split("::").last.underscore}"
@@ -336,6 +335,12 @@ module Formtastic #:nodoc:
336
335
  end
337
336
 
338
337
  # Add a link to add more partials
338
+ #
339
+ # Example:
340
+ #
341
+ # - f.inputs "contacts" do
342
+ # = f.add_associated_link :contacts, :partial => "other/path"
343
+ #
339
344
  def add_associated_link(name, association, opts = {})
340
345
  object = @object.send(association).build
341
346
  associated_name = extract_option_or_class_name(opts, :name, object)
@@ -345,7 +350,7 @@ module Formtastic #:nodoc:
345
350
  partial = opts.delete(:partial) || associated_name
346
351
  container = opts.delete(:expression) || "'#{opts.delete(:container) || '#'+associated_name.pluralize}'"
347
352
 
348
- form = self.render_associated_form(object, :partial => partial)
353
+ form = render_associated_form(object, :partial => partial)
349
354
  form.gsub!(/attributes_(\d+)/, '__idx__')
350
355
  form.gsub!(/\[(\d+)\]/, '__idxx__')
351
356
 
@@ -363,7 +368,6 @@ module Formtastic #:nodoc:
363
368
  opts.symbolize_keys!
364
369
  (opts[:new] - associated.select(&:new_record?).length).times { associated.build } if opts[:new]
365
370
 
366
-
367
371
  unless associated.empty?
368
372
  name = extract_option_or_class_name(opts, :name, associated.first)
369
373
  partial = opts[:partial] || name
@@ -371,7 +375,7 @@ module Formtastic #:nodoc:
371
375
 
372
376
  output = associated.map do |element|
373
377
  fields_for(association_name(name), element, (opts[:fields_for] || {}).merge(:name => name)) do |f|
374
- template.render({:partial => "#{partial}", :locals => {local_assign_name.to_sym => element, :f => f}.merge(opts[:locals] || {})}.merge(opts[:render] || {}))
378
+ render({:partial => "#{partial}", :locals => {local_assign_name.to_sym => element, :f => f}.merge(opts[:locals] || {})}.merge(opts[:render] || {}))
375
379
  end
376
380
  end
377
381
  output.join
@@ -397,17 +401,20 @@ module Formtastic #:nodoc:
397
401
  #
398
402
  # It should raise an error if a block with arity zero is given.
399
403
  #
400
- def inputs_for_nested_attributes(fields_for_object, inputs, options, &block)
404
+ def inputs_for_nested_attributes(args, options, &block)
405
+ args << options.merge!(:parent => { :builder => self, :for => options[:for] })
406
+
401
407
  fields_for_block = if block_given?
402
408
  raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
403
409
  'but the block does not accept any argument.' if block.arity <= 0
404
410
 
405
- proc { |f| f.inputs(*inputs){ block.call(f) } }
411
+ proc { |f| f.inputs(*args){ block.call(f) } }
406
412
  else
407
- proc { |f| f.inputs(*inputs) }
413
+ proc { |f| f.inputs(*args) }
408
414
  end
409
415
 
410
- semantic_fields_for(*(Array(fields_for_object) << options), &fields_for_block)
416
+ fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
417
+ semantic_fields_for(*fields_for_args, &fields_for_block)
411
418
  end
412
419
 
413
420
  # Remove any Formtastic-specific options before passing the down options.
@@ -568,6 +575,20 @@ module Formtastic #:nodoc:
568
575
  end
569
576
  alias :boolean_select_input :select_input
570
577
 
578
+ # Outputs a timezone select input as Rails' time_zone_select helper. You
579
+ # can give priority zones as option.
580
+ #
581
+ # Examples:
582
+ #
583
+ # f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
584
+ #
585
+ def time_zone_input(method, options)
586
+ html_options = options.delete(:input_html) || {}
587
+
588
+ input_label(method, options.delete(:label), options.slice(:required)) +
589
+ self.time_zone_select(method, options.delete(:priority_zones), set_options(options), html_options)
590
+ end
591
+
571
592
  # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
572
593
  # items, one for each possible choice in the belongs_to association. Each li contains a
573
594
  # label and a radio input.
@@ -748,11 +769,13 @@ module Formtastic #:nodoc:
748
769
  def boolean_input(method, options)
749
770
  html_options = options.delete(:input_html) || {}
750
771
 
751
- content = self.check_box(method, set_options(options).merge(html_options),
752
- options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
772
+ input = self.check_box(method, set_options(options).merge(html_options),
773
+ options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
753
774
 
754
- # required does not make sense in check box
755
- input_label(method, content + options.delete(:label), :skip_required => true)
775
+ # Generate the label by hand because required or optional does not make sense here
776
+ label = options.delete(:label) || humanized_attribute_name(method)
777
+
778
+ self.label(method, input + label, options)
756
779
  end
757
780
 
758
781
  # Generates an input for the given method using the type supplied with :as.
@@ -776,16 +799,32 @@ module Formtastic #:nodoc:
776
799
  # or as sentence. If :none is set, no error is shown.
777
800
  #
778
801
  def inline_errors_for(method, options) #:nodoc:
779
- return nil unless @object && [:sentence, :list].include?(@@inline_errors)
802
+ return nil unless @object && @object.respond_to?(:errors) && [:sentence, :list].include?(@@inline_errors)
803
+
804
+ # Ruby 1.9: Strings are not Enumerable, ie no String#to_a
805
+ errors = @object.errors.on(method.to_s)
806
+ unless errors.respond_to?(:to_a)
807
+ errors = [errors]
808
+ else
809
+ errors = errors.to_a
810
+ end
811
+
812
+ # Ruby 1.9: Strings are not Enumerable, ie no String#to_a
813
+ errors = @object.errors.on(method.to_s)
814
+ unless errors.respond_to?(:to_a)
815
+ errors = [errors]
816
+ else
817
+ errors = errors.to_a
818
+ end
780
819
 
781
- errors = @object.errors.on(method.to_s).to_a
782
820
  send("error_#{@@inline_errors}", errors) unless errors.empty?
783
821
  end
784
822
 
785
823
  # Generates hints for the given method using the text supplied in :hint.
786
824
  #
787
825
  def inline_hints_for(method, options) #:nodoc:
788
- options[:hint].blank? ? '' : template.content_tag(:p, options[:hint], :class => 'inline-hints')
826
+ return if options[:hint].blank?
827
+ template.content_tag(:p, options[:hint], :class => 'inline-hints')
789
828
  end
790
829
 
791
830
  # Creates an error sentence by calling to_sentence on the errors array.
@@ -809,7 +848,8 @@ module Formtastic #:nodoc:
809
848
  # with class label.
810
849
  #
811
850
  def input_label(method, text, options={}, as_span=false) #:nodoc:
812
- text << required_or_optional_string(options.delete(:required)) unless options.delete(:skip_required)
851
+ text ||= humanized_attribute_name(method)
852
+ text << required_or_optional_string(options.delete(:required))
813
853
 
814
854
  if as_span
815
855
  options[:class] ||= 'label'
@@ -841,23 +881,18 @@ module Formtastic #:nodoc:
841
881
  # And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
842
882
  # 'Task #3' and so on.
843
883
  #
844
- # If you are using inputs :for, for more than one association in the same
845
- # form builder, you might want to set the nested_child_index as well. You
846
- # can do that doing:
847
- #
848
- # f.nested_child_index = -1
849
- #
850
- def field_set_and_list_wrapping(html_options, contents = '', &block) #:nodoc:
851
- # Generates the legend text allowing nested_child_index support for interpolation
852
- legend_text = html_options.delete(:name).to_s
853
- legend_text %= html_options[:parent_builder].instance_variable_get('@nested_child_index').to_i + 1
884
+ def field_set_and_list_wrapping(html_options, contents='', &block) #:nodoc:
885
+ legend = html_options.delete(:name).to_s
886
+ legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
887
+ legend = template.content_tag(:legend, template.content_tag(:span, legend)) unless legend.blank?
854
888
 
855
- legend = legend_text.blank? ? "" : template.content_tag(:legend, template.content_tag(:span, legend_text))
856
889
  contents = template.capture(&block) if block_given?
857
890
 
891
+ # Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
892
+ contents = contents.join if contents.respond_to?(:join)
858
893
  fieldset = template.content_tag(:fieldset,
859
894
  legend + template.content_tag(:ol, contents),
860
- html_options.except(:builder, :parent_builder)
895
+ html_options.except(:builder, :parent)
861
896
  )
862
897
 
863
898
  template.concat(fieldset) if block_given?
@@ -874,7 +909,7 @@ module Formtastic #:nodoc:
874
909
  )
875
910
  end
876
911
 
877
- # For methods that have a database column, take a best guess as to what the inout method
912
+ # For methods that have a database column, take a best guess as to what the input method
878
913
  # should be. In most cases, it will just return the column type (eg :string), but for special
879
914
  # cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
880
915
  # something different (like :password and :select).
@@ -885,23 +920,22 @@ module Formtastic #:nodoc:
885
920
  def default_input_type(method) #:nodoc:
886
921
  return :string if @object.nil?
887
922
 
888
- # Find the column object by attribute
889
923
  column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
890
924
 
891
- # Associations map by default to a select
892
- return :select if column.nil? && find_reflection(method)
893
-
894
925
  if column
895
926
  # handle the special cases where the column type doesn't map to an input method
896
- return :select if column.type == :integer && method.to_s =~ /_id$/
897
- return :datetime if column.type == :timestamp
898
- return :numeric if [:integer, :float, :decimal].include?(column.type)
899
- return :password if column.type == :string && method.to_s =~ /password/
927
+ return :time_zone if column.type == :string && method.to_s =~ /time_?zone/
928
+ return :select if column.type == :integer && method.to_s =~ /_id$/
929
+ return :datetime if column.type == :timestamp
930
+ return :numeric if [:integer, :float, :decimal].include?(column.type)
931
+ return :password if column.type == :string && method.to_s =~ /password/
932
+
900
933
  # otherwise assume the input name will be the same as the column type (eg string_input)
901
934
  return column.type
902
935
  else
903
936
  obj = @object.send(method) if @object.respond_to?(method)
904
937
 
938
+ return :select if find_reflection(method)
905
939
  return :file if obj && @@file_methods.any? { |m| obj.respond_to?(m) }
906
940
  return :password if method.to_s =~ /password/
907
941
  return :string
@@ -986,7 +1020,7 @@ module Formtastic #:nodoc:
986
1020
  # reflection object.
987
1021
  #
988
1022
  def find_reflection(method)
989
- object.class.reflect_on_association(method) if object.class.respond_to?(:reflect_on_association)
1023
+ @object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
990
1024
  end
991
1025
 
992
1026
  # Generates default_string_options by retrieving column information from
@@ -1020,10 +1054,34 @@ module Formtastic #:nodoc:
1020
1054
  "#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
1021
1055
  end
1022
1056
 
1057
+ # Gets the nested_child_index value from the parent builder. In Rails 2.3
1058
+ # it always returns a fixnum. In next versions it returns a hash with each
1059
+ # association that the parent builds.
1060
+ #
1061
+ def parent_child_index(parent)
1062
+ duck = parent[:builder].instance_variable_get('@nested_child_index')
1063
+
1064
+ if duck.is_a?(Hash)
1065
+ child = parent[:for]
1066
+ child = child.first if child.respond_to?(:first)
1067
+ duck[child].to_i + 1
1068
+ else
1069
+ duck.to_i + 1
1070
+ end
1071
+ end
1072
+
1023
1073
  def sanitized_object_name
1024
1074
  @sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
1025
1075
  end
1026
1076
 
1077
+ def humanized_attribute_name(method)
1078
+ if @object && @object.class.respond_to?(:human_attribute_name)
1079
+ @object.class.human_attribute_name(method.to_s)
1080
+ else
1081
+ method.to_s.send(@@label_str_method)
1082
+ end
1083
+ end
1084
+
1027
1085
  end
1028
1086
 
1029
1087
 
@@ -28,6 +28,7 @@ describe 'Formtastic' do
28
28
  include ActionView::Helpers::CaptureHelper
29
29
  include ActiveSupport
30
30
  include ActionController::PolymorphicRoutes
31
+ include ActionController
31
32
 
32
33
  include Formtastic::SemanticFormHelper
33
34
 
@@ -561,7 +562,7 @@ describe 'Formtastic' do
561
562
  end
562
563
 
563
564
  it 'should call the corresponding input method' do
564
- [:select, :radio, :date, :datetime, :time, :boolean].each do |input_style|
565
+ [:select, :time_zone, :radio, :date, :datetime, :time, :boolean].each do |input_style|
565
566
  @new_post.stub!(:generic_column_name)
566
567
  @new_post.stub!(:column_for_attribute).and_return(mock('column', :type => :string, :limit => 255))
567
568
  semantic_form_for(@new_post) do |builder|
@@ -647,6 +648,47 @@ describe 'Formtastic' do
647
648
  end
648
649
 
649
650
  end
651
+
652
+ describe ':wrapper_html option' do
653
+
654
+ describe 'when provided' do
655
+ it 'should be passed down to the li tag' do
656
+ semantic_form_for(@new_post) do |builder|
657
+ concat(builder.input(:title, :wrapper_html => {:id => :another_id}))
658
+ end
659
+ output_buffer.should have_tag("form li#another_id")
660
+ end
661
+
662
+ it 'should append given classes to li default classes' do
663
+ semantic_form_for(@new_post) do |builder|
664
+ concat(builder.input(:title, :wrapper_html => {:class => :another_class}, :required => true))
665
+ end
666
+ output_buffer.should have_tag("form li.string")
667
+ output_buffer.should have_tag("form li.required")
668
+ output_buffer.should have_tag("form li.another_class")
669
+ end
670
+
671
+ it 'should allow classes to be an array' do
672
+ semantic_form_for(@new_post) do |builder|
673
+ concat(builder.input(:title, :wrapper_html => {:class => [ :my_class, :another_class ]}))
674
+ end
675
+ output_buffer.should have_tag("form li.string")
676
+ output_buffer.should have_tag("form li.my_class")
677
+ output_buffer.should have_tag("form li.another_class")
678
+ end
679
+ end
680
+
681
+ describe 'when not provided' do
682
+ it 'should use default id and class' do
683
+ semantic_form_for(@new_post) do |builder|
684
+ concat(builder.input(:title))
685
+ end
686
+ output_buffer.should have_tag("form li#post_title_input")
687
+ output_buffer.should have_tag("form li.string")
688
+ end
689
+ end
690
+
691
+ end
650
692
  end
651
693
 
652
694
  describe ':as any type of input' do
@@ -971,6 +1013,57 @@ describe 'Formtastic' do
971
1013
  end
972
1014
  end
973
1015
 
1016
+ describe ":as => :time_zone" do
1017
+ before do
1018
+ @new_post.stub!(:time_zone)
1019
+ @new_post.stub!(:column_for_attribute).and_return(mock('column', :type => :string))
1020
+
1021
+ semantic_form_for(@new_post) do |builder|
1022
+ concat(builder.input(:time_zone))
1023
+ end
1024
+ end
1025
+
1026
+ it "should have a time_zone class on the wrapper" do
1027
+ output_buffer.should have_tag('form li.time_zone')
1028
+ end
1029
+
1030
+ it 'should have a post_title_input id on the wrapper' do
1031
+ output_buffer.should have_tag('form li#post_time_zone_input')
1032
+ end
1033
+
1034
+ it 'should generate a label for the input' do
1035
+ output_buffer.should have_tag('form li label')
1036
+ output_buffer.should have_tag('form li label[@for="post_time_zone"')
1037
+ output_buffer.should have_tag('form li label', /Time zone/)
1038
+ end
1039
+
1040
+ it "should generate a select" do
1041
+ output_buffer.should have_tag("form li select")
1042
+ output_buffer.should have_tag("form li select#post_time_zone")
1043
+ output_buffer.should have_tag("form li select[@name=\"post[time_zone]\"]")
1044
+ end
1045
+
1046
+ it 'should use input_html to style inputs' do
1047
+ semantic_form_for(@new_post) do |builder|
1048
+ concat(builder.input(:time_zone, :input_html => { :class => 'myclass' }))
1049
+ end
1050
+ output_buffer.should have_tag("form li select.myclass")
1051
+ end
1052
+
1053
+ it 'should generate input and labels even if no object is given' do
1054
+ semantic_form_for(:project, :url => 'http://test.host/') do |builder|
1055
+ concat(builder.input(:time_zone, :as => :time_zone))
1056
+ end
1057
+
1058
+ output_buffer.should have_tag('form li label')
1059
+ output_buffer.should have_tag('form li label[@for="project_time_zone"')
1060
+ output_buffer.should have_tag('form li label', /Time zone/)
1061
+
1062
+ output_buffer.should have_tag("form li select")
1063
+ output_buffer.should have_tag("form li select#project_time_zone")
1064
+ output_buffer.should have_tag("form li select[@name=\"project[time_zone]\"]")
1065
+ end
1066
+ end
974
1067
 
975
1068
  describe ':as => :radio' do
976
1069
 
@@ -1145,7 +1238,7 @@ describe 'Formtastic' do
1145
1238
 
1146
1239
  it 'should have a label inside the wrapper' do
1147
1240
  output_buffer.should have_tag('form li label')
1148
- output_buffer.should have_tag('form li label', /Posts/)
1241
+ output_buffer.should have_tag('form li label', /Post ids/)
1149
1242
  output_buffer.should have_tag("form li label[@for='author_post_ids']")
1150
1243
  end
1151
1244
 
@@ -1187,7 +1280,7 @@ describe 'Formtastic' do
1187
1280
 
1188
1281
  it 'should have a label inside the wrapper' do
1189
1282
  output_buffer.should have_tag('form li label')
1190
- output_buffer.should have_tag('form li label', /Authors/)
1283
+ output_buffer.should have_tag('form li label', /Author ids/)
1191
1284
  output_buffer.should have_tag("form li label[@for='post_author_ids']")
1192
1285
  end
1193
1286
 
@@ -1658,22 +1751,20 @@ describe 'Formtastic' do
1658
1751
  describe 'when :discard_input => true is set' do
1659
1752
 
1660
1753
  it 'should use default hidden value equals to 1 when attribute returns nil' do
1661
- pending
1662
1754
  semantic_form_for(@new_post) do |builder|
1663
1755
  concat(builder.input(:publish_at, :as => :datetime, :discard_day => true))
1664
1756
  end
1665
1757
 
1666
- output_buffer.should have_tag("form li fieldset ol input[@type='hidden'][@value='1']")
1758
+ output_buffer.should have_tag("form li fieldset input[@type='hidden'][@value='1']")
1667
1759
  end
1668
1760
 
1669
1761
  it 'should use default attribute value when it is not nil' do
1670
- pending
1671
1762
  @new_post.stub!(:publish_at).and_return(Date.new(2007,12,27))
1672
1763
  semantic_form_for(@new_post) do |builder|
1673
1764
  concat(builder.input(:publish_at, :as => :datetime, :discard_day => true))
1674
1765
  end
1675
1766
 
1676
- output_buffer.should have_tag("form li fieldset ol input[@type='hidden'][@value='27']")
1767
+ output_buffer.should have_tag("form li fieldset input[@type='hidden'][@value='27']")
1677
1768
  end
1678
1769
  end
1679
1770
 
@@ -1787,15 +1878,13 @@ describe 'Formtastic' do
1787
1878
  end
1788
1879
 
1789
1880
  it 'should have five labels for hour and minute' do
1790
- pending
1791
- output_buffer.should have_tag('form li.time fieldset ol li label', :count => 2)
1792
- output_buffer.should have_tag('form li.time fieldset ol li label', /hour/i)
1793
- output_buffer.should have_tag('form li.time fieldset ol li label', /minute/i)
1881
+ output_buffer.should have_tag('form li.time fieldset li label', :count => 2)
1882
+ output_buffer.should have_tag('form li.time fieldset li label', /hour/i)
1883
+ output_buffer.should have_tag('form li.time fieldset li label', /minute/i)
1794
1884
  end
1795
1885
 
1796
1886
  it 'should have five selects for hour and minute' do
1797
- pending
1798
- output_buffer.should have_tag('form li.time fieldset ol li select', :count => 2)
1887
+ output_buffer.should have_tag('form li.time fieldset li select', :count => 2)
1799
1888
  end
1800
1889
  end
1801
1890
 
@@ -1970,7 +2059,7 @@ describe 'Formtastic' do
1970
2059
 
1971
2060
  it 'should send parent_builder as an option to allow child index interpolation' do
1972
2061
  semantic_form_for(@new_post) do |builder|
1973
- builder.should_receive(:instance_variable_get).with('@nested_child_index').and_return(0)
2062
+ builder.instance_variable_set('@nested_child_index', 0)
1974
2063
  builder.inputs :for => [:author, @bob], :name => 'Author #%i' do |bob_builder|
1975
2064
  concat('input')
1976
2065
  end
@@ -1978,6 +2067,17 @@ describe 'Formtastic' do
1978
2067
 
1979
2068
  output_buffer.should have_tag('fieldset legend', 'Author #1')
1980
2069
  end
2070
+
2071
+ it 'should also provide child index interpolation when nested child index is a hash' do
2072
+ semantic_form_for(@new_post) do |builder|
2073
+ builder.instance_variable_set('@nested_child_index', :author => 10)
2074
+ builder.inputs :for => [:author, @bob], :name => 'Author #%i' do |bob_builder|
2075
+ concat('input')
2076
+ end
2077
+ end
2078
+
2079
+ output_buffer.should have_tag('fieldset legend', 'Author #11')
2080
+ end
1981
2081
  end
1982
2082
 
1983
2083
  describe 'when a :name option is provided' do
@@ -2314,6 +2414,16 @@ describe 'Formtastic' do
2314
2414
  output_buffer.should have_tag('li.commit input[@name="commit"]')
2315
2415
  end
2316
2416
 
2417
+ it 'should pass options given in :button_html to the button' do
2418
+ @new_post.stub!(:new_record?).and_return(false)
2419
+ semantic_form_for(@new_post) do |builder|
2420
+ concat(builder.commit_button('text', :button_html => {:class => 'my_class', :id => 'my_id'}))
2421
+ end
2422
+
2423
+ output_buffer.should have_tag('li.commit input#my_id')
2424
+ output_buffer.should have_tag('li.commit input.my_class')
2425
+ end
2426
+
2317
2427
  end
2318
2428
 
2319
2429
  describe 'when used on an existing record' do
@@ -2408,18 +2518,20 @@ describe 'Formtastic' do
2408
2518
  describe "Nested Js Helpers" do
2409
2519
 
2410
2520
  it "should have a add method" do
2411
- @new_post.stub!(:authors).and_return(@mock_authors = mock("Authors", :build => []))
2521
+ @new_post.stub!(:authors).and_return(mock("Authors", :build => mock('Author')))
2412
2522
 
2413
2523
  semantic_form_for(@new_post) do |builder|
2414
- builder.inputs :name => "Contacts" do
2524
+ builder.should_receive(:render).with(hash_including(:partial => "mock")).and_return('"attributes___idx___test__idxx__"')
2525
+ builder.inputs :name => "Contacts", :id => "contacts" do
2415
2526
  concat(builder.add_associated_link(:name, :authors))
2416
2527
  end
2417
2528
  end
2418
2529
 
2419
- output_buffer.should eql("<form action=\"/posts\" class=\"formtastic post\" id=\"new_post\" method=\"post\"><fieldset class=\"inputs\"><legend><span>Contacts</span></legend><ol><a href=\"#\" onclick=\"if (typeof formtastic_next_array_id == 'undefined') formtastic_next_array_id = 0;\n $('#arrays').append($.template(null), { number: --formtastic_next_array_id});; return false;\">name</a></ol></fieldset></form>")
2530
+ output_buffer.should have_tag('form fieldset a[@href=#]')
2531
+ output_buffer.should match(/\\&quot;attributes___idx___test__idxx__\\&quot;&quot;.replace/)
2420
2532
  end
2421
2533
 
2422
- it "should have a remove method" do
2534
+ it "should have a remove method" do
2423
2535
  semantic_form_for(@new_post) do |builder|
2424
2536
  builder.semantic_fields_for(:links) do |link_builder|
2425
2537
  concat(link_builder.remove_link("Remove"))
@@ -2430,15 +2542,16 @@ describe 'Formtastic' do
2430
2542
  end
2431
2543
 
2432
2544
  it "should have a show method" do
2433
- @new_post.stub!(:authors).and_return(@mock_authors = mock("Authors"))
2434
- pending
2545
+ @new_post.stub!(:authors).and_return([mock("Author")])
2546
+
2435
2547
  semantic_form_for(@new_post) do |builder|
2548
+ builder.should_receive(:render).with(hash_including(:partial => "mock")).and_return("fields")
2436
2549
  builder.inputs :name => "Contacts" do
2437
2550
  concat(builder.render_associated_form @new_post.authors)
2438
2551
  end
2439
2552
  end
2440
2553
 
2441
- output_buffer.should eql("<form action=\"/posts\" class=\"formtastic post\" id=\"new_post\" method=\"post\"><fieldset class=\"inputs\"><legend><span>Contacts</span></legend><ol><a href=\"#\" onclick=\"if (typeof formtastic_next_array_id == 'undefined') formtastic_next_array_id = 0;\n $('#arrays').append($.template(null), { number: --formtastic_next_array_id});; return false;\">name</a></ol></fieldset></form>")
2554
+ output_buffer.should eql("<form action=\"/posts\" class=\"formtastic post\" id=\"new_post\" method=\"post\"><fieldset class=\"inputs\"><legend><span>Contacts</span></legend><ol>fields</ol></fieldset></form>")
2442
2555
  end
2443
2556
 
2444
2557
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nofxx-formtastic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin French
@@ -9,7 +9,7 @@ autorequire: formtastic
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-17 00:00:00 -07:00
12
+ date: 2009-04-18 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -25,6 +25,9 @@ files:
25
25
  - MIT-LICENSE
26
26
  - README.textile
27
27
  - Rakefile
28
+ - generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb
29
+ - generators/formtastic_stylesheets/templates/formtastic.css
30
+ - generators/formtastic_stylesheets/templates/formtastic_changes.css
28
31
  - lib/formtastic.rb
29
32
  - lib/justin_french/formtastic.rb
30
33
  - lib/locale/en.yml