form_assistant 1.1.0 → 1.2.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/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