forme 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a74dc2b36401ea1374b6e0790ee2292b793beac2
4
- data.tar.gz: 33e3d4e182a2af0513ac37e3200c0eda9826d08b
3
+ metadata.gz: f907e786f16f2b923a7b06def6a58680fc922d27
4
+ data.tar.gz: 88e4c51dc087507766783ee18b45fac47cf8007d
5
5
  SHA512:
6
- metadata.gz: 7c9e809e2c1cd4ea343de693f4611c5f42e5f320723545ec7f76d2e1112d3d695159ed791c2ff0f7810ed2e1bdfe01eb1d850fcd37493c13ebe3e98aa413b0a5
7
- data.tar.gz: b1dfd6ce07b3e35e0873e86f6956662779ecd46f545e0c030a5e557da6cf19df7a8f9dda20ef9feb2a4f418e7d231d33fff6e4f4bcf35e858816c2e9f53fcbd1
6
+ metadata.gz: d76cdda658389678ae596350be89a0f42c9e6210ec55bf28d9c7f1df8615b429c0e537475616f9e37e77027d6123012538aa0a997967484954e2c8d598b58247
7
+ data.tar.gz: 283d18147c8ca75e75b000af73d4a90030f65b672ff242b6124473ff5ed626a0a5fc9ec7798db6e62a497d0557b17d79ca9adde1b60065df36e7b5cf285aa71e
data/CHANGELOG CHANGED
@@ -1,3 +1,27 @@
1
+ === 1.3.0 (2015-04-17)
2
+
3
+ * Support option groups in select, checkboxset, and radioset inputs via :optgroups option (jeremyevans)
4
+
5
+ * Support :select_options option for date/datetime :as=>:select, for setting specific options in each select field (jeremyevans)
6
+
7
+ * The id for first select input for date/datetime :as=>:select is now the same as the :id option/attribute (jeremyevans)
8
+
9
+ * Support :order option for date/datetime :as=>:select inputs, so you can order select boxes day/month/year or month/day/year (jeremyevans)
10
+
11
+ * Add helper transformer type, for adding help text next to fields (jeremyevans)
12
+
13
+ * Support :skip_primary_key option to not add hidden primary key fields for existing associated objects in subform in the Sequel plugin (jeremyevans)
14
+
15
+ * Support :blank_attr option for select/radioset/checkboxset inputs (jeremyevans)
16
+
17
+ * Support :blank_position=>:after option for select/radioset/checkboxset inputs (jeremyevans)
18
+
19
+ * Respect existing :add_blank option when using select tags for boolean fields in the Sequel plugin (jeremyevans)
20
+
21
+ * Use type=datetime-local for datetime types (jeremyevans)
22
+
23
+ * Handle error messages on the underlying column in pg_array_to_many associations (jeremyevans)
24
+
1
25
  === 1.2.0 (2014-11-06)
2
26
 
3
27
  * Support pg_array_to_many associations in the Sequel plugin, treating them similarly to other *_to_many associations (jeremyevans)
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2014 Jeremy Evans
1
+ Copyright (c) 2011-2015 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
@@ -38,6 +38,8 @@ data is broken down to the following steps (called transformers):
38
38
  <tt>Forme::Tag</tt> instance (or array of them).
39
39
  +ErrorHandler+ :: If the <tt>Forme::Input</tt> instance has a error,
40
40
  takes the formatted tag and marks it as having the error.
41
+ +Helper+ :: If the <tt>Forme::Input</tt> instance has any help text,
42
+ adds the help text in a separate tag.
41
43
  +Labeler+ :: If the <tt>Forme::Input</tt> instance has a label,
42
44
  takes the formatted output and labels it.
43
45
  +Wrapper+ :: Takes the output of the formatter, labeler, and
@@ -60,7 +62,7 @@ There is also an +InputsWrapper+ transformer, that is called by
60
62
  related options (in a fieldset by default).
61
63
 
62
64
  The <tt>Forme::Form</tt> object takes the 6 transformers as options (:formatter,
63
- :labeler, :error_handler, :wrapper, :inputs_wrapper, and :serializer), all of which
65
+ :labeler, :error_handler, :helper, :wrapper, :inputs_wrapper, and :serializer), all of which
64
66
  should be objects responding to +call+ (so you can use +Proc+s) or be symbols
65
67
  registered with the library using <tt>Forme.register_transformer</tt>:
66
68
 
@@ -392,7 +394,7 @@ these additional options to the #input method:
392
394
  == string
393
395
 
394
396
  :as :: Can be set to :textarea to use a textarea input. You can use the usual attributes hash or a stylesheet to
395
- control the size of the textarea.
397
+ control the size of the textarea.
396
398
 
397
399
  == associations
398
400
 
@@ -528,7 +530,7 @@ is not the case, use the :output option to +form+ to specify the outvar.
528
530
  = Rails Support
529
531
 
530
532
  Forme ships with a Rails extension that you can get by <tt>require "forme/rails"</tt> and using
531
- <tt>helpers Forme::Rails::ERB</tt> in your controller. If allows you to use the following API
533
+ <tt>helper Forme::Rails::ERB</tt> in your controller. If allows you to use the following API
532
534
  in your Rails forms:
533
535
 
534
536
  <%= forme(@obj, :action=>'/foo') do |f| %>
@@ -559,6 +561,7 @@ These options are supported by all of the input types:
559
561
  :disabled :: Set the disabled attribute if true
560
562
  :error :: Set an error message, invoking the error_handler
561
563
  :error_handler :: Set a custom error_handler, overriding the form's default
564
+ :helper :: Set a custom helper, overriding the form's default
562
565
  :id :: The id attribute to use
563
566
  :key :: The base to use for the name and id attributes, based on the current
564
567
  namespace for the form.
@@ -587,19 +590,18 @@ Creates an input tag with type radio. Options:
587
590
 
588
591
  :checked :: Mark the radio button as checked.
589
592
 
590
- === :date
593
+ === :date / :datetime
591
594
 
592
- By default, creates an input tag with type date. With the :as=>:select option, creates
593
- 3 select boxes, one for the year, month, and day. Options:
595
+ By default, creates an input tag with type date or datetime. With the :as=>:select option,
596
+ creates multiple select options. Options:
594
597
 
595
- :as :: When value is :select, uses 3 select boxes.
596
-
597
- === :datetime
598
-
599
- By default, creates an input tag with type datetime. With the :as=>:select option, creates
600
- 6 select boxes, one for the year, month, day, hour, minute, and second. Options:
601
-
602
- :as :: When value is :select, uses 6 select boxes.
598
+ :as :: When value is :select, uses 3 or 6 select boxes by default.
599
+ :order :: The order of select boxes when using :as=>:select. Entries should be a symbol
600
+ for the select field and string to use a string
601
+ (:date default: <tt>[:year, '-', :month, '-', :day]</tt>)
602
+ (:datetime default: <tt>[:year, '-', :month, '-', :day, ' ', :hour, ':', :minute, ':', :second]</tt>)
603
+ :select_options :: The options to use for the select boxes. Should be a hash keyed by the
604
+ symbol used in order (e.g. <tt>{:year=>1970..2020}</tt>)
603
605
 
604
606
  === :select
605
607
 
@@ -608,8 +610,15 @@ Creates a select tag, containing option tags specified by the :options option.
608
610
  :add_blank :: Add a blank option if true. If the value is a string,
609
611
  use it as the text content of the blank option. The default value can be
610
612
  set with Forme.default_add_blank_prompt, and defaults to the empty string.
613
+ :blank_attr :: If :add_blank is set, sets the attributes to use for the blank option.
614
+ :blank_position :: If :add_blank is set, can be set to :after to add the prompt after
615
+ the inputs, instead of before (which is the default).
611
616
  :multiple :: Creates a multiple select box.
612
- :options :: an array of options used for creating option tags.
617
+ :optgroups :: An enumerable of pairs with the first element being option group labels or
618
+ a hash of option group attributes, and values being enumerables of options
619
+ (as described by :options below). Creates optgroup tags around the
620
+ appropriate options. This overrides any options specified via :options.
621
+ :options :: An enumerable of options used for creating option tags.
613
622
  If the :text_method and :value_method are not given and the entry is an
