form_assistant 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 1.2.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{form_assistant}
8
- s.version = "1.1.0"
8
+ s.version = "1.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ryan Heath", "Chris Scharf"]
12
- s.date = %q{2012-07-06}
12
+ s.date = %q{2012-07-07}
13
13
  s.description = %q{Custom form builder that attempts to make your forms friendly}
14
14
  s.email = %q{scharfie@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -34,12 +34,12 @@ Gem::Specification.new do |s|
34
34
  "forms/_radio_button.html.erb",
35
35
  "forms/_text_area.html.erb",
36
36
  "forms/_text_field.html.erb",
37
- "init.rb",
38
- "install.rb",
39
37
  "lib/form_assistant.rb",
40
38
  "lib/form_assistant/error.rb",
41
39
  "lib/form_assistant/field_errors.rb",
40
+ "lib/form_assistant/form_builder.rb",
42
41
  "lib/form_assistant/rules.rb",
42
+ "lib/form_assistant/view_helpers.rb",
43
43
  "tasks/form_assistant_tasks.rake",
44
44
  "test/forms/_field.html.erb",
45
45
  "test/forms/_fieldset.html.erb",
@@ -1,375 +1,7 @@
1
- %w(error field_errors rules).each do |f|
2
- require File.join(File.dirname(__FILE__), 'form_assistant', f)
3
- end
1
+ require 'form_assistant/error'
2
+ require 'form_assistant/field_errors'
3
+ require 'form_assistant/rules'
4
+ require 'form_assistant/form_builder'
5
+ require 'form_assistant/view_helpers'
4
6
 
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
- # used if no other template is available
40
- attr_accessor :fallback_template
41
-
42
- # override the field_error_proc so that it no longer wraps the field
43
- # with <div class="fieldWithErrors">...</div>, but just returns the field
44
- ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| html_tag }
45
-
46
- class << self
47
- attr_accessor :ignore_templates
48
- attr_accessor :ignore_labels
49
- attr_accessor :ignore_errors
50
- attr_accessor :template_root
51
-
52
- # if set to true, none of the templates will be used;
53
- # however, labels can still be automatically attached
54
- # and all FormAssistant helpers are still avaialable
55
- @ignore_templates = false
56
-
57
- # if set to true, labels will become nil everywhere (both
58
- # with and without templates)
59
- @ignore_labels = false
60
-
61
- # set to true if you'd rather use #error_messages_for()
62
- @ignore_errors = false
63
-
64
- # sets the root directory where templates will be searched
65
- # note: the template root should be nested within the
66
- # configured view path (which defaults to app/views)
67
- def template_root(full_path = false)
68
- @template_root ||= File.join(Rails.configuration.view_path, 'forms')
69
-
70
- # render(:partial => '...') doesn't want the full path of the template
71
- full_path ? @template_root : @template_root.gsub(Rails.configuration.view_path + '/', '')
72
- end
73
- end
74
-
75
- private
76
- # get the error messages (if any) for a field
77
- def error_message_for(fields)
78
- errors = []
79
- fields = [fields] unless Array === fields
80
-
81
- fields.each do |field|
82
- next unless has_errors?(field)
83
-
84
- errors += if RPH::FormAssistant::Rules.has_I18n_support?
85
- full_messages_for(field)
86
- else
87
- human_field_name = field.to_s.humanize
88
- errors += [*object.errors[field]].map do |error|
89
- "#{human_field_name} #{error}"
90
- end
91
- end
92
- end
93
-
94
- errors.empty? ? nil : RPH::FormAssistant::FieldErrors.new(errors)
95
- end
96
-
97
- # Returns full error messages for given field (uses I18n)
98
- def full_messages_for(field)
99
- attr_name = object.class.human_attribute_name(field.to_s)
100
-
101
- object.errors[field].inject([]) do |full_messages, message|
102
- next unless message
103
- full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
104
- end
105
- end
106
-
107
- # returns true if a field is invalid
108
- def has_errors?(field)
109
- !(object.nil? || object.errors[field].blank?)
110
- end
111
-
112
- # checks to make sure the template exists
113
- def template_exists?(template)
114
- File.exists?(File.join(self.class.template_root(true), "_#{template}.html.erb"))
115
- end
116
-
117
- protected
118
- # renders the appropriate partial located in the template root
119
- def render_partial_for(element, field, label, tip, template, helper, required, extra_locals, args)
120
- errors = self.class.ignore_errors ? nil : error_message_for(field)
121
- 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)
122
-
123
- @template.render :partial => "#{self.class.template_root}/#{template}.html.erb", :locals => locals
124
- end
125
-
126
- # render the element with an optional label (does not use the templates)
127
- def render_element(element, field, name, options, ignore_label = false)
128
- return element if ignore_label
129
-
130
- # need to consider if the shortcut label option was used
131
- # i.e. <%= form.text_field :title, :label => 'Project Title' %>
132
- text, label_options = if options[:label].is_a?(String)
133
- [options.delete(:label), {}]
134
- else
135
- [options[:label].delete(:text), options.delete(:label)]
136
- end
137
-
138
- # consider trailing labels
139
- if %w(check_box radio_button).include?(name)
140
- label_options[:class] = (label_options[:class].to_s + ' inline').strip
141
- element + label(field, text, label_options)
142
- else
143
- label(field, text, label_options) + element
144
- end
145
- end
146
-
147
- def extract_options_for_label(field, options={})
148
- label_options = {}
149
-
150
- # consider the global setting for labels and
151
- # allow for turning labels off on a per-helper basis
152
- # <%= form.text_field :title, :label => false %>
153
- if self.class.ignore_labels || options[:label] === false || field.blank?
154
- label_options[:label] = false
155
- else
156
- # ensure that the :label option is a Hash from this point on
157
- options[:label] ||= {}
158
-
159
- # allow for a cleaner way of setting label text
160
- # <%= form.text_field :whatever, :label => 'Whatever Title' %>
161
- label_options.merge!(options[:label].is_a?(String) ? {:text => options[:label]} : options[:label])
162
-
163
- # allow for a more convenient way to set common label options
164
- # <%= form.text_field :whatever, :label_id => 'dom_id' %>
165
- # <%= form.text_field :whatever, :label_class => 'required' %>
166
- # <%= form.text_field :whatever, :label_text => 'Whatever' %>
167
- %w(id class text).each do |option|
168
- label_option = "label_#{option}".to_sym
169
- label_options.merge!(option.to_sym => options.delete(label_option)) if options[label_option]
170
- end
171
-
172
- # Ensure we have default label text
173
- # (since Rails' label() does not currently respect I18n)
174
- label_options[:text] ||= object.class.human_attribute_name(field.to_s)
175
- end
176
-
177
- label_options
178
- end
179
-
180
- def extract_options_for_template(helper_name, options={})
181
- template_options = {}
182
-
183
- if options.has_key?(:template) && options[:template].kind_of?(FalseClass)
184
- template_options[:template] = false
185
- else
186
- # grab the template
187
- template = options.delete(:template) || helper_name.to_s
188
- template = self.fallback_template unless template_exists?(template)
189
- template_options[:template] = template
190
- end
191
-
192
- template_options
193
- end
194
-
195
- public
196
- def fallback_template
197
- @fallback_template ||= 'field'
198
- end
199
-
200
- def self.assist(helper_name)
201
- define_method(helper_name) do |field, *args|
202
- options = (helper_name == 'check_box' ? args.shift : args.extract_options!) || {}
203
- label_options = extract_options_for_label(field, options)
204
- template_options = extract_options_for_template(helper_name, options)
205
- extra_locals = options.delete(:locals) || {}
206
-
207
- # build out the label element (if desired)
208
- label = label_options[:label] === false ? nil : self.label(field, label_options.delete(:text), label_options)
209
-
210
- # grab the tip, if any
211
- tip = options.delete(:tip)
212
-
213
- # is the field required?
214
- required = !!options.delete(:required)
215
-
216
- # ensure that we don't have any custom options pass through
217
- field_options = options.except(:label, :template, :tip, :required)
218
-
219
- # call the original render for the element
220
- super_args = helper_name == 'check_box' ? args.unshift(field_options) : args.push(field_options)
221
- element = super(field, *super_args)
222
-
223
- return element if template_options[:template] === false
224
-
225
- # return the helper with an optional label if templates are not to be used
226
- return render_element(element, field, helper_name, options, label_options[:label] === false) if self.class.ignore_templates
227
-
228
- # render the partial template from the desired template root
229
- render_partial_for(element, field, label, tip, template_options[:template], helper_name, required, extra_locals, args)
230
- end
231
- end
232
-
233
- # redefining all traditional form helpers so that they
234
- # behave the way FormAssistant thinks they should behave
235
- RPH::FormAssistant::FORM_HELPERS.each do |helper_name|
236
- assist(helper_name)
237
- end
238
-
239
- def without_assistance(options={}, &block)
240
- # TODO - allow options to only turn off templates and/or labels
241
- ignore_labels, ignore_templates = self.class.ignore_labels, self.class.ignore_templates
242
-
243
- begin
244
- self.class.ignore_labels, self.class.ignore_templates = true, true
245
- result = yield
246
- ensure
247
- self.class.ignore_labels, self.class.ignore_templates = ignore_labels, ignore_templates
248
- end
249
-
250
- result
251
- end
252
-
253
- def widget(*args, &block)
254
- options = args.extract_options!
255
- fields = args.shift || nil
256
- field = Array === fields ? fields.first : fields
257
-
258
- label_options = extract_options_for_label(field, options)
259
- template_options = extract_options_for_template(self.fallback_template, options)
260
- label = label_options[:label] === false ? nil : self.label(field, label_options.delete(:text), label_options)
261
- tip = options.delete(:tip)
262
- locals = options.delete(:locals)
263
- required = !!options.delete(:required)
264
-
265
- if block_given?
266
- element = without_assistance do
267
- @template.capture(&block)
268
- end
269
- else
270
- element = nil
271
- end
272
-
273
- partial = render_partial_for(element, fields, label, tip, template_options[:template], 'widget', required, locals, args)
274
- RPH::FormAssistant::Rules.binding_required? ? @template.concat(partial, block.binding) : @template.concat(partial)
275
- end
276
-
277
- # Renders a partial, passing the form object as a local
278
- # variable named 'form'
279
- # <%= form.partial 'shared/new', :locals => { :whatever => @whatever } %>
280
- def partial(name, options={})
281
- (options[:locals] ||= {}).update :form => self
282
- options.update :partial => name
283
- @template.render options
284
- end
285
-
286
- def input(field, *args)
287
- helper_name = case column_type(field)
288
- when :string
289
- field.to_s.include?('password') ? :password_field : :text_field
290
- when :text ; :text_area
291
- when :integer, :float, :decimal ; :text_field
292
- when :date ; :date_select
293
- when :datetime, :timestamp ; :datetime_select
294
- when :time ; :time_select
295
- when :boolean ; :check_box
296
- else ; :text_field
297
- end
298
-
299
- send(helper_name, field, *args)
300
- end
301
-
302
- def inputs(*args)
303
- options = args.extract_options!
304
- args.flatten.map do |field|
305
- input(field, options.dup)
306
- end.join('')
307
- end
308
-
309
- def column_type(field)
310
- object.class.columns_hash[field.to_s].type rescue :string
311
- end
312
-
313
- # since #fields_for() doesn't inherit the builder from form_for, we need
314
- # to provide a means to set the builder automatically (works with nesting, too)
315
- #
316
- # Usage: simply call #fields_for() on the builder object
317
- #
318
- # <% form_assistant_for @project do |form| %>
319
- # <%= form.text_field :title %>
320
- # <% form.fields_for :tasks do |task_fields| %>
321
- # <%= task_fields.text_field :name %>
322
- # <% end %>
323
- # <% end %>
324
- def fields_for_with_form_assistant(record_or_name_or_array, *args, &proc)
325
- options = args.extract_options!
326
- # hand control over to the original #fields_for()
327
- fields_for_without_form_assistant(record_or_name_or_array, *(args << options.merge!(:builder => self.class)), &proc)
328
- end
329
-
330
- # used to intercept #fields_for() and set the builder
331
- alias_method_chain :fields_for, :form_assistant
332
- end
333
-
334
- # methods that mix into ActionView::Base
335
- module ActionView
336
- private
337
- # used to ensure that the desired builder gets set before calling #form_for()
338
- def form_for_with_builder(record_or_name_or_array, builder, *args, &proc)
339
- options = args.extract_options!
340
- # hand control over to the original #form_for()
341
- form_for(record_or_name_or_array, *(args << options.merge!(:builder => builder)), &proc)
342
- end
343
-
344
- # determines if binding is needed for #concat()
345
- # (Rails 2.2.0 and greater no longer requires the binding)
346
- def binding_required
347
- RPH::FormAssistant::Rules.binding_required?
348
- end
349
-
350
- public
351
- # easy way to make use of FormAssistant::FormBuilder
352
- #
353
- # <% form_assistant_for @project do |form| %>
354
- # // fancy form stuff
355
- # <% end %>
356
- def form_assistant_for(record_or_name_or_array, *args, &proc)
357
- form_for_with_builder(record_or_name_or_array, RPH::FormAssistant::FormBuilder, *args, &proc)
358
- end
359
-
360
- # (borrowed the #fieldset() helper from Chris Scharf:
361
- # http://github.com/scharfie/slate/tree/master/app/helpers/application_helper.rb)
362
- #
363
- # <% fieldset 'User Registration' do %>
364
- # // fields
365
- # <% end %>
366
- def fieldset(legend, &block)
367
- locals = { :legend => legend, :fields => capture(&block) }
368
- partial = render(:partial => "#{RPH::FormAssistant::FormBuilder.template_root}/fieldset.html.erb", :locals => locals)
369
-
370
- # render the fields
371
- binding_required ? concat(partial, block.binding) : concat(partial)
372
- end
373
- end
374
- end
375
- end
7
+ ActionView::Base.send :include, RPH::FormAssistant::ViewHelpers
@@ -0,0 +1,339 @@
1
+ # Developed by Ryan Heath (http://rpheath.com)
2
+ module RPH
3
+ # The idea is to make forms extremely less painful and a lot more DRY
4
+ module FormAssistant
5
+ FORM_HELPERS = [
6
+ ActionView::Helpers::FormBuilder.field_helpers +
7
+ %w(date_select datetime_select time_select collection_select select country_select time_zone_select) -
8
+ %w(hidden_field label fields_for)
9
+ ].flatten.freeze
10
+
11
+ # FormAssistant::FormBuilder
12
+ # * provides several convenient helpers (see helpers.rb) and
13
+ # an infrastructure to easily add your own
14
+ # * method_missing hook to wrap content "on the fly"
15
+ # * optional: automatically attach labels to field helpers
16
+ # * optional: format fields using partials (extremely extensible)
17
+ #
18
+ # Usage:
19
+ #
20
+ # <% form_for @project, :builder => RPH::FormAssistant::FormBuilder do |form| %>
21
+ # // fancy form stuff
22
+ # <% end %>
23
+ #
24
+ # - or -
25
+ #
26
+ # <% form_assistant_for @project do |form| %>
27
+ # // fancy form stuff
28
+ # <% end %>
29
+ #
30
+ # - or -
31
+ #
32
+ # # in config/intializers/form_assistant.rb
33
+ # ActionView::Base.default_form_builder = RPH::FormAssistant::FormBuilder
34
+ class FormBuilder < ActionView::Helpers::FormBuilder
35
+ # used if no other template is available
36
+ attr_accessor :fallback_template
37
+
38
+ # override the field_error_proc so that it no longer wraps the field
39
+ # with <div class="fieldWithErrors">...</div>, but just returns the field
40
+ ActionView::Base.field_error_proc = Proc.new { |html_tag, instance| html_tag }
41
+
42
+ class << self
43
+ attr_accessor :ignore_templates
44
+ attr_accessor :ignore_labels
45
+ attr_accessor :ignore_errors
46
+ attr_accessor :template_root
47
+
48
+ # if set to true, none of the templates will be used;
49
+ # however, labels can still be automatically attached
50
+ # and all FormAssistant helpers are still avaialable
51
+ @ignore_templates = false
52
+
53
+ # if set to true, labels will become nil everywhere (both
54
+ # with and without templates)
55
+ @ignore_labels = false
56
+
57
+ # set to true if you'd rather use #error_messages_for()
58
+ @ignore_errors = false
59
+
60
+ def view_path
61
+ if Rails.configuration.respond_to?(:view_path)
62
+ return Rails.configuration.view_path
63
+ else
64
+ return Rails.configuration.paths['app/views'].first
65
+ end
66
+ end
67
+
68
+ # sets the root directory where templates will be searched
69
+ # note: the template root should be nested within the
70
+ # configured view path (which defaults to app/views)
71
+ def template_root(full_path = false)
72
+ @template_root ||= File.join(view_path, 'forms')
73
+
74
+ # render(:partial => '...') doesn't want the full path of the template
75
+ full_path ? @template_root : @template_root.gsub(view_path, '')
76
+ end
77
+ end
78
+
79
+ private
80
+ # get the error messages (if any) for a field
81
+ def error_message_for(fields)
82
+ errors = []
83
+ fields = [fields] unless Array === fields
84
+
85
+ fields.each do |field|
86
+ next unless has_errors?(field)
87
+
88
+ errors += if RPH::FormAssistant::Rules.has_I18n_support?
89
+ full_messages_for(field)
90
+ else
91
+ human_field_name = field.to_s.humanize
92
+ errors += [*object.errors[field]].map do |error|
93
+ "#{human_field_name} #{error}"
94
+ end
95
+ end
96
+ end
97
+
98
+ errors.empty? ? nil : RPH::FormAssistant::FieldErrors.new(errors)
99
+ end
100
+
101
+ # Returns full error messages for given field (uses I18n)
102
+ def full_messages_for(field)
103
+ attr_name = object.class.human_attribute_name(field.to_s)
104
+
105
+ object.errors[field].inject([]) do |full_messages, message|
106
+ next unless message
107
+ full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
108
+ end
109
+ end
110
+
111
+ # returns true if a field is invalid
112
+ def has_errors?(field)
113
+ !(object.nil? || object.errors[field].blank?)
114
+ end
115
+
116
+ # checks to make sure the template exists
117
+ def template_exists?(template)
118
+ File.exists?(File.join(self.class.template_root(true), "_#{template}.html.erb"))
119
+ end
120
+
121
+ protected
122
+ # renders the appropriate partial located in the template root
123
+ def render_partial_for(element, field, label, tip, template, helper, required, extra_locals, args)
124
+ errors = self.class.ignore_errors ? nil : error_message_for(field)
125
+ 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)
126
+
127
+ @template.render :partial => "#{self.class.template_root}/#{template}.html.erb", :locals => locals
128
+ end
129
+
130
+ # render the element with an optional label (does not use the templates)
131
+ def render_element(element, field, name, options, ignore_label = false)
132
+ return element if ignore_label
133
+
134
+ # need to consider if the shortcut label option was used
135
+ # i.e. <%= form.text_field :title, :label => 'Project Title' %>
136
+ text, label_options = if options[:label].is_a?(String)
137
+ [options.delete(:label), {}]
138
+ else
139
+ [options[:label].delete(:text), options.delete(:label)]
140
+ end
141
+
142
+ # consider trailing labels
143
+ if %w(check_box radio_button).include?(name)
144
+ label_options[:class] = (label_options[:class].to_s + ' inline').strip
145
+ element + label(field, text, label_options)
146
+ else
147
+ label(field, text, label_options) + element
148
+ end
149
+ end
150
+
151
+ def extract_options_for_label(field, options={})
152
+ label_options = {}
153
+
154
+ # consider the global setting for labels and
155
+ # allow for turning labels off on a per-helper basis
156
+ # <%= form.text_field :title, :label => false %>
157
+ if self.class.ignore_labels || options[:label] === false || field.blank?
158
+ label_options[:label] = false
159
+ else
160
+ # ensure that the :label option is a Hash from this point on
161
+ options[:label] ||= {}
162
+
163
+ # allow for a cleaner way of setting label text
164
+ # <%= form.text_field :whatever, :label => 'Whatever Title' %>
165
+ label_options.merge!(options[:label].is_a?(String) ? {:text => options[:label]} : options[:label])
166
+
167
+ # allow for a more convenient way to set common label options
168
+ # <%= form.text_field :whatever, :label_id => 'dom_id' %>
169
+ # <%= form.text_field :whatever, :label_class => 'required' %>
170
+ # <%= form.text_field :whatever, :label_text => 'Whatever' %>
171
+ %w(id class text).each do |option|
172
+ label_option = "label_#{option}".to_sym
173
+ label_options.merge!(option.to_sym => options.delete(label_option)) if options[label_option]
174
+ end
175
+
176
+ # Ensure we have default label text
177
+ # (since Rails' label() does not currently respect I18n)
178
+ label_options[:text] ||= object.class.human_attribute_name(field.to_s)
179
+ end
180
+
181
+ label_options
182
+ end
183
+
184
+ def extract_options_for_template(helper_name, options={})
185
+ template_options = {}
186
+
187
+ if options.has_key?(:template) && options[:template].kind_of?(FalseClass)
188
+ template_options[:template] = false
189
+ else
190
+ # grab the template
191
+ template = options.delete(:template) || helper_name.to_s
192
+ template = self.fallback_template unless template_exists?(template)
193
+ template_options[:template] = template
194
+ end
195
+
196
+ template_options
197
+ end
198
+
199
+ public
200
+ def fallback_template
201
+ @fallback_template ||= 'field'
202
+ end
203
+
204
+ def self.assist(helper_name)
205
+ define_method(helper_name) do |field, *args|
206
+ options = (helper_name == 'check_box' ? args.shift : args.extract_options!) || {}
207
+ label_options = extract_options_for_label(field, options)
208
+ template_options = extract_options_for_template(helper_name, options)
209
+ extra_locals = options.delete(:locals) || {}
210
+
211
+ # build out the label element (if desired)
212
+ label = label_options[:label] === false ? nil : self.label(field, label_options.delete(:text), label_options)
213
+
214
+ # grab the tip, if any
215
+ tip = options.delete(:tip)
216
+
217
+ # is the field required?
218
+ required = !!options.delete(:required)
219
+
220
+ # ensure that we don't have any custom options pass through
221
+ field_options = options.except(:label, :template, :tip, :required)
222
+
223
+ # call the original render for the element
224
+ super_args = helper_name == 'check_box' ? args.unshift(field_options) : args.push(field_options)
225
+ element = super(field, *super_args)
226
+
227
+ return element if template_options[:template] === false
228
+
229
+ # return the helper with an optional label if templates are not to be used
230
+ return render_element(element, field, helper_name, options, label_options[:label] === false) if self.class.ignore_templates
231
+
232
+ # render the partial template from the desired template root
233
+ render_partial_for(element, field, label, tip, template_options[:template], helper_name, required, extra_locals, args)
234
+ end
235
+ end
236
+
237
+ # redefining all traditional form helpers so that they
238
+ # behave the way FormAssistant thinks they should behave
239
+ RPH::FormAssistant::FORM_HELPERS.each do |helper_name|
240
+ assist(helper_name)
241
+ end
242
+
243
+ def without_assistance(options={}, &block)
244
+ # TODO - allow options to only turn off templates and/or labels
245
+ ignore_labels, ignore_templates = self.class.ignore_labels, self.class.ignore_templates
246
+
247
+ begin
248
+ self.class.ignore_labels, self.class.ignore_templates = true, true
249
+ result = yield
250
+ ensure
251
+ self.class.ignore_labels, self.class.ignore_templates = ignore_labels, ignore_templates
252
+ end
253
+
254
+ result
255
+ end
256
+
257
+ def widget(*args, &block)
258
+ options = args.extract_options!
259
+ fields = args.shift || nil
260
+ field = Array === fields ? fields.first : fields
261
+
262
+ label_options = extract_options_for_label(field, options)
263
+ template_options = extract_options_for_template(self.fallback_template, options)
264
+ label = label_options[:label] === false ? nil : self.label(field, label_options.delete(:text), label_options)
265
+ tip = options.delete(:tip)
266
+ locals = options.delete(:locals)
267
+ required = !!options.delete(:required)
268
+
269
+ if block_given?
270
+ element = without_assistance do
271
+ @template.capture(&block)
272
+ end
273
+ else
274
+ element = nil
275
+ end
276
+
277
+ partial = render_partial_for(element, fields, label, tip, template_options[:template], 'widget', required, locals, args)
278
+ RPH::FormAssistant::Rules.binding_required? ? @template.concat(partial, block.binding) : @template.concat(partial)
279
+ end
280
+
281
+ # Renders a partial, passing the form object as a local
282
+ # variable named 'form'
283
+ # <%= form.partial 'shared/new', :locals => { :whatever => @whatever } %>
284
+ def partial(name, options={})
285
+ (options[:locals] ||= {}).update :form => self
286
+ options.update :partial => name
287
+ @template.render options
288
+ end
289
+
290
+ def input(field, *args)
291
+ helper_name = case column_type(field)
292
+ when :string
293
+ field.to_s.include?('password') ? :password_field : :text_field
294
+ when :text ; :text_area
295
+ when :integer, :float, :decimal ; :text_field
296
+ when :date ; :date_select
297
+ when :datetime, :timestamp ; :datetime_select
298
+ when :time ; :time_select
299
+ when :boolean ; :check_box
300
+ else ; :text_field
301
+ end
302
+
303
+ send(helper_name, field, *args)
304
+ end
305
+
306
+ def inputs(*args)
307
+ options = args.extract_options!
308
+ args.flatten.map do |field|
309
+ input(field, options.dup)
310
+ end.join('')
311
+ end
312
+
313
+ def column_type(field)
314
+ object.class.columns_hash[field.to_s].type rescue :string
315
+ end
316
+
317
+ # since #fields_for() doesn't inherit the builder from form_for, we need
318
+ # to provide a means to set the builder automatically (works with nesting, too)
319
+ #
320
+ # Usage: simply call #fields_for() on the builder object
321
+ #
322
+ # <% form_assistant_for @project do |form| %>
323
+ # <%= form.text_field :title %>
324
+ # <% form.fields_for :tasks do |task_fields| %>
325
+ # <%= task_fields.text_field :name %>
326
+ # <% end %>
327
+ # <% end %>
328
+ def fields_for_with_form_assistant(record_or_name_or_array, *args, &proc)
329
+ options = args.extract_options!
330
+ # hand control over to the original #fields_for()
331
+ fields_for_without_form_assistant(record_or_name_or_array, *(args << options.merge!(:builder => self.class)), &proc)
332
+ end
333
+
334
+ # used to intercept #fields_for() and set the builder
335
+ alias_method_chain :fields_for, :form_assistant
336
+ end
337
+
338
+ end
339
+ end
@@ -0,0 +1,44 @@
1
+ module RPH
2
+ module FormAssistant
3
+ # methods that mix into ActionView::Base
4
+ module ViewHelpers
5
+ private
6
+ # used to ensure that the desired builder gets set before calling #form_for()
7
+ def form_for_with_builder(record_or_name_or_array, builder, *args, &proc)
8
+ options = args.extract_options!
9
+ # hand control over to the original #form_for()
10
+ form_for(record_or_name_or_array, *(args << options.merge!(:builder => builder)), &proc)
11
+ end
12
+
13
+ # determines if binding is needed for #concat()
14
+ # (Rails 2.2.0 and greater no longer requires the binding)
15
+ def binding_required
16
+ RPH::FormAssistant::Rules.binding_required?
17
+ end
18
+
19
+ public
20
+ # easy way to make use of FormAssistant::FormBuilder
21
+ #
22
+ # <% form_assistant_for @project do |form| %>
23
+ # // fancy form stuff
24
+ # <% end %>
25
+ def form_assistant_for(record_or_name_or_array, *args, &proc)
26
+ form_for_with_builder(record_or_name_or_array, RPH::FormAssistant::FormBuilder, *args, &proc)
27
+ end
28
+
29
+ # (borrowed the #fieldset() helper from Chris Scharf:
30
+ # http://github.com/scharfie/slate/tree/master/app/helpers/application_helper.rb)
31
+ #
32
+ # <% fieldset 'User Registration' do %>
33
+ # // fields
34
+ # <% end %>
35
+ def fieldset(legend, &block)
36
+ locals = { :legend => legend, :fields => capture(&block) }
37
+ partial = render(:partial => "#{RPH::FormAssistant::FormBuilder.template_root}/fieldset.html.erb", :locals => locals)
38
+
39
+ # render the fields
40
+ binding_required ? concat(partial, block.binding) : concat(partial)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -67,7 +67,7 @@ end
67
67
 
