form_assistant 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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