614
623
  array, uses the first entry of the array as the text of the option, and
615
624
  the second entry of the array as the value of the option.
@@ -656,6 +665,7 @@ the defaults for Inputs created via the form:
656
665
  :config :: The configuration to use, which automatically sets defaults
657
666
  for the transformers to use.
658
667
  :error_handler :: Sets the default error_handler for the form's inputs
668
+ :helper :: Sets the default helper for the form's inputs
659
669
  :formatter :: Sets the default formatter for the form's inputs
660
670
  :hidden_tags :: Sets the hidden tags to automatically add to this form, see below.
661
671
  :input_defaults :: Sets the default options for each input type. This should
@@ -710,15 +720,16 @@ API, so you can use a +Proc+ for any custom transformer.
710
720
  +serializer+ :: tags input/tag, returns string
711
721
  +formatter+ :: takes input, returns tag
712
722
  +error_handler+ :: takes tag and input, returns version of tag with errors noted
723
+ +helper+ :: takes tag and input, returns version of tag with help added
713
724
  +labeler+ :: takes tag and input, returns labeled version of tag
714
725
  +wrapper+ :: takes tag and input, returns wrapped version of tag
715
726
  +inputs_wrapper+ :: takes form, options hash, and block, wrapping block in a tag
716
727
 
717
728
  The +serializer+ is the base of the transformations. It turns +Tag+ instances into strings. If it comes across
718
729
  an +Input+, it calls the +formatter+ on the +Input+ to turn it into a +Tag+, and then serializes
719
- that +Tag+. The +formatter+ first converts the +Input+ to a +Tag+, and then calls the
720
- +error_handler+ if the <tt>:error</tt> option is set and the +labeler+ if the <tt>:label</tt>
721
- option is set. Finally, it calls the +wrapper+ to wrap the resulting tag before returning it.
730
+ that +Tag+. The +formatter+ first converts the +Input+ to a +Tag+, and then calls the +labeler+ if the <tt>:label</tt>
731
+ option is set, the +error_handler+ if the <tt>:error</tt> option is set, and the +helper+ if the <tt>:help</tt> option
732
+ is set . Finally, it calls the +wrapper+ to wrap the resulting tag before returning it.
722
733
 
723
734
  The +inputs_wrapper+ is called by <tt>Forme::Form#inputs</tt> and serves to wrap a bunch
724
735
  of related inputs.
@@ -747,6 +758,14 @@ This supports the following options:
747
758
 
748
759
  :error_attr :: A hash of attributes to use for the span with the error message
749
760
 
761
+ === +helper+
762
+
763
+ :default :: adds a span with the help text
764
+
765
+ This supports the following options:
766
+
767
+ :helper_attr :: A hash of attributes to use for the span with the help message
768
+
750
769
  === +labeler+
751
770
 
752
771
  :default :: uses implicit labels, where the tag is a child of the label tag
data/Rakefile CHANGED
@@ -50,15 +50,16 @@ end
50
50
 
51
51
  begin
52
52
  begin
53
+ raise LoadError if ENV['RSPEC1']
54
+ # RSpec 2+
55
+ require "rspec/core/rake_task"
56
+ spec_class = RSpec::Core::RakeTask
57
+ spec_files_meth = :pattern=
58
+ rescue LoadError
53
59
  # RSpec 1
54
60
  require "spec/rake/spectask"
55
61
  spec_class = Spec::Rake::SpecTask
56
62
  spec_files_meth = :spec_files=
57
- rescue LoadError
58
- # RSpec 2
59
- require "rspec/core/rake_task"
60
- spec_class = RSpec::Core::RakeTask
61
- spec_files_meth = :pattern=
62
63
  end
63
64
 
64
65
  spec = lambda do |name, files, d|
@@ -1,6 +1,5 @@
1
1
  require 'date'
2
2
  require 'bigdecimal'
3
- require 'forme/version'
4
3
 
5
4
  module Forme
6
5
  # Exception class for exceptions raised by Forme.
@@ -19,7 +18,7 @@ module Forme
19
18
  end
20
19
 
21
20
  # Array of all supported transformer types.
22
- TRANSFORMER_TYPES = [:formatter, :serializer, :wrapper, :error_handler, :labeler, :inputs_wrapper]
21
+ TRANSFORMER_TYPES = [:formatter, :serializer, :wrapper, :error_handler, :helper, :labeler, :inputs_wrapper]
23
22
 
24
23
  # Transformer symbols shared by wrapper and inputs_wrapper
25
24
  SHARED_WRAPPERS = [:tr, :table, :ol, :fieldset_ol]
@@ -92,7 +91,7 @@ module Forme
92
91
  case type
93
92
  when :inputs_wrapper
94
93
  yield
95
- when :labeler, :error_handler, :wrapper
94
+ when :labeler, :error_handler, :wrapper, :helper
96
95
  args.first
97
96
  else
98
97
  raise Error, "No matching #{type}: #{trans_name.inspect}"
@@ -111,7 +110,7 @@ module Forme
111
110
  def self.transformer(type, trans, default_opts)
112
111
  case trans
113
112
  when Symbol
114
- TRANSFORMERS[type][trans] || raise(Error, "invalid #{type}: #{trans.inspect} (valid #{type}s: #{TRANSFORMERS[type].keys.map{|k| k.inspect}.join(', ')})")
113
+ TRANSFORMERS[type][trans] || raise(Error, "invalid #{type}: #{trans.inspect} (valid #{type}s: #{TRANSFORMERS[type].keys.map(&:inspect).join(', ')})")
115
114
  when Hash
116
115
  if trans.has_key?(type)
117
116
  if v = trans[type]
@@ -130,1348 +129,7 @@ module Forme
130
129
  end
131
130
  end
132
131
  end
