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 +14 -0
- data/Gemfile.lock +38 -0
- data/LICENSE.txt +20 -0
- data/README.textile +128 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +1 -0
- data/autotest/testunit.rb +26 -0
- data/forms/_check_box.html.erb +4 -0
- data/forms/_field.html.erb +5 -0
- data/forms/_fieldset.html.erb +4 -0
- data/forms/_file_field.html.erb +5 -0
- data/forms/_password_field.html.erb +5 -0
- data/forms/_radio_button.html.erb +4 -0
- data/forms/_text_area.html.erb +5 -0
- data/forms/_text_field.html.erb +5 -0
- data/init.rb +2 -0
- data/install.rb +1 -0
- data/lib/form_assistant.rb +373 -0
- data/lib/form_assistant/error.rb +16 -0
- data/lib/form_assistant/field_errors.rb +27 -0
- data/lib/form_assistant/rules.rb +17 -0
- data/tasks/form_assistant_tasks.rake +18 -0
- data/test/forms/_field.html.erb +14 -0
- data/test/forms/_fieldset.html.erb +4 -0
- data/test/forms/_text_field.html.erb +5 -0
- data/test/forms/_widget.html.erb +14 -0
- data/test/helper.rb +28 -0
- data/test/mock_rails.rb +27 -0
- data/test/test_field_errors.rb +25 -0
- data/test/test_form_assistant.rb +205 -0
- data/uninstall.rb +1 -0
- metadata +235 -0
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
|
data/init.rb
ADDED
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
|