68
68
  class FormAssistantTest < ActionView::TestCase
69
69
  include FormAssistantHelpers
70
- include ::RPH::FormAssistant::ActionView
70
+ include ::RPH::FormAssistant::ViewHelpers
71
71
  attr_accessor :form
72
72
 
73
73
  def setup
@@ -77,6 +77,10 @@ class FormAssistantTest < ActionView::TestCase
77
77
  @form = RPH::FormAssistant::FormBuilder.new(:address_book, @address_book, self, {}, nil)
78
78
  RPH::FormAssistant::FormBuilder.template_root = File.expand_path(File.join(File.dirname(__FILE__), 'forms'))
79
79
  end
80
+
81
+ test "should add helper methods" do
82
+ assert view.respond_to?(:form_assistant_for)
83
+ end
80
84
 
81
85
  test "should use template based on input type" do
82
86
  form.text_field :first_name
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: form_assistant
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 1.1.0
10
+ version: 1.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ryan Heath
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2012-07-06 00:00:00 -04:00
19
+ date: 2012-07-07 00:00:00 -04:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -182,12 +182,12 @@ files:
182
182
  - forms/_radio_button.html.erb
183
183
  - forms/_text_area.html.erb
184
184
  - forms/_text_field.html.erb
185
- - init.rb
186
- - install.rb
187
185
  - lib/form_assistant.rb
188
186
  - lib/form_assistant/error.rb
189
187
  - lib/form_assistant/field_errors.rb
188
+ - lib/form_assistant/form_builder.rb
190
189
  - lib/form_assistant/rules.rb
190
+ - lib/form_assistant/view_helpers.rb
191
191
  - tasks/form_assistant_tasks.rake
192
192
  - test/forms/_field.html.erb
193
193
  - test/forms/_fieldset.html.erb
data/init.rb DELETED
@@ -1,2 +0,0 @@
1
- require 'form_assistant'
2
- ActionView::Base.send :include, RPH::FormAssistant::ActionView
data/install.rb DELETED
@@ -1 +0,0 @@
1
- # Install hook code here