forme 1.2.0 → 1.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG +24 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -18
- data/Rakefile +6 -5
- data/lib/forme.rb +6 -1348
- data/lib/forme/form.rb +387 -0
- data/lib/forme/input.rb +48 -0
- data/lib/forme/raw.rb +12 -0
- data/lib/forme/tag.rb +60 -0
- data/lib/forme/transformers/error_handler.rb +18 -0
- data/lib/forme/transformers/formatter.rb +511 -0
- data/lib/forme/transformers/helper.rb +17 -0
- data/lib/forme/transformers/inputs_wrapper.rb +95 -0
- data/lib/forme/transformers/labeler.rb +78 -0
- data/lib/forme/transformers/serializer.rb +171 -0
- data/lib/forme/transformers/wrapper.rb +52 -0
- data/lib/forme/version.rb +1 -1
- data/lib/sequel/plugins/forme.rb +7 -4
- data/spec/forme_spec.rb +65 -5
- data/spec/sequel_plugin_spec.rb +18 -3
- metadata +15 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f907e786f16f2b923a7b06def6a58680fc922d27
|
4
|
+
data.tar.gz: 88e4c51dc087507766783ee18b45fac47cf8007d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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)
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -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>
|
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,
|
593
|
-
|
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
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
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
|
-
:
|
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 +
|
721
|
-
|
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|
|
data/lib/forme.rb
CHANGED
@@ -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
|
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 = {"&" => "&", "<" => "<", ">" => ">", "'" => "'", '"' => """}
|
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__)}
|