133
-
134
- # The +Form+ class is the main entry point to the library.
135
- # Using the +form+, +input+, +tag+, and +inputs+ methods, one can easily build
136
- # an abstract syntax tree of +Tag+ and +Input+ instances, which can be serialized
137
- # to a string using +to_s+.
138
- class Form
139
- # A hash of options for the form.
140
- attr_reader :opts
141
-
142
- # Set the default options for inputs by type. This should be a hash with
143
- # input type keys and values that are hashes of input options.
144
- attr_reader :input_defaults
145
-
146
- # The hidden tags to automatically add to the form.
147
- attr_reader :hidden_tags
148
-
149
- # The namespaces if any for the receiver's inputs. This can be used to
150
- # automatically setup namespaced class and id attributes.
151
- attr_accessor :namespaces
152
-
153
- # The +serializer+ determines how +Tag+ objects are transformed into strings.
154
- # Must respond to +call+ or be a registered symbol.
155
- attr_reader :serializer
156
-
157
- # Use appropriate Form subclass for object based on the current class, if the
158
- # object responds to +forme_form_class+.
159
- def self.new(obj=nil, opts={})
160
- if obj && obj.respond_to?(:forme_form_class) && !opts[:_forme_form_class_set]
161
- obj.forme_form_class(self).new(obj, opts.merge(:_forme_form_class_set=>true))
162
- else
163
- super
164
- end
165
- end
166
-
167
- # Create a +Form+ instance and yield it to the block,
168
- # injecting the opening form tag before yielding and
169
- # the closing form tag after yielding.
170
- #
171
- # Argument Handling:
172
- # No args :: Creates a +Form+ object with no options and not associated
173
- # to an +obj+, and with no attributes in the opening tag.
174
- # 1 hash arg :: Treated as opening form tag attributes, creating a
175
- # +Form+ object with no options.
176
- # 1 non-hash arg :: Treated as the +Form+'s +obj+, with empty options
177
- # and no attributes in the opening tag.
178
- # 2 hash args :: First hash is opening attributes, second hash is +Form+
179
- # options.
180
- # 1 non-hash arg, 1-2 hash args :: First argument is +Form+'s obj, second is
181
- # opening attributes, third if provided is
182
- # +Form+'s options.
183
- def self.form(obj=nil, attr={}, opts={}, &block)
184
- f = if obj.is_a?(Hash)
185
- raise Error, "Can't provide 3 hash arguments to form" unless opts.empty?
186
- opts = attr
187
- attr = obj
188
- new(opts)
189
- else
190
- new(obj, opts)
191
- end
192
-
193
- ins = opts[:inputs]
194
- button = opts[:button]
195
- if ins || button
196
- block = Proc.new do |form|
197
- form._inputs(ins, opts) if ins
198
- yield form if block_given?
199
- form.emit(form.button(button)) if button
200
- end
201
- end
202
-
203
- f.form(attr, &block)
204
- end
205
-
206
- # Creates a +Form+ object. Arguments:
207
- # obj :: Sets the obj for the form. If a hash, is merged with the +opts+ argument
208
- # to set the opts.
209
- # opts :: A hash of options for the form
210
- def initialize(obj=nil, opts={})
211
- @opts = opts.merge(obj.is_a?(Hash) ? obj : {:obj=>obj})
212
- @opts[:namespace] = Array(@opts[:namespace])
213
-
214
- if obj && obj.respond_to?(:forme_config)
215
- obj.forme_config(self)
216
- end
217
-
218
- config = CONFIGURATIONS[@opts[:config]||Forme.default_config]
219
- copy_inputs_wrapper_from_wrapper(@opts)
220
-
221
- TRANSFORMER_TYPES.each do |t|
222
- case @opts[t]
223
- when Symbol
224
- @opts[t] = Forme.transformer(t, @opts[t], @opts)
225
- when nil
226
- unless @opts.has_key?(t)
227
- @opts[t] = Forme.transformer(t, config, @opts)
228
- end
229
- end
230
- end
231
-
232
- @serializer = @opts[:serializer]
233
- @input_defaults = @opts[:input_defaults] || {}
234
- @hidden_tags = @opts[:hidden_tags]
235
- @nesting = []
236
- end
237
-
238
- # Create a form tag with the given attributes.
239
- def form(attr={}, &block)
240
- tag(:form, attr, method(:hidden_form_tags), &block)
241
- end
242
-
243
- # Empty method designed to ease integration with other libraries where
244
- # Forme is used in template code and some output implicitly
245
- # created by Forme needs to be injected into the template output.
246
- def emit(tag)
247
- end
248
-
249
- # Creates an +Input+ with the given +field+ and +opts+ associated with
250
- # the receiver, and add it to the list of children to the currently
251
- # open tag.
252
- #
253
- # If the form is associated with an +obj+, or the :obj key exists in
254
- # the +opts+ argument, treats the +field+ as a call to the +obj+. If
255
- # +obj+ responds to +forme_input+, that method is called with the +field+
256
- # and a copy of +opts+. Otherwise, the field is used as a method call
257
- # on the +obj+ and a text input is created with the result.
258
- #
259
- # If no +obj+ is associated with the receiver, +field+ represents an input
260
- # type (e.g. <tt>:text</tt>, <tt>:textarea</tt>, <tt>:select</tt>), and
261
- # an input is created directly with the +field+ and +opts+.
262
- def input(field, opts={})
263
- if opts.has_key?(:obj)
264
- opts = opts.dup
265
- obj = opts.delete(:obj)
266
- else
267
- obj = self.obj
268
- end
269
- input = if obj
270
- if obj.respond_to?(:forme_input)
271
- obj.forme_input(self, field, opts.dup)
272
- else
273
- opts = opts.dup
274
- opts[:key] = field unless opts.has_key?(:key)
275
- unless opts.has_key?(:value)
276
- opts[:value] = if obj.is_a?(Hash)
277
- obj[field]
278
- else
279
- obj.send(field)
280
- end
281
- end
282
- _input(:text, opts)
283
- end
284
- else
285
- _input(field, opts)
286
- end
287
- self << input
288
- input
289
- end
290
-
291
- # Create a new +Input+ associated with the receiver with the given
292
- # arguments, doing no other processing.
293
- def _input(*a)
294
- Input.new(self, *a)
295
- end
296
-
297
- # Creates a tag using the +inputs_wrapper+ (a fieldset by default), calls
298
- # input on each element of +inputs+, and yields if given a block.
299
- # You can use array arguments if you want inputs to be created with specific
300
- # options:
301
- #
302
- # f.inputs([:field1, :field2])
303
- # f.inputs([[:field1, {:name=>'foo'}], :field2])
304
- #
305
- # The given +opts+ are passed to the +inputs_wrapper+, and the default
306
- # +inputs_wrapper+ supports a <tt>:legend</tt> option that is used to
307
- # set the legend for the fieldset.
308
- #
309
- # +opts+ can also include transformer options itself (e.g. :wrapper), which
310
- # override the form's current transformer options for the duration of the block.
311
- # The exception is the :inputs_wrapper transformer option, which affects the
312
- # wrapper to use for this inputs call. You can use the :nested_inputs_wrapper
313
- # option to set the default :inputs_wrapper option for the duration of the block.
314
- #
315
- # This can also be called with a single hash argument to just use an options hash:
316
- #
317
- # f.inputs(:legend=>'Foo') do
318
- # # ...
319
- # end
320
- #
321
- # or even without any arguments:
322
- #
323
- # f.inputs do
324
- # # ...
325
- # end
326
- def inputs(inputs=[], opts={}, &block)
327
- _inputs(inputs, opts, &block)
328
- end
329
-
330
- # Internals of #inputs, should be used internally by the library, where #inputs
331
- # is designed for external use.
332
- def _inputs(inputs=[], opts={}) # :nodoc:
333
- if inputs.is_a?(Hash)
334
- opts = inputs.merge(opts)
335
- inputs = []
336
- end
337
-
338
- form_opts = {}
339
- form_opts[:inputs_wrapper] = opts[:nested_inputs_wrapper] if opts[:nested_inputs_wrapper]
340
- TRANSFORMER_TYPES.each do |t|
341
- if opts.has_key?(t) && t != :inputs_wrapper
342
- form_opts[t] = opts[t]
343
- end
344
- end
345
-
346
- Forme.transform(:inputs_wrapper, opts, @opts, self, opts) do
347
- with_opts(form_opts) do
348
- inputs.each do |i|
349
- emit(input(*i))
350
- end
351
- yield if block_given?
352
- end
353
- end
354
- end
355
-
356
- # Returns a string representing the opening of the form tag for serializers
357
- # that support opening tags.
358
- def open(attr)
359
- serializer.serialize_open(_tag(:form, attr)) if serializer.respond_to?(:serialize_open)
360
- end
361
-
362
- # Returns a string representing the closing of the form tag, for serializers
363
- # that support closing tags.
364
- def close
365
- serializer.serialize_close(_tag(:form)) if serializer.respond_to?(:serialize_close)
366
- end
367
-
368
- # Create a +Tag+ associated to the receiver with the given arguments and block,
369
- # doing no other processing.
370
- def _tag(*a, &block)
371
- tag = Tag.new(self, *a, &block)
372
- end
373
-
374
- # The object associated with this form, if any. If the +Form+ has an associated
375
- # obj, then calls to +input+ are assumed to be accessing fields of the object
376
- # instead to directly representing input types.
377
- def obj
378
- @opts[:obj]
379
- end
380
-
381
- # The current namespaces for the form, if any.
382
- def namespaces
383
- @opts[:namespace]
384
- end
385
-
386
- # Creates a +Tag+ associated to the receiver with the given arguments.
387
- # Add the tag to the the list of children for the currently open tag.
388
- # If a block is given, make this tag the currently open tag while inside
389
- # the block.
390
- def tag(*a, &block)
391
- tag = _tag(*a)
392
- self << tag
393
- nest(tag, &block) if block
394
- tag
395
- end
396
-
397
- # Aliased for tag. Workaround for issue with rails plugin.
398
- def tag_(*a, &block) # :nodoc:
399
- tag(*a, &block)
400
- end
401
-
402
- # Creates a :submit +Input+ with the given opts, adding it to the list
403
- # of children for the currently open tag.
404
- def button(opts={})
405
- opts = {:value=>opts} if opts.is_a?(String)
406
- input = _input(:submit, opts)
407
- self << input
408
- input
409
- end
410
-
411
- # Add the +Input+/+Tag+ instance given to the currently open tag.
412
- def <<(tag)
413
- if n = @nesting.last
414
- n << tag
415
- end
416
- end
417
-
418
- # Calls the block for each object in objs, using with_obj with the given namespace
419
- # and an index namespace (starting at 0).
420
- def each_obj(objs, namespace=nil)
421
- objs.each_with_index do |obj, i|
422
- with_obj(obj, Array(namespace) + [i]) do
423
- yield obj, i
424
- end
425
- end
426
- end
427
-
428
- # Return a new string that will not be html escaped by the default serializer.
429
- def raw(s)
430
- Forme.raw(s)
431
- end
432
-
433
- # Marks the string as containing already escaped output. Returns string given
434
- # by default, but subclasses for specific web frameworks can handle automatic
435
- # html escaping by overriding this.
436
- def raw_output(s)
437
- s
438
- end
439
-
440
- # Temporarily override the given object and namespace for the form. Any given
441
- # namespaces are appended to the form's current namespace.
442
- def with_obj(obj, namespace=nil)
443
- with_opts(:obj=>obj, :namespace=>@opts[:namespace]+Array(namespace)) do
444
- yield obj
445
- end
446
- end
447
-
448
- # Temporarily override the opts for the form for the duration of the block.
449
- # This merges the given opts with the form's current opts, restoring
450
- # the previous opts before returning.
451
- def with_opts(opts)
452
- orig_opts = @opts
453
- @opts = orig_opts.merge(opts)
454
- copy_inputs_wrapper_from_wrapper(opts, @opts)
455
- yield
456
- ensure
457
- @opts = orig_opts if orig_opts
458
- end
459
-
460
- private
461
-
462
- # Copy the :wrapper option to :inputs_wrapper in output_opts if only :wrapper
463
- # is present in input_opts and the :wrapper option value is a shared wrapper.
464
- def copy_inputs_wrapper_from_wrapper(input_opts, output_opts=input_opts)
465
- if input_opts[:wrapper] && !input_opts[:inputs_wrapper] && SHARED_WRAPPERS.include?(input_opts[:wrapper])
466
- output_opts[:inputs_wrapper] = output_opts[:wrapper]
467
- end
468
- end
469
-
470
- # Return array of hidden tags to use for this form,
471
- # or nil if the form does not have hidden tags added automatically.
472
- def hidden_form_tags(form_tag)
473
- if hidden_tags
474
- tags = []
475
- hidden_tags.each do |hidden_tag|
476
- hidden_tag = hidden_tag.call(form_tag) if hidden_tag.respond_to?(:call)
477
- tags.concat(parse_hidden_tags(hidden_tag))
478
- end
479
- tags
480
- end
481
- end
482
-
483
- # Handle various types of hidden tags for the form.
484
- def parse_hidden_tags(hidden_tag)
485
- case hidden_tag
486
- when Array
487
- hidden_tag
488
- when Tag, String
489
- [hidden_tag]
490
- when Hash
491
- hidden_tag.map{|k,v| _tag(:input, :type=>:hidden, :name=>k, :value=>v)}
492
- when nil
493
- []
494
- else
495
- raise Error, "unhandled hidden_tag response: #{hidden_tag.inspect}"
496
- end
497
- end
498
-
499
- # Make the given tag the currently open tag, and yield. After the
500
- # block returns, make the previously open tag the currently open
501
- # tag.
502
- def nest(tag)
503
- @nesting << tag
504
- yield self
505
- ensure
506
- @nesting.pop
507
- end
508
-
509
- # Return a serialized opening tag for the given tag.
510
- def serialize_open(tag)
511
- raw_output(serializer.serialize_open(tag)) if serializer.respond_to?(:serialize_open)
512
- end
513
-
514
- # Return a serialized closing tag for the given tag.
515
- def serialize_close(tag)
516
- raw_output(serializer.serialize_close(tag)) if serializer.respond_to?(:serialize_close)
517
- end
518
- end
519
-
520
- # High level abstract tag form, transformed by formatters into the lower
521
- # level +Tag+ form (or an array of them).
522
- class Input
523
- # The +Form+ object related to the receiver.
524
- attr_reader :form
525
-
526
- # The type of input, should be a symbol (e.g. :submit, :text, :select).
527
- attr_reader :type
528
-
529
- # The options hash for the Input.
530
- attr_reader :opts
531
-
532
- # The options hash in use by the form at the time of the Input's instantiation.
533
- attr_reader :form_opts
534
-
535
- # Set the +form+, +type+, and +opts+.
536
- def initialize(form, type, opts={})
537
- @form, @type = form, type
538
- defaults = form.input_defaults
539
- @opts = (defaults.fetch(type){defaults[type.to_s]} || {}).merge(opts)
540
- @form_opts = form.opts
541
- end
542
-
543
- # Replace the +opts+ by merging the given +hash+ into +opts+,
544
- # without modifying +opts+.
545
- def merge_opts(hash)
546
- @opts = @opts.merge(hash)
547
- end
548
-
549
- # Create a new +Tag+ instance with the given arguments and block
550
- # related to the receiver's +form+.
551
- def tag(*a, &block)
552
- form._tag(*a, &block)
553
- end
554
-
555
- # Return a string containing the serialized content of the receiver.
556
- def to_s
557
- form.raw_output(Forme.transform(:serializer, @opts, @form_opts, self))
558
- end
559
-
560
- # Transform the receiver into a lower level +Tag+ form (or an array
561
- # of them).
562
- def format
563
- Forme.transform(:formatter, @opts, @form_opts, self)
564
- end
565
- end
566
-
567
- # Low level abstract tag form, where each instance represents a
568
- # html tag with attributes and children.
569
- class Tag
570
- # The +Form+ object related to the receiver.
571
- attr_reader :form
572
-
573
- # The type of tag, should be a symbol (e.g. :input, :select).
574
- attr_reader :type
575
-
576
- # The attributes hash of this receiver.
577
- attr_reader :attr
578
-
579
- # An array instance representing the children of the receiver,
580
- # or possibly +nil+ if the receiver has no children.
581
- attr_reader :children
582
-
583
- # Set the +form+, +type+, +attr+, and +children+.
584
- def initialize(form, type, attr={}, children=nil)
585
- @form, @type, @attr = form, type, (attr||{})
586
- @children = parse_children(children)
587
- end
588
-
589
- # Adds a child to the array of receiver's children.
590
- def <<(child)
591
- if children
592
- children << child
593
- else
594
- @children = [child]
595
- end
596
- end
597
-
598
- # Create a new +Tag+ instance with the given arguments and block
599
- # related to the receiver's +form+.
600
- def tag(*a, &block)
601
- form._tag(*a, &block)
602
- end
603
-
604
- # Return a string containing the serialized content of the receiver.
605
- def to_s
606
- form.raw_output(Forme.transform(:serializer, @opts, @form.opts, self))
607
- end
608
-
609
- private
610
-
611
- # Convert children constructor argument into the children to use for the tag.
612
- def parse_children(children)
613
- case children
614
- when Array
615
- children
616
- when Proc, Method
617
- parse_children(children.call(self))
618
- when nil
619
- nil
620
- else
621
- [children]
622
- end
623
- end
624
- end
625
-
626
- # Empty module for marking objects as "raw", where they will no longer
627
- # html escaped by the default serializer.
628
- module Raw
629
- end
630
-
631
- # A String subclass that includes Raw, which will cause the default
632
- # serializer to no longer html escape the string.
633
- class RawString < ::String
634
- include Raw
635
- end
636
-
637
- # The default formatter used by the library. Any custom formatters should
638
- # probably inherit from this formatter unless they have very special needs.
639
- #
640
- # Unlike most other transformers which are registered as instances and use
641
- # a functional style, this class is registered as a class due to the large
642
- # amount of state it uses.
643
- #
644
- # Registered as :default.
645
- class Formatter
646
- Forme.register_transformer(:formatter, :default, self)
647
-
648
- # These options are copied directly from the options hash to the the
649
- # attributes hash, so they don't need to be specified in the :attr
650
- # option. However, they can be specified in both places, and if so,
651
- # the :attr option version takes precedence.
652
- ATTRIBUTE_OPTIONS = [:name, :id, :placeholder, :value, :style]
653
-
654
- # Options copied from the options hash into the attributes hash,
655
- # where a true value in the options hash sets the attribute
656
- # value to the same name as the key.
657
- ATTRIBUTE_BOOLEAN_OPTIONS = [:autofocus, :required, :disabled]
658
-
659
- # Create a new instance and call it
660
- def self.call(input)
661
- new.call(input)
662
- end
663
-
664
- # The +Form+ instance for the receiver, taken from the +input+.
665
- attr_reader :form
666
-
667
- # The +Input+ instance for the receiver. This is what the receiver
668
- # converts to the lower level +Tag+ form (or an array of them).
669
- attr_reader :input
670
-
671
- # The attributes to to set on the lower level +Tag+ form returned.
672
- # This are derived from the +input+'s +opts+, but some processing is done on
673
- # them.
674
- attr_reader :attr
675
-
676
- # The +opts+ hash of the +input+.
677
- attr_reader :opts
678
-
679
- # Used to specify the value of the hidden input created for checkboxes.
680
- # Since the default for an unspecified checkbox value is 1, the default is
681
- # 0. If the checkbox value is 't', the hidden value is 'f', since that is
682
- # common usage for boolean values.
683
- CHECKBOX_MAP = Hash.new(0)
684
- CHECKBOX_MAP['t'] = 'f'
685
-
686
- # Transform the +input+ into a +Tag+ instance (or an array of them),
687
- # wrapping it with the +form+'s wrapper, and the form's +error_handler+
688
- # and +labeler+ if the +input+ has an <tt>:error</tt> or <tt>:label</tt>
689
- # options.
690
- def call(input)
691
- @input = input
692
- @form = input.form
693
- attr = input.opts[:attr]
694
- @attr = attr ? attr.dup : {}
695
- @opts = input.opts
696
- normalize_options
697
-
698
- tag = convert_to_tag(input.type)
699
- tag = wrap_tag_with_label(tag) if input.opts[:label]
700
- tag = wrap_tag_with_error(tag) if input.opts[:error]
701
- wrap_tag(tag)
702
- end
703
-
704
- private
705
-
706
- # Dispatch to a format_<i>type</i> method if there is one that matches the
707
- # type, otherwise, call +_format_input+ with the given +type+.
708
- def convert_to_tag(type)
709
- meth = :"format_#{type}"
710
- if respond_to?(meth, true)
711
- send(meth)
712
- else
713
- _format_input(type)
714
- end
715
- end
716
-
717
- # If the checkbox has a name, will create a hidden input tag with the
718
- # same name that comes before this checkbox. That way, if the checkbox
719
- # is checked, the web app will generally see the value of the checkbox, and
720
- # if it is not checked, the web app will generally see the value of the hidden
721
- # input tag.
722
- def format_checkbox
723
- @attr[:type] = :checkbox
724
- @attr[:checked] = :checked if @opts[:checked]
725
- if @attr[:name] && !@opts[:no_hidden]
726
- attr = {:type=>:hidden}
727
- unless attr[:value] = @opts[:hidden_value]
728
- attr[:value] = CHECKBOX_MAP[@attr[:value]]
729
- end
730
- attr[:id] = "#{@attr[:id]}_hidden" if @attr[:id]
731
- attr[:name] = @attr[:name]
732
- [tag(:input, attr), tag(:input)]
733
- else
734
- tag(:input)
735
- end
736
- end
737
-
738
- # For radio buttons, recognizes the :checked option and sets the :checked
739
- # attribute in the tag appropriately.
740
- def format_radio
741
- @attr[:checked] = :checked if @opts[:checked]
742
- @attr[:type] = :radio
743
- tag(:input)
744
- end
745
-
746
- # Use a date input by default. If the :as=>:select option is given,
747
- # use a multiple select box for the options.
748
- def format_date
749
- if @opts[:as] == :select
750
- name = @attr[:name]
751
- id = @attr[:id]
752
- v = @attr[:value]
753
- if v
754
- v = Date.parse(v) unless v.is_a?(Date)
755
- values = {}
756
- values[:year], values[:month], values[:day] = v.year, v.month, v.day
757
- end
758
- ops = {:year=>1900..2050, :month=>1..12, :day=>1..31}
759
- input.merge_opts(:label_for=>"#{id}_year")
760
- [:year, '-', :month, '-', :day].map{|x| x.is_a?(String) ? x : form._input(:select, @opts.merge(:label=>nil, :wrapper=>nil, :error=>nil, :name=>"#{name}[#{x}]", :id=>"#{id}_#{x}", :value=>values[x], :options=>ops[x].map{|x| [sprintf("%02i", x), x]})).format}
761
- else
762
- _format_input(:date)
763
- end
764
- end
765
-
766
- # Use a datetime input by default. If the :as=>:select option is given,
767
- # use a multiple select box for the options.
768
- def format_datetime
769
- if @opts[:as] == :select
770
- name = @attr[:name]
771
- id = @attr[:id]
772
- v = @attr[:value]
773
- v = DateTime.parse(v) unless v.is_a?(Time) || v.is_a?(DateTime)
774
- values = {}
775
- values[:year], values[:month], values[:day], values[:hour], values[:minute], values[:second] = v.year, v.month, v.day, v.hour, v.min, v.sec
776
- ops = {:year=>1900..2050, :month=>1..12, :day=>1..31, :hour=>0..23, :minute=>0..59, :second=>0..59}
777
- input.merge_opts(:label_for=>"#{id}_year")
778
- [:year, '-', :month, '-', :day, ' ', :hour, ':', :minute, ':', :second].map{|x| x.is_a?(String) ? x : form._input(:select, @opts.merge(:label=>nil, :wrapper=>nil, :error=>nil, :name=>"#{name}[#{x}]", :id=>"#{id}_#{x}", :value=>values[x], :options=>ops[x].map{|x| [sprintf("%02i", x), x]})).format}
779
- else
780
- _format_input(:datetime)
781
- end
782
- end
783
-
784
- # The default fallback method for handling inputs. Assumes an input tag
785
- # with the type attribute set to input.
786
- def _format_input(type)
787
- @attr[:type] = type
788
- copy_options_to_attributes([:size, :maxlength])
789
- tag(:input)
790
- end
791
-
792
- # Takes a select input and turns it into a select tag with (possibly) option
793
- # children tags.
794
- def format_select
795
- @attr[:multiple] = :multiple if @opts[:multiple]
796
- copy_options_to_attributes([:size])
797
-
798
- if os = process_select_options(@opts[:options])
799
- os = os.map do |label, value, sel, attrs|
800
- if value || sel
801
- attrs = attrs.dup
802
- attrs[:value] = value if value
803
- attrs[:selected] = :selected if sel
804
- end
805
- tag(:option, attrs, [label])
806
- end
807
- end
808
- tag(:select, @attr, os)
809
- end
810
-
811
- def format_checkboxset
812
- @opts[:multiple] = true unless @opts.has_key?(:multiple)
813
- _format_set(:checkbox, :no_hidden=>true, :multiple=>true)
814
- end
815
-
816
- def format_radioset
817
- _format_set(:radio)
818
- end
819
-
820
- def _format_set(type, tag_attrs={})
821
- raise Error, "can't have radioset with no options" unless os = @opts[:options]
822
- key = @opts[:key]
823
- name = @opts[:name]
824
- id = @opts[:id]
825
- if @opts[:error]
826
- @opts[:set_error] = @opts.delete(:error)
827
- end
828
- if @opts[:label]
829
- @opts[:set_label] = @opts.delete(:label)
830
- end
831
- tag_wrapper = @opts.delete(:tag_wrapper) || :default
832
- wrapper = Forme.transformer(:wrapper, @opts, @input.form_opts)
833
-
834
- tags = process_select_options(os).map do |label, value, sel, attrs|
835
- value ||= label
836
- r_opts = attrs.merge(tag_attrs).merge(:label=>label||value, :label_attr=>{:class=>:option}, :wrapper=>tag_wrapper)
837
- r_opts[:value] ||= value if value
838
- r_opts[:checked] ||= :checked if sel
839
-
840
- if name
841
- r_opts[:name] ||= name
842
- end
843
- if id
844
- r_opts[:id] ||= "#{id}_#{value}"
845
- end
846
- if key
847
- r_opts[:key] ||= key
848
- r_opts[:key_id] ||= value
849
- end
850
-
851
- form._input(type, r_opts)
852
- end
853
-
854
- if (last_input = tags.last) && last_input.is_a?(Input)
855
- last_input.opts[:error] = @opts[:set_error]
856
- else
857
- tags << form._tag(:span, {:class=>'error_message'}, [@opts[:set_error]])
858
- end
859
- tags.unshift(form._tag(:span, {:class=>:label}, @opts[:set_label])) if @opts[:set_label]
860
- wrapper.call(tags, form._input(type, opts)) if wrapper
861
- tags
862
- end
863
-
864
- # Formats a textarea. Respects the following options:
865
- # :value :: Sets value as the child of the textarea.
866
- def format_textarea
867
- copy_options_to_attributes([:cols, :rows])
868
- if val = @attr.delete(:value)
869
- tag(:textarea, @attr, [val])
870
- else
871
- tag(:textarea)
872
- end
873
- end
874
-
875
- # Copy option values for given keys to the attributes unless the
876
- # attributes already have a value for the key.
877
- def copy_options_to_attributes(keys)
878
- keys.each do |k|
879
- if @opts.has_key?(k) && !@attr.has_key?(k)
880
- @attr[k] = @opts[k]
881
- end
882
- end
883
- end
884
-
885
- # Set attribute values for given keys to be the same as the key
886
- # unless the attributes already have a value for the key.
887
- def copy_boolean_options_to_attributes(keys)
888
- keys.each do |k|
889
- if @opts[k] && !@attr.has_key?(k)
890
- @attr[k] = k
891
- end
892
- end
893
- end
894
-
895
- # Normalize the options used for all input types.
896
- def normalize_options
897
- copy_options_to_attributes(ATTRIBUTE_OPTIONS)
898
- copy_boolean_options_to_attributes(ATTRIBUTE_BOOLEAN_OPTIONS)
899
- handle_key_option
900
-
901
- Forme.attr_classes(@attr, @opts[:class]) if @opts.has_key?(:class)
902
- Forme.attr_classes(@attr, 'error') if @opts[:error]
903
-
904
- if data = opts[:data]
905
- data.each do |k, v|
906
- sym = :"data-#{k}"
907
- @attr[sym] = v unless @attr.has_key?(sym)
908
- end
909
- end
910
- end
911
-
912
- # Have the :key option possibly set the name, id, and/or value attributes if not already set.
913
- def handle_key_option
914
- if key = @opts[:key]
915
- unless @attr[:name] || @attr['name']
916
- @attr[:name] = namespaced_name(key, @opts[:array] || @opts[:multiple])
917
- if !@attr.has_key?(:value) && !@attr.has_key?('value') && (values = @form.opts[:values])
918
- set_value_from_namespaced_values(namespaces, values, key)
919
- end
920
- end
921
- unless @attr[:id] || @attr['id']
922
- id = namespaced_id(key)
923
- if suffix = @opts[:key_id]
924
- id << '_' << suffix.to_s
925
- end
926
- @attr[:id] = id
927
- end
928
- end
929
- end
930
-
931
- # Array of namespaces to use for the input
932
- def namespaces
933
- input.form_opts[:namespace]
934
- end
935
-
936
- # Return a unique id attribute for the +field+, based on the current namespaces.
937
- def namespaced_id(field)
938
- "#{namespaces.join('_')}#{'_' unless namespaces.empty?}#{field}"
939
- end
940
-
941
- # Return a unique name attribute for the +field+, based on the current namespaces.
942
- # If +multiple+ is true, end the name with [] so that param parsing will treat
943
- # the name as part of an array.
944
- def namespaced_name(field, multiple=false)
945
- if namespaces.empty?
946
- if multiple
947
- "#{field}[]"
948
- else
949
- field
950
- end
951
- else
952
- root, *nsps = namespaces
953
- "#{root}#{nsps.map{|n| "[#{n}]"}.join}[#{field}]#{'[]' if multiple}"
954
- end
955
- end
956
-
957
- # Set the values option based on the (possibly nested) values
958
- # hash given, array of namespaces, and key.
959
- def set_value_from_namespaced_values(namespaces, values, key)
960
- namespaces.each do |ns|
961
- v = values[ns] || values[ns.to_s]
962
- return unless v
963
- values = v
964
- end
965
-
966
- @attr[:value] = values.fetch(key){values.fetch(key.to_s){return}}
967
- end
968
-
969
- # Returns an array of arrays, where each array entry contains the label, value,
970
- # currently selected flag, and attributes for that tag.
971
- def process_select_options(os)
972
- if os
973
- vm = @opts[:value_method]
974
- tm = @opts[:text_method]
975
- sel = @opts[:selected] || @attr.delete(:value)
976
-
977
- if @opts[:multiple]
978
- sel = Array(sel)
979
- cmp = lambda{|v| sel.include?(v)}
980
- else
981
- cmp = lambda{|v| v == sel}
982
- end
983
-
984
- os = os.map do |x|
985
- attr = {}
986
- if tm
987
- text = x.send(tm)
988
- val = x.send(vm) if vm
989
- elsif x.is_a?(Array)
990
- text = x.first
991
- val = x.last
992
-
993
- if val.is_a?(Hash)
994
- value = val[:value]
995
- attr.merge!(val)
996
- val = value
997
- end
998
- else
999
- text = x
1000
- end
1001
-
1002
- [text, val, val ? cmp.call(val) : cmp.call(text), attr]
1003
- end
1004
-
1005
- if prompt = @opts[:add_blank]
1006
- unless prompt.is_a?(String)
1007
- prompt = Forme.default_add_blank_prompt
1008
- end
1009
- os.unshift([prompt, '', false, {}])
1010
- end
1011
-
1012
- os
1013
- end
1014
- end
1015
-
1016
- # Create a +Tag+ instance related to the receiver's +form+ with the given
1017
- # arguments.
1018
- def tag(type, attr=@attr, children=nil)
1019
- form._tag(type, attr, children)
1020
- end
1021
-
1022
- # Wrap the tag with the form's +wrapper+.
1023
- def wrap_tag(tag)
1024
- Forme.transform(:wrapper, @opts, input.form_opts, tag, input)
1025
- end
1026
-
1027
- # Wrap the tag with the form's +error_handler+.
1028
- def wrap_tag_with_error(tag)
1029
- Forme.transform(:error_handler, @opts, input.form_opts, tag, input)
1030
- end
1031
-
1032
- # Wrap the tag with the form's +labeler+.
1033
- def wrap_tag_with_label(tag)
1034
- Forme.transform(:labeler, @opts, input.form_opts, tag, input)
1035
- end
1036
- end
1037
-
1038
- # Formatter that disables all input fields,
1039
- #
1040
- # Registered as :disabled.
1041
- class Formatter::Disabled < Formatter
1042
- Forme.register_transformer(:formatter, :disabled, self)
1043
-
1044
- private
1045
-
1046
- # Unless the :disabled option is specifically set
1047
- # to +false+, set the :disabled attribute on the
1048
- # resulting tag.
1049
- def normalize_options
1050
- if @opts[:disabled] == false
1051
- super
1052
- else
1053
- super
1054
- @attr[:disabled] = :disabled
1055
- end
1056
- end
1057
- end
1058
-
1059
- # Formatter that uses span tags with text for most input types,
1060
- # and disables radio/checkbox inputs.
1061
- #
1062
- # Registered as :readonly.
1063
- class Formatter::ReadOnly < Formatter
1064
- Forme.register_transformer(:formatter, :readonly, self)
1065
-
1066
- private
1067
-
1068
- # Disabled checkbox inputs.
1069
- def format_checkbox
1070
- @attr[:disabled] = :disabled
1071
- super
1072
- end
1073
-
1074
- # Use a span with text instead of an input field.
1075
- def _format_input(type)
1076
- tag(:span, {}, @attr[:value])
1077
- end
1078
-
1079
- # Disabled radio button inputs.
1080
- def format_radio
1081
- @attr[:disabled] = :disabled
1082
- super
1083
- end
1084
-
1085
- # Use a span with text of the selected values instead of a select box.
1086
- def format_select
1087
- t = super
1088
- children = [t.children.select{|o| o.attr[:selected]}.map{|o| o.children}.join(', ')] if t.children
1089
- tag(:span, {}, children)
1090
- end
1091
-
1092
- # Use a span with text instead of a text area.
1093
- def format_textarea
1094
- tag(:span, {}, @attr[:value])
1095
- end
1096
- end
1097
-
1098
- # Default error handler used by the library, using an "error" class
1099
- # for the input field and a span tag with an "error_message" class
1100
- # for the error message.
1101
- #
1102
- # Registered as :default.
1103
- class ErrorHandler
1104
- Forme.register_transformer(:error_handler, :default, new)
1105
-
1106
- # Return tag with error message span tag after it.
1107
- def call(tag, input)
1108
- attr = input.opts[:error_attr]
1109
- attr = attr ? attr.dup : {}
1110
- Forme.attr_classes(attr, 'error_message')
1111
- [tag, input.tag(:span, attr, input.opts[:error])]
1112
- end
1113
- end
1114
-
1115
- # Default labeler used by the library, using implicit labels (where the
1116
- # label tag encloses the other tag).
1117
- #
1118
- # Registered as :default.
1119
- class Labeler
1120
- Forme.register_transformer(:labeler, :default, new)
1121
-
1122
- # Return a label tag wrapping the given tag. For radio and checkbox
1123
- # inputs, the label occurs directly after the tag, for all other types,
1124
- # the label occurs before the tag.
1125
- def call(tag, input)
1126
- label = input.opts[:label]
1127
- label_position = input.opts[:label_position]
1128
- if [:radio, :checkbox].include?(input.type)
1129
- if input.type == :checkbox && tag.is_a?(Array) && tag.length == 2 && tag.first.attr[:type].to_s == 'hidden'
1130
- t = if label_position == :before
1131
- [label, ' ', tag.last]
1132
- else
1133
- [tag.last, ' ', label]
1134
- end
1135
- return [tag.first , input.tag(:label, input.opts[:label_attr]||{}, t)]
1136
- elsif label_position == :before
1137
- t = [label, ' ', tag]
1138
- else
1139
- t = [tag, ' ', label]
1140
- end
1141
- elsif label_position == :after
1142
- t = [tag, ' ', label]
1143
- else
1144
- t = [label, ": ", tag]
1145
- end
1146
- input.tag(:label, input.opts[:label_attr]||{}, t)
1147
- end
1148
- end
1149
-
1150
- # Explicit labeler that creates a separate label tag that references
1151
- # the given tag's id using a +for+ attribute. Requires that all tags
1152
- # with labels have +id+ fields.
1153
- #
1154
- # Registered as :explicit.
1155
- class Labeler::Explicit
1156
- Forme.register_transformer(:labeler, :explicit, new)
1157
-
1158
- # Return an array with a label tag as the first entry and +tag+ as
1159
- # a second entry. If the +input+ has a :label_for option, use that,
1160
- # otherwise use the input's :id option. If neither the :id or
1161
- # :label_for option is used, the label created will not be
1162
- # associated with an input.
1163
- def call(tag, input)
1164
- unless id = input.opts[:id]
1165
- if key = input.opts[:key]
1166
- namespaces = input.form_opts[:namespace]
1167
- id = "#{namespaces.join('_')}#{'_' unless namespaces.empty?}#{key}"
1168
- if key_id = input.opts[:key_id]
1169
- id << "_#{key_id.to_s}"
1170
- end
1171
- end
1172
- end
1173
-
1174
- label_attr = input.opts[:label_attr]
1175
- label_attr = label_attr ? label_attr.dup : {}
1176
- label_attr[:for] ||= input.opts.fetch(:label_for, id)
1177
- lpos = input.opts[:label_position] || ([:radio, :checkbox].include?(input.type) ? :after : :before)
1178
-
1179
- Forme.attr_classes(label_attr, "label-#{lpos}")
1180
- label = input.tag(:label, label_attr, [input.opts[:label]])
1181
-
1182
- t = if lpos == :before
1183
- [label, tag]
1184
- else
1185
- [tag, label]
1186
- end
1187
-
1188
- t
1189
- end
1190
- end
1191
-
1192
- Forme.register_transformer(:wrapper, :default){|tag, input| tag}
1193
- [:li, :p, :div, :span, :td].each do |x|
1194
- Forme.register_transformer(:wrapper, x){|tag, input| input.tag(x, input.opts[:wrapper_attr], Array(tag))}
1195
- end
1196
- Forme.register_transformer(:wrapper, :trtd) do |tag, input|
1197
- a = Array(tag).flatten
1198
- labels, other = a.partition{|e| e.is_a?(Tag) && e.type.to_s == 'label'}
1199
- if labels.length == 1
1200
- ltd = labels
1201
- rtd = other
1202
- elsif a.length == 1
1203
- ltd = [a.first]
1204
- rtd = a[1..-1]
1205
- else
1206
- ltd = a
1207
- end
1208
- input.tag(:tr, input.opts[:wrapper_attr], [input.tag(:td, {}, ltd), input.tag(:td, {}, rtd)])
1209
- end
1210
- {:tr=>:td, :table=>:trtd, :ol=>:li, :fieldset_ol=>:li}.each do |k, v|
1211
- Forme.register_transformer(:wrapper, k, TRANSFORMERS[:wrapper][v])
1212
- end
1213
-
1214
- # Default inputs_wrapper used by the library, uses a <fieldset>.
1215
- #
1216
- # Registered as :default.
1217
- class InputsWrapper
1218
- Forme.register_transformer(:inputs_wrapper, :default, new)
1219
-
1220
- # Wrap the inputs in a <fieldset>. If the :legend
1221
- # option is given, add a <legend> tag as the first
1222
- # child of the fieldset.
1223
- def call(form, opts)
1224
- attr = opts[:attr] ? opts[:attr].dup : {}
1225
- Forme.attr_classes(attr, 'inputs')
1226
- if legend = opts[:legend]
1227
- form.tag(:fieldset, attr) do
1228
- form.emit(form.tag(:legend, opts[:legend_attr], legend))
1229
- yield
1230
- end
1231
- else
1232
- form.tag(:fieldset, attr, &Proc.new)
1233
- end
1234
- end
1235
- end
1236
-
1237
- # Use a <fieldset> and an <ol> tag to wrap the inputs.
1238
- #
1239
- # Registered as :fieldset_ol.
1240
- class InputsWrapper::FieldSetOL < InputsWrapper
1241
- Forme.register_transformer(:inputs_wrapper, :fieldset_ol, new)
1242
-
1243
- # Wrap the inputs in a <fieldset> and a <ol> tag.
1244
- def call(form, opts)
1245
- super(form, opts){form.tag_(:ol){yield}}
1246
- end
1247
- end
1248
-
1249
- # Use an <ol> tag to wrap the inputs.
1250
- #
1251
- # Registered as :ol.
1252
- class InputsWrapper::OL
1253
- Forme.register_transformer(:inputs_wrapper, :ol, new)
1254
-
1255
- # Wrap the inputs in an <ol> tag
1256
- def call(form, opts, &block)
1257
- form.tag(:ol, opts[:attr], &block)
1258
- end
1259
- end
1260
-
1261
- # Use a <div> tag to wrap the inputs.
1262
- #
1263
- # Registered as :div.
1264
- class InputsWrapper::Div
1265
- Forme.register_transformer(:inputs_wrapper, :div, new)
1266
-
1267
- # Wrap the inputs in an <div> tag
1268
- def call(form, opts, &block)
1269
- form.tag(:div, opts[:attr], &block)
1270
- end
1271
- end
1272
-
1273
- # Use a <tr> tag to wrap the inputs.
1274
- #
1275
- # Registered as :tr.
1276
- class InputsWrapper::TR
1277
- Forme.register_transformer(:inputs_wrapper, :tr, new)
1278
-
1279
- # Wrap the inputs in an <tr> tag
1280
- def call(form, opts, &block)
1281
- form.tag(:tr, opts[:attr], &block)
1282
- end
1283
- end
1284
-
1285
- # Use a <table> tag to wrap the inputs.
1286
- #
1287
- # Registered as :table.
1288
- class InputsWrapper::Table
1289
- Forme.register_transformer(:inputs_wrapper, :table, new)
1290
-
1291
- # Wrap the inputs in a <table> tag.
1292
- def call(form, opts, &block)
1293
- attr = opts[:attr] ? opts[:attr].dup : {}
1294
- form.tag(:table, attr) do
1295
- if legend = opts[:legend]
1296
- form.emit(form.tag(:caption, opts[:legend_attr], legend))
1297
- end
1298
-
1299
- if (labels = opts[:labels]) && !labels.empty?
1300
- form.emit(form.tag(:tr, {}, labels.map{|l| form._tag(:th, {}, l)}))
1301
- end
1302
-
1303
- yield
1304
- end
1305
- end
1306
- end
1307
-
1308
- # Default serializer class used by the library. Any other serializer
1309
- # classes that want to produce html should probably subclass this class.
1310
- #
1311
- # Registered as :default.
1312
- class Serializer
1313
- Forme.register_transformer(:serializer, :default, new)
1314
-
1315
- # Borrowed from Rack::Utils, map of single character strings to html escaped versions.
1316
- ESCAPE_HTML = {"&" => "&amp;", "<" => "&lt;", ">" => "&gt;", "'" => "&#39;", '"' => "&quot;"}
1317
-
1318
- # A regexp that matches all html characters requiring escaping.
1319
- ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
1320
-
1321
- # Which tags are self closing (such tags ignore children).
1322
- SELF_CLOSING = [:img, :input]
1323
-
1324
- # Serialize the tag object to an html string. Supports +Tag+ instances,
1325
- # +Input+ instances (recursing into +call+ with the result of formatting the input),
1326
- # arrays (recurses into +call+ for each entry and joins the result), and
1327
- # (html escapes the string version of them, unless they include the +Raw+
1328
- # module, in which case no escaping is done).
1329
- def call(tag)
1330
- case tag
1331
- when Tag
1332
- if SELF_CLOSING.include?(tag.type)
1333
- "<#{tag.type}#{attr_html(tag.attr)}/>"
1334
- else
1335
- "#{serialize_open(tag)}#{call(tag.children)}#{serialize_close(tag)}"
1336
- end
1337
- when Input
1338
- call(tag.format)
1339
- when Array
1340
- tag.map{|x| call(x)}.join
1341
- when DateTime, Time
1342
- format_time(tag)
1343
- when Date
1344
- format_date(tag)
1345
- when BigDecimal
1346
- tag.to_s('F')
1347
- when Raw
1348
- tag.to_s
1349
- else
1350
- h tag
1351
- end
1352
- end
1353
-
1354
- # Returns the opening part of the given tag.
1355
- def serialize_open(tag)
1356
- "<#{tag.type}#{attr_html(tag.attr)}>"
1357
- end
1358
-
1359
- # Returns the closing part of the given tag.
1360
- def serialize_close(tag)
1361
- "</#{tag.type}>"
1362
- end
1363
-
1364
- private
1365
-
1366
- # Return a string in ISO format representing the +Date+ instance.
1367
- def format_date(date)
1368
- date.strftime("%F")
1369
- end
1370
-
1371
- # Return a string in ISO format representing the +Time+ or +DateTime+ instance.
1372
- def format_time(time)
1373
- time.strftime("%F %H:%M:%S%Z")
1374
- end
1375
-
1376
- # Escape ampersands, brackets and quotes to their HTML/XML entities.
1377
- def h(string)
1378
- string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
1379
- end
1380
-
1381
- # Join attribute values that are arrays with spaces instead of an empty
1382
- # string.
1383
- def attr_value(v)
1384
- if v.is_a?(Array)
1385
- v.map{|c| attr_value(c)}.join(' ')
1386
- else
1387
- call(v)
1388
- end
1389
- end
1390
-
1391
- # Transforms the +tag+'s attributes into an html string, sorting by the keys
1392
- # and quoting and html escaping the values.
1393
- def attr_html(attr)
1394
- attr = attr.to_a.reject{|k,v| v.nil?}
1395
- " #{attr.map{|k, v| "#{k}=\"#{attr_value(v)}\""}.sort.join(' ')}" unless attr.empty?
1396
- end
1397
- end
1398
-
1399
- # Overrides formatting of dates and times to use an American format without
1400
- # timezones.
1401
- class Serializer::AmericanTime < Serializer
1402
- Forme.register_transformer(:serializer, :html_usa, new)
1403
-
1404
- def call(tag)
1405
- case tag
1406
- when Tag
1407
- if tag.type.to_s == 'input' && %w'date datetime'.include?((tag.attr[:type] || tag.attr['type']).to_s)
1408
- attr = tag.attr.dup
1409
- attr.delete(:type)
1410
- attr.delete('type')
1411
- attr['type'] = 'text'
1412
- "<#{tag.type}#{attr_html(attr)}/>"
1413
- else
1414
- super
1415
- end
1416
- else
1417
- super
1418
- end
1419
- end
1420
-
1421
- private
1422
-
1423
- # Return a string in American format representing the +Date+ instance.
1424
- def format_date(date)
1425
- date.strftime("%m/%d/%Y")
1426
- end
1427
-
1428
- # Return a string in American format representing the +Time+ or +DateTime+ instance, without the timezone.
1429
- def format_time(time)
1430
- time.strftime("%m/%d/%Y %I:%M:%S%p")
1431
- end
1432
- end
1433
-
1434
- # Serializer class that converts tags to plain text strings.
1435
- #
1436
- # Registered at :text.
1437
- class Serializer::PlainText
1438
- Forme.register_transformer(:serializer, :text, new)
1439
-
1440
- # Serialize the tag to plain text string.
1441
- def call(tag)
1442
- case tag
1443
- when Tag
1444
- case tag.type.to_sym
1445
- when :input
1446
- case tag.attr[:type].to_sym
1447
- when :radio, :checkbox
1448
- tag.attr[:checked] ? '_X_' : '___'
1449
- when :submit, :reset, :hidden
1450
- ''
1451
- when :password
1452
- "********\n"
1453
- else
1454
- "#{tag.attr[:value].to_s}\n"
1455
- end
1456
- when :select
1457
- "\n#{call(tag.children)}"
1458
- when :option
1459
- "#{call([tag.attr[:selected] ? '_X_ ' : '___ ', tag.children])}\n"
1460
- when :textarea, :label
1461
- "#{call(tag.children)}\n"
1462
- when :legend
1463
- v = call(tag.children)
1464
- "#{v}\n#{'-' * v.length}\n"
1465
- else
1466
- call(tag.children)
1467
- end
1468
- when Input
1469
- call(tag.format)
1470
- when Array
1471
- tag.map{|x| call(x)}.join
1472
- else
1473
- tag.to_s
1474
- end
1475
- end
1476
- end
1477
132
  end
133
+
134
+ %w'form input tag raw version'.each{|f| require File.expand_path("../forme/#{f}", __FILE__)}
135
+ %w'error_handler formatter helper inputs_wrapper labeler serializer wrapper'.each{|f| require File.expand_path("../forme/transformers/#{f}", __FILE__)}