form_assistant 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rake", "0.8.7"
4
+
5
+ group :development do
6
+ gem "activesupport", "~> 2.3"
7
+ gem "actionpack", "~> 2.3"
8
+ gem "activerecord", "~> 2.3"
9
+ gem "rdoc", "~> 3.12"
10
+ gem "bundler", ">= 1.0.0"
11
+ gem "jeweler", "~> 1.8.4"
12
+ gem "rcov", ">= 0"
13
+ gem "mocha", :require => false
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionpack (2.3.14)
5
+ activesupport (= 2.3.14)
6
+ rack (~> 1.1.0)
7
+ activerecord (2.3.14)
8
+ activesupport (= 2.3.14)
9
+ activesupport (2.3.14)
10
+ git (1.2.5)
11
+ jeweler (1.8.4)
12
+ bundler (~> 1.0)
13
+ git (>= 1.2.5)
14
+ rake
15
+ rdoc
16
+ json (1.7.3)
17
+ metaclass (0.0.1)
18
+ mocha (0.12.0)
19
+ metaclass (~> 0.0.1)
20
+ rack (1.1.3)
21
+ rake (0.8.7)
22
+ rcov (1.0.0)
23
+ rdoc (3.12)
24
+ json (~> 1.4)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ actionpack (~> 2.3)
31
+ activerecord (~> 2.3)
32
+ activesupport (~> 2.3)
33
+ bundler (>= 1.0.0)
34
+ jeweler (~> 1.8.4)
35
+ mocha
36
+ rake (= 0.8.7)
37
+ rcov
38
+ rdoc (~> 3.12)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Chris Scharf
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,128 @@
1
+ h1. FormAssistant
2
+
3
+ This is a Rails plugin that provides a custom form builder that attempts to make forms somewhat friendly.
4
+
5
+ h2. Usage
6
+
7
+ Once installed, use the form assistant just like #form_for():
8
+
9
+ <pre><code><% form_assistant_for @project do |form| %>
10
+ // typical form_for stuff
11
+ <% end %></code></pre>
12
+
13
+ Or if you'd rather use #form_for() everywhere, you can set the form assistant's builder to be your default across the entire application like so:
14
+
15
+ <pre><code>ActionView::Base.default_form_builder = RPH::FormAssistant::FormBuilder</code></pre>
16
+
17
+ h2. Defaults and Configuration
18
+
19
+ Things you can customize...
20
+
21
+ <pre><code># config/initializers/form_assistant.rb
22
+
23
+ RPH::FormAssistant::FormBuilder.ignore_templates = true # defaults to false
24
+ RPH::FormAssistant::FormBuilder.ignore_labels = true # defaults to false
25
+ RPH::FormAssistant::FormBuilder.ignore_errors = true # defaults to false
26
+ RPH::FormAssistant::FormBuilder.template_root = '...' # defaults to app/views/forms
27
+ </code></pre>
28
+
29
+ The only thing worth mentioning deals with ignoring templates. If you ignore the templates, you will still get access to all of the custom helpers and your form helpers (@text_field@, @text_area@, etc) will automatically have labels attached to them. The form assistant considers trailing labels, too, meaning if you have a @check_box@ the label will be _after_ the check box instead of before it. It will also be given a CSS class of 'inline'.
30
+
31
+ h2. Examples
32
+
33
+ Here are a few reasons why it's worth using the form assistant.
34
+
35
+ I'm going to refer to a @form@ object in the examples. Assume this object is yielded back to the block of a form assistant call, like so:
36
+
37
+ <pre><code><% form_assistant_for @project do |form| %>
38
+ // the 'form' object would be used in here
39
+ <% end %>
40
+ </code></pre>
41
+
42
+ And just to be clear, the regular @fields_for()@ doesn't inherit the builder from the builder object, but as long as you're using the form assistant, this problem has been taken care of automatically. Just call the @fields_for()@ helper on the builder object, like so:
43
+
44
+ <pre><code><% form_assistant_for @project do |form| %>
45
+ <% form.fields_for :tasks do |task_fields| %>
46
+ <%= task_fields.text_field :name %>
47
+ <% end %>
48
+ <% end %>
49
+ </code></pre>
50
+
51
+ h3. Form Templates
52
+
53
+ The new and improved form assistant uses partials to format your fields, labels, errors (if any), and tips. To get started, run...
54
+
55
+ <pre><code>$> rake form_assistant:install</code></pre>
56
+
57
+ ...from your project root. That will put some example form partials in app/views/forms/*.
58
+
59
+ By default, the form assistant will try to render a template based on the name of the helper. For instance, calling <pre><code><%= form.text_field :title %></code></pre> will look for a template called _text_field.html.erb located in app/views/forms. However, you can specify a different template easily:
60
+
61
+ <pre><code><%= form.text_field :title, :template => 'custom_template' %></code></pre>
62
+
63
+ If a specified template doesn't exist, a fallback template will be used (called '_field.html.erb').
64
+
65
+ There's also a @#fieldset()@ helper available to you, although it doesn't belong to the form object (it's mixed into action view itself).
66
+
67
+ <pre><code><% fieldset 'User Registration' do %>
68
+ <%= form.text_field :name %>
69
+ <%= form.text_field :username %>
70
+ <%= form.text_field :password %>
71
+ <% end %>
72
+ </code></pre>
73
+
74
+ The nice thing about that is it's also controlled by a template (cleverly called '_fieldset.html.erb').
75
+
76
+ h3. Required Fields
77
+
78
+ You can now pass a @:required@ flag to the field helpers, and it will be available within the templates.
79
+
80
+ <pre><code><%= form.text_field :title, :required => true %></code></pre>
81
+
82
+ Then you can check the 'required' local variable and handle it accordingly. Naturally, this defaults to false.
83
+
84
+ h3. Form Labels
85
+
86
+ Another convenient thing about the form assistant is the ability to control labels from their respective helper. For example...
87
+
88
+ <pre><code><%= form.text_field :title, :label => 'Project Title' %>
89
+ <%= form.text_field :title, :label_text => 'Project Title' %>
90
+ <%= form.text_field :title, :label_class => 'required' %>
91
+ <%= form.text_field :title, :label_id => 'dom_id' %>
92
+ <%= form.text_field :title, :label => { :text => 'Project Title', :id => 'dom_id', :class => 'required' } %>
93
+ <%= form.text_field :title, :label => false %>
94
+ </code></pre>
95
+
96
+ That works for all form helpers (text_area, check_box, etc). And by default, the label will be the humanized version of the field name, so that's what you'll get if you ignore the label options altogether.
97
+
98
+ h3. Form Widgets
99
+
100
+ Sometimes, a single form field isn't enough. Form assistant provides a construct to help you
101
+ with this, called widget:
102
+
103
+ <pre><code><% form.widget :expiration_date, :label => 'Card expiration date' do %>
104
+ <%= form.select :expiration_month, (1..12) %>
105
+ <%= form.select :expiration_month, (1..12) %>
106
+ <% end %></code></pre>
107
+
108
+ There are a few things to note about this new feature:
109
+
110
+ * error messages will come from the errors on the field name ('expiration_date' in the above example)
111
+ * templates and labels are disabled for the duration of the block
112
+ * the default template for widgets is the fallback template (===_field.html.erb=== unless configured otherwise)
113
+
114
+ h3. Custom Helpers
115
+
116
+ @#partial()@ helper:
117
+
118
+ <pre><code><%= form.partial 'shared/new', :locals => { ... } %></code></pre>
119
+
120
+ The builder variable will be automatically passed as a local called 'form'.
121
+
122
+ h2. Requirements
123
+
124
+ The form assistant requires _at least_ Rails version 2.1.0. This is mainly due to the usage of the convenience methods now included in the Rails module (for things like @Rails.version@ and @Rails.root@).
125
+
126
+ h2. Licensing
127
+
128
+ (c) 2008 Ryan Heath, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "form_assistant"
18
+ gem.homepage = "http://github.com/scharfie/form_assistant"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Custom form builder that attempts to make your forms friendly}
21
+ gem.description = %Q{Custom form builder that attempts to make your forms friendly}
22
+ gem.email = "scharfie@gmail.com"
23
+ gem.authors = ["Ryan Heath", "Chris Scharf"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ test.rcov_opts << '--exclude "gems/*"'
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rdoc/task'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "gem #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "testunit" }
@@ -0,0 +1,26 @@
1
+ require 'autotest'
2
+
3
+ class Autotest::Testunit < Autotest
4
+ def initialize # :nodoc:
5
+ super
6
+ @exceptions = /^\.\/(?:config|doc|log|tmp|website)/
7
+
8
+ @test_mappings = {
9
+ %r%^test/.*\.rb$% => proc { |filename, _|
10
+ filename
11
+ },
12
+ %r%^lib/(.*)\.rb$% => proc { |_, m|
13
+ ["test/#{m[1]}_test.rb"]
14
+ },
15
+ %r%^test/test_helper.rb$% => proc {
16
+ files_matching %r%^test/.*_test\.rb$%
17
+ },
18
+ }
19
+ end
20
+
21
+ # Given the string filename as the path, determine
22
+ # the corresponding tests for it, in an array.
23
+ def tests_for_file(filename)
24
+ super.select { |f| @files.has_key? f }
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ <p>
2
+ <%= element %> <%= label %> <span><%= tip %></span><br />
3
+ <%= errors %>
4
+ </p>
@@ -0,0 +1,5 @@
1
+ <%= label %>
2
+ <p>
3
+ <%= element %> <span><%= tip %></span><br />
4
+ <%= errors %>
5
+ </p>
@@ -0,0 +1,4 @@
1
+ <h2><%= legend %></h2>
2
+ <div class="fieldset">
3
+ <%= fields %>
4
+ </div>
@@ -0,0 +1,5 @@
1
+ <%= label %>
2
+ <p>
3
+ <%= element %> <span><%= tip %></span><br />
4
+ <%= errors %>
5
+ </p>
@@ -0,0 +1,5 @@
1
+ <%= label %>
2
+ <p>
3
+ <%= element %> <span><%= tip %></span><br />
4
+ <%= errors %>
5
+ </p>
@@ -0,0 +1,4 @@
1
+ <p>
2
+ <%= element %> <%= label %> <span><%= tip %></span><br />
3
+ <%= errors %>
4
+ </p>
@@ -0,0 +1,5 @@
1
+ <%= label %>
2
+ <p>
3
+ <%= element %> <span><%= tip %></span><br />
4
+ <%= errors %>
5
+ </p>
@@ -0,0 +1,5 @@
1
+ <%= label %>
2
+ <p>
3
+ <%= element %> <span><%= tip %></span><br />
4
+ <%= errors %>
5
+ </p>
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'form_assistant'
2
+ ActionView::Base.send :include, RPH::FormAssistant::ActionView
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,373 @@
1
+ %w(error field_errors rules).each do |f|
2
+ require File.join(File.dirname(__FILE__), 'form_assistant', f)
3
+ end
4
+
5
+ # Developed by Ryan Heath (http://rpheath.com)
6
+ module RPH
7
+ # The idea is to make forms extremely less painful and a lot more DRY
8
+ module FormAssistant
9
+ FORM_HELPERS = [
10
+ ActionView::Helpers::FormBuilder.field_helpers +
11
+ %w(date_select datetime_select time_select collection_select select country_select time_zone_select) -
12
+ %w(hidden_field label fields_for)
13
+ ].flatten.freeze
14
+
15
+ # FormAssistant::FormBuilder
16
+ # * provides several convenient helpers (see helpers.rb) and
17
+ # an infrastructure to easily add your own
18
+ # * method_missing hook to wrap content "on the fly"
19
+ # * optional: automatically attach labels to field helpers
20
+ # * optional: format fields using partials (extremely extensible)
21
+ #
22
+ # Usage:
23
+ #
24
+ # <% form_for @project, :builder => RPH::FormAssistant::FormBuilder do |form| %>
25
+ # // fancy form stuff
26
+ # <% end %>
27
+ #
28
+ # - or -
29
+ #
30
+ # <% form_assistant_for @project do |form| %>
31
+ # // fancy form stuff
32
+ # <% end %>
33
+ #
34
+ # - or -
35
+ #
36
+ # # in config/intializers/form_assistant.rb
37
+ # ActionView::Base.default_form_builder = RPH::FormAssistant::FormBuilder
38
+ class FormBuilder < ActionView::Helpers::FormBuilder
39
+ cattr_accessor :ignore_templates
40
+ cattr_accessor :ignore_labels
41
+ cattr_accessor :ignore_errors
42
+ cattr_accessor :template_root
43
+
44
+ # used if no other template is available
45
+ attr_accessor :fallback_template
46
+
47
+ # if set to true, none of the templates will be used;
48
+ # however, labels can still be automatically attached
49
+ # and all FormAssistant helpers are still avaialable
50
+ self.ignore_templates = false
51
+
52
+ # if set to true, labels will become nil everywhere (both
53
+ # with and without templates)
54
+ self.ignore_labels = false
55
+
56
+ # set to true if you'd rather use #error_messages_for()
57
+ self.ignore_errors = false
58
+
59
+ # sets the root directory where templates will be searched
60
+ # note: the template root should be nested within the
61
+ # configured view path (which defaults to app/views)
62
+ self.template_root = File.join(Rails.configuration.view_path, 'forms')
63
+
64
+ # override the field_error_proc so that it no longer wraps the field
65
+ # with <div class="fieldWithErrors">...</div>, but just returns the field
66
+ ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| html_tag }
67
+
68
+ private
69
+ # render(:partial => '...') doesn't want the full path of the template
70
+ def self.template_root(full_path = false)
71
+ full_path ? @@template_root : @@template_root.gsub(Rails.configuration.view_path + '/', '')
72
+ end
73
+
74
+ # get the error messages (if any) for a field
75
+ def error_message_for(fields)
76
+ errors = []
77
+ fields = [fields] unless Array === fields
78
+
79
+ fields.each do |field|
80
+ next unless has_errors?(field)
81
+
82
+ errors += if RPH::FormAssistant::Rules.has_I18n_support?
83
+ full_messages_for(field)
84
+ else
85
+ human_field_name = field.to_s.humanize
86
+ errors += [*object.errors[field]].map do |error|
87
+ "#{human_field_name} #{error}"
88
+ end
89
+ end
90
+ end
91
+
92
+ errors.empty? ? nil : RPH::FormAssistant::FieldErrors.new(errors)
93
+ end
94
+
95
+ # Returns full error messages for given field (uses I18n)
96
+ def full_messages_for(field)
97
+ attr_name = object.class.human_attribute_name(field.to_s)
98
+
99
+ object.errors[field].inject([]) do |full_messages, message|
100
+ next unless message
101
+ full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
102
+ end
103
+ end
104
+
105
+ # returns true if a field is invalid
106
+ def has_errors?(field)
107
+ !(object.nil? || object.errors[field].blank?)
108
+ end
109
+
110
+ # checks to make sure the template exists
111
+ def template_exists?(template)
112
+ File.exists?(File.join(self.class.template_root(true), "_#{template}.html.erb"))
113
+ end
114
+
115
+ protected
116
+ # renders the appropriate partial located in the template root
117
+ def render_partial_for(element, field, label, tip, template, helper, required, extra_locals, args)
118
+ errors = self.class.ignore_errors ? nil : error_message_for(field)
119
+ locals = (extra_locals || {}).merge(:element => element, :field => field, :builder => self, :object => object, :object_name => object_name, :label => label, :errors => errors, :tip => tip, :helper => helper, :required => required)
120
+
121
+ @template.render :partial => "#{self.class.template_root}/#{template}.html.erb", :locals => locals
122
+ end
123
+
124
+ # render the element with an optional label (does not use the templates)
125
+ def render_element(element, field, name, options, ignore_label = false)
126
+ return element if ignore_label
127
+
128
+ # need to consider if the shortcut label option was used
129
+ # i.e. <%= form.text_field :title, :label => 'Project Title' %>
130
+ text, label_options = if options[:label].is_a?(String)
131
+ [options.delete(:label), {}]
132
+ else
133
+ [options[:label].delete(:text), options.delete(:label)]
134
+ end
135
+
136
+ # consider trailing labels
137
+ if %w(check_box radio_button).include?(name)
138
+ label_options[:class] = (label_options[:class].to_s + ' inline').strip
139
+ element + label(field, text, label_options)
140
+ else
141
+ label(field, text, label_options) + element
142
+ end
143
+ end
144
+
145
+ def extract_options_for_label(field, options={})
146
+ label_options = {}
147
+
148
+ # consider the global setting for labels and
149
+ # allow for turning labels off on a per-helper basis
150
+ # <%= form.text_field :title, :label => false %>
151
+ if self.class.ignore_labels || options[:label] === false || field.blank?
152
+ label_options[:label] = false
153
+ else
154
+ # ensure that the :label option is a Hash from this point on
155
+ options[:label] ||= {}
156
+
157
+ # allow for a cleaner way of setting label text
158
+ # <%= form.text_field :whatever, :label => 'Whatever Title' %>
159
+ label_options.merge!(options[:label].is_a?(String) ? {:text => options[:label]} : options[:label])
160
+
161
+ # allow for a more convenient way to set common label options
162
+ # <%= form.text_field :whatever, :label_id => 'dom_id' %>
163
+ # <%= form.text_field :whatever, :label_class => 'required' %>
164
+ # <%= form.text_field :whatever, :label_text => 'Whatever' %>
165
+ %w(id class text).each do |option|
166
+ label_option = "label_#{option}".to_sym
167
+ label_options.merge!(option.to_sym => options.delete(label_option)) if options[label_option]
168
+ end
169
+
170
+ # Ensure we have default label text
171
+ # (since Rails' label() does not currently respect I18n)
172
+ label_options[:text] ||= object.class.human_attribute_name(field.to_s)
173
+ end
174
+
175
+ label_options
176
+ end
177
+
178
+ def extract_options_for_template(helper_name, options={})
179
+ template_options = {}
180
+
181
+ if options.has_key?(:template) && options[:template].kind_of?(FalseClass)
182
+ template_options[:template] = false
183
+ else
184
+ # grab the template
185
+ template = options.delete(:template) || helper_name.to_s
186
+ template = self.fallback_template unless template_exists?(template)
187
+ template_options[:template] = template
188
+ end
189
+
190
+ template_options
191
+ end
192
+
193
+ public
194
+ def fallback_template
195
+ @fallback_template ||= 'field'
196
+ end
197
+
198
+ def self.assist(helper_name)
199
+ define_method(helper_name) do |field, *args|
200
+ options = (helper_name == 'check_box' ? args.shift : args.extract_options!) || {}
201
+ label_options = extract_options_for_label(field, options)
202
+ template_options = extract_options_for_template(helper_name, options)
203
+ extra_locals = options.delete(:locals) || {}
204
+
205
+ # build out the label element (if desired)
206
+ label = label_options[:label] === false ? nil : self.label(field, label_options.delete(:text), label_options)
207
+
208
+ # grab the tip, if any
209
+ tip = options.delete(:tip)
210
+
211
+ # is the field required?
212
+ required = !!options.delete(:required)
213
+
214
+ # ensure that we don't have any custom options pass through
215
+ field_options = options.except(:label, :template, :tip, :required)
216
+
217
+ # call the original render for the element
218
+ super_args = helper_name == 'check_box' ? args.unshift(field_options) : args.push(field_options)
219
+ element = super(field, *super_args)
220
+
221
+ return element if template_options[:template] === false
222
+
223
+ # return the helper with an optional label if templates are not to be used
224
+ return render_element(element, field, helper_name, options, label_options[:label] === false) if self.class.ignore_templates
225
+
226
+ # render the partial template from the desired template root
227
+ render_partial_for(element, field, label, tip, template_options[:template], helper_name, required, extra_locals, args)
228
+ end
229
+ end
230
+
231
+ # redefining all traditional form helpers so that they
232
+ # behave the way FormAssistant thinks they should behave
233
+ RPH::FormAssistant::FORM_HELPERS.each do |helper_name|
234
+ assist(helper_name)
235
+ end
236
+
237
+ def without_assistance(options={}, &block)
238
+ # TODO - allow options to only turn off templates and/or labels
239
+ ignore_labels, ignore_templates = self.class.ignore_labels, self.class.ignore_templates
240
+
241
+ begin
242
+ self.class.ignore_labels, self.class.ignore_templates = true, true
243
+ result = yield
244
+ ensure
245
+ self.class.ignore_labels, self.class.ignore_templates = ignore_labels, ignore_templates
246
+ end
247
+
248
+ result
249
+ end
250
+
251
+ def widget(*args, &block)
252
+ options = args.extract_options!
253
+ fields = args.shift || nil
254
+ field = Array === fields ? fields.first : fields
255
+
256
+ label_options = extract_options_for_label(field, options)
257
+ template_options = extract_options_for_template(self.fallback_template, options)
258
+ label = label_options[:label] === false ? nil : self.label(field, label_options.delete(:text), label_options)
259
+ tip = options.delete(:tip)
260
+ locals = options.delete(:locals)
261
+ required = !!options.delete(:required)
262
+
263
+ if block_given?
264
+ element = without_assistance do
265
+ @template.capture(&block)
266
+ end
267
+ else
268
+ element = nil
269
+ end
270
+
271
+ partial = render_partial_for(element, fields, label, tip, template_options[:template], 'widget', required, locals, args)
272
+ RPH::FormAssistant::Rules.binding_required? ? @template.concat(partial, block.binding) : @template.concat(partial)
273
+ end
274
+
275
+ # Renders a partial, passing the form object as a local
276
+ # variable named 'form'
277
+ # <%= form.partial 'shared/new', :locals => { :whatever => @whatever } %>
278
+ def partial(name, options={})
279
+ (options[:locals] ||= {}).update :form => self
280
+ options.update :partial => name
281
+ @template.render options
282
+ end
283
+
284
+ def input(field, *args)
285
+ helper_name = case column_type(field)
286
+ when :string
287
+ field.to_s.include?('password') ? :password_field : :text_field
288
+ when :text ; :text_area
289
+ when :integer, :float, :decimal ; :text_field
290
+ when :date ; :date_select
291
+ when :datetime, :timestamp ; :datetime_select
292
+ when :time ; :time_select
293
+ when :boolean ; :check_box
294
+ else ; :text_field
295
+ end
296
+
297
+ send(helper_name, field, *args)
298
+ end
299
+
300
+ def inputs(*args)
301
+ options = args.extract_options!
302
+ args.flatten.map do |field|
303
+ input(field, options.dup)
304
+ end.join('')
305
+ end
306
+
307
+ def column_type(field)
308
+ object.class.columns_hash[field.to_s].type rescue :string
309
+ end
310
+
311
+ # since #fields_for() doesn't inherit the builder from form_for, we need
312
+ # to provide a means to set the builder automatically (works with nesting, too)
313
+ #
314
+ # Usage: simply call #fields_for() on the builder object
315
+ #
316
+ # <% form_assistant_for @project do |form| %>
317
+ # <%= form.text_field :title %>
318
+ # <% form.fields_for :tasks do |task_fields| %>
319
+ # <%= task_fields.text_field :name %>
320
+ # <% end %>
321
+ # <% end %>
322
+ def fields_for_with_form_assistant(record_or_name_or_array, *args, &proc)
323
+ options = args.extract_options!
324
+ # hand control over to the original #fields_for()
325
+ fields_for_without_form_assistant(record_or_name_or_array, *(args << options.merge!(:builder => self.class)), &proc)
326
+ end
327
+
328
+ # used to intercept #fields_for() and set the builder
329
+ alias_method_chain :fields_for, :form_assistant
330
+ end
331
+
332
+ # methods that mix into ActionView::Base
333
+ module ActionView
334
+ private
335
+ # used to ensure that the desired builder gets set before calling #form_for()
336
+ def form_for_with_builder(record_or_name_or_array, builder, *args, &proc)
337
+ options = args.extract_options!
338
+ # hand control over to the original #form_for()
339
+ form_for(record_or_name_or_array, *(args << options.merge!(:builder => builder)), &proc)
340
+ end
341
+
342
+ # determines if binding is needed for #concat()
343
+ # (Rails 2.2.0 and greater no longer requires the binding)
344
+ def binding_required
345
+ RPH::FormAssistant::Rules.binding_required?
346
+ end
347
+
348
+ public
349
+ # easy way to make use of FormAssistant::FormBuilder
350
+ #
351
+ # <% form_assistant_for @project do |form| %>
352
+ # // fancy form stuff
353
+ # <% end %>
354
+ def form_assistant_for(record_or_name_or_array, *args, &proc)
355
+ form_for_with_builder(record_or_name_or_array, RPH::FormAssistant::FormBuilder, *args, &proc)
356
+ end
357
+
358
+ # (borrowed the #fieldset() helper from Chris Scharf:
359
+ # http://github.com/scharfie/slate/tree/master/app/helpers/application_helper.rb)
360
+ #
361
+ # <% fieldset 'User Registration' do %>
362
+ # // fields
363
+ # <% end %>
364
+ def fieldset(legend, &block)
365
+ locals = { :legend => legend, :fields => capture(&block) }
366
+ partial = render(:partial => "#{RPH::FormAssistant::FormBuilder.template_root}/fieldset.html.erb", :locals => locals)
367
+
368
+ # render the fields
369
+ binding_required ? concat(partial, block.binding) : concat(partial)
370
+ end
371
+ end
372
+ end
373
+ end