forme 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,387 @@
1
+ module Forme
2
+ # The +Form+ class is the main entry point to the library.
3
+ # Using the +form+, +input+, +tag+, and +inputs+ methods, one can easily build
4
+ # an abstract syntax tree of +Tag+ and +Input+ instances, which can be serialized
5
+ # to a string using +to_s+.
6
+ class Form
7
+ # A hash of options for the form.
8
+ attr_reader :opts
9
+
10
+ # Set the default options for inputs by type. This should be a hash with
11
+ # input type keys and values that are hashes of input options.
12
+ attr_reader :input_defaults
13
+
14
+ # The hidden tags to automatically add to the form.
15
+ attr_reader :hidden_tags
16
+
17
+ # The namespaces if any for the receiver's inputs. This can be used to
18
+ # automatically setup namespaced class and id attributes.
19
+ attr_accessor :namespaces
20
+
21
+ # The +serializer+ determines how +Tag+ objects are transformed into strings.
22
+ # Must respond to +call+ or be a registered symbol.
23
+ attr_reader :serializer
24
+
25
+ # Use appropriate Form subclass for object based on the current class, if the
26
+ # object responds to +forme_form_class+.
27
+ def self.new(obj=nil, opts={})
28
+ if obj && obj.respond_to?(:forme_form_class) && !opts[:_forme_form_class_set]
29
+ obj.forme_form_class(self).new(obj, opts.merge(:_forme_form_class_set=>true))
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ # Create a +Form+ instance and yield it to the block,
36
+ # injecting the opening form tag before yielding and
37
+ # the closing form tag after yielding.
38
+ #
39
+ # Argument Handling:
40
+ # No args :: Creates a +Form+ object with no options and not associated
41
+ # to an +obj+, and with no attributes in the opening tag.
42
+ # 1 hash arg :: Treated as opening form tag attributes, creating a
43
+ # +Form+ object with no options.
44
+ # 1 non-hash arg :: Treated as the +Form+'s +obj+, with empty options
45
+ # and no attributes in the opening tag.
46
+ # 2 hash args :: First hash is opening attributes, second hash is +Form+
47
+ # options.
48
+ # 1 non-hash arg, 1-2 hash args :: First argument is +Form+'s obj, second is
49
+ # opening attributes, third if provided is
50
+ # +Form+'s options.
51
+ def self.form(obj=nil, attr={}, opts={}, &block)
52
+ f = if obj.is_a?(Hash)
53
+ raise Error, "Can't provide 3 hash arguments to form" unless opts.empty?
54
+ opts = attr
55
+ attr = obj
56
+ new(opts)
57
+ else
58
+ new(obj, opts)
59
+ end
60
+
61
+ ins = opts[:inputs]
62
+ button = opts[:button]
63
+ if ins || button
64
+ block = Proc.new do |form|
65
+ form._inputs(ins, opts) if ins
66
+ yield form if block_given?
67
+ form.emit(form.button(button)) if button
68
+ end
69
+ end
70
+
71
+ f.form(attr, &block)
72
+ end
73
+
74
+ # Creates a +Form+ object. Arguments:
75
+ # obj :: Sets the obj for the form. If a hash, is merged with the +opts+ argument
76
+ # to set the opts.
77
+ # opts :: A hash of options for the form
78
+ def initialize(obj=nil, opts={})
79
+ @opts = opts.merge(obj.is_a?(Hash) ? obj : {:obj=>obj})
80
+ @opts[:namespace] = Array(@opts[:namespace])
81
+
82
+ if obj && obj.respond_to?(:forme_config)
83
+ obj.forme_config(self)
84
+ end
85
+
86
+ config = CONFIGURATIONS[@opts[:config]||Forme.default_config]
87
+ copy_inputs_wrapper_from_wrapper(@opts)
88
+
89
+ TRANSFORMER_TYPES.each do |t|
90
+ case @opts[t]
91
+ when Symbol
92
+ @opts[t] = Forme.transformer(t, @opts[t], @opts)
93
+ when nil
94
+ unless @opts.has_key?(t)
95
+ @opts[t] = Forme.transformer(t, config, @opts)
96
+ end
97
+ end
98
+ end
99
+
100
+ @serializer = @opts[:serializer]
101
+ @input_defaults = @opts[:input_defaults] || {}
102
+ @hidden_tags = @opts[:hidden_tags]
103
+ @nesting = []
104
+ end
105
+
106
+ # Create a form tag with the given attributes.
107
+ def form(attr={}, &block)
108
+ tag(:form, attr, method(:hidden_form_tags), &block)
109
+ end
110
+
111
+ # Empty method designed to ease integration with other libraries where
112
+ # Forme is used in template code and some output implicitly
113
+ # created by Forme needs to be injected into the template output.
114
+ def emit(tag)
115
+ end
116
+
117
+ # Creates an +Input+ with the given +field+ and +opts+ associated with
118
+ # the receiver, and add it to the list of children to the currently
119
+ # open tag.
120
+ #
121
+ # If the form is associated with an +obj+, or the :obj key exists in
122
+ # the +opts+ argument, treats the +field+ as a call to the +obj+. If
123
+ # +obj+ responds to +forme_input+, that method is called with the +field+
124
+ # and a copy of +opts+. Otherwise, the field is used as a method call
125
+ # on the +obj+ and a text input is created with the result.
126
+ #
127
+ # If no +obj+ is associated with the receiver, +field+ represents an input
128
+ # type (e.g. <tt>:text</tt>, <tt>:textarea</tt>, <tt>:select</tt>), and
129
+ # an input is created directly with the +field+ and +opts+.
130
+ def input(field, opts={})
131
+ if opts.has_key?(:obj)
132
+ opts = opts.dup
133
+ obj = opts.delete(:obj)
134
+ else
135
+ obj = self.obj
136
+ end
137
+ input = if obj
138
+ if obj.respond_to?(:forme_input)
139
+ obj.forme_input(self, field, opts.dup)
140
+ else
141
+ opts = opts.dup
142
+ opts[:key] = field unless opts.has_key?(:key)
143
+ unless opts.has_key?(:value)
144
+ opts[:value] = if obj.is_a?(Hash)
145
+ obj[field]
146
+ else
147
+ obj.send(field)
148
+ end
149
+ end
150
+ _input(:text, opts)
151
+ end
152
+ else
153
+ _input(field, opts)
154
+ end
155
+ self << input
156
+ input
157
+ end
158
+
159
+ # Create a new +Input+ associated with the receiver with the given
160
+ # arguments, doing no other processing.
161
+ def _input(*a)
162
+ Input.new(self, *a)
163
+ end
164
+
165
+ # Creates a tag using the +inputs_wrapper+ (a fieldset by default), calls
166
+ # input on each element of +inputs+, and yields if given a block.
167
+ # You can use array arguments if you want inputs to be created with specific
168
+ # options:
169
+ #
170
+ # f.inputs([:field1, :field2])
171
+ # f.inputs([[:field1, {:name=>'foo'}], :field2])
172
+ #
173
+ # The given +opts+ are passed to the +inputs_wrapper+, and the default
174
+ # +inputs_wrapper+ supports a <tt>:legend</tt> option that is used to
175
+ # set the legend for the fieldset.
176
+ #
177
+ # +opts+ can also include transformer options itself (e.g. :wrapper), which
178
+ # override the form's current transformer options for the duration of the block.
179
+ # The exception is the :inputs_wrapper transformer option, which affects the
180
+ # wrapper to use for this inputs call. You can use the :nested_inputs_wrapper
181
+ # option to set the default :inputs_wrapper option for the duration of the block.
182
+ #
183
+ # This can also be called with a single hash argument to just use an options hash:
184
+ #
185
+ # f.inputs(:legend=>'Foo') do
186
+ # # ...
187
+ # end
188
+ #
189
+ # or even without any arguments:
190
+ #
191
+ # f.inputs do
192
+ # # ...
193
+ # end
194
+ def inputs(inputs=[], opts={}, &block)
195
+ _inputs(inputs, opts, &block)
196
+ end
197
+
198
+ # Internals of #inputs, should be used internally by the library, where #inputs
199
+ # is designed for external use.
200
+ def _inputs(inputs=[], opts={}) # :nodoc:
201
+ if inputs.is_a?(Hash)
202
+ opts = inputs.merge(opts)
203
+ inputs = []
204
+ end
205
+
206
+ form_opts = {}
207
+ form_opts[:inputs_wrapper] = opts[:nested_inputs_wrapper] if opts[:nested_inputs_wrapper]
208
+ TRANSFORMER_TYPES.each do |t|
209
+ if opts.has_key?(t) && t != :inputs_wrapper
210
+ form_opts[t] = opts[t]
211
+ end
212
+ end
213
+
214
+ Forme.transform(:inputs_wrapper, opts, @opts, self, opts) do
215
+ with_opts(form_opts) do
216
+ inputs.each do |i|
217
+ emit(input(*i))
218
+ end
219
+ yield if block_given?
220
+ end
221
+ end
222
+ end
223
+
224
+ # Returns a string representing the opening of the form tag for serializers
225
+ # that support opening tags.
226
+ def open(attr)
227
+ serializer.serialize_open(_tag(:form, attr)) if serializer.respond_to?(:serialize_open)
228
+ end
229
+
230
+ # Returns a string representing the closing of the form tag, for serializers
231
+ # that support closing tags.
232
+ def close
233
+ serializer.serialize_close(_tag(:form)) if serializer.respond_to?(:serialize_close)
234
+ end
235
+
236
+ # Create a +Tag+ associated to the receiver with the given arguments and block,
237
+ # doing no other processing.
238
+ def _tag(*a, &block)
239
+ tag = Tag.new(self, *a, &block)
240
+ end
241
+
242
+ # The object associated with this form, if any. If the +Form+ has an associated
243
+ # obj, then calls to +input+ are assumed to be accessing fields of the object
244
+ # instead to directly representing input types.
245
+ def obj
246
+ @opts[:obj]
247
+ end
248
+
249
+ # The current namespaces for the form, if any.
250
+ def namespaces
251
+ @opts[:namespace]
252
+ end
253
+
254
+ # Creates a +Tag+ associated to the receiver with the given arguments.
255
+ # Add the tag to the the list of children for the currently open tag.
256
+ # If a block is given, make this tag the currently open tag while inside
257
+ # the block.
258
+ def tag(*a, &block)
259
+ tag = _tag(*a)
260
+ self << tag
261
+ nest(tag, &block) if block
262
+ tag
263
+ end
264
+
265
+ # Aliased for tag. Workaround for issue with rails plugin.
266
+ def tag_(*a, &block) # :nodoc:
267
+ tag(*a, &block)
268
+ end
269
+
270
+ # Creates a :submit +Input+ with the given opts, adding it to the list
271
+ # of children for the currently open tag.
272
+ def button(opts={})
273
+ opts = {:value=>opts} if opts.is_a?(String)
274
+ input = _input(:submit, opts)
275
+ self << input
276
+ input
277
+ end
278
+
279
+ # Add the +Input+/+Tag+ instance given to the currently open tag.
280
+ def <<(tag)
281
+ if n = @nesting.last
282
+ n << tag
283
+ end
284
+ end
285
+
286
+ # Calls the block for each object in objs, using with_obj with the given namespace
287
+ # and an index namespace (starting at 0).
288
+ def each_obj(objs, namespace=nil)
289
+ objs.each_with_index do |obj, i|
290
+ with_obj(obj, Array(namespace) + [i]) do
291
+ yield obj, i
292
+ end
293
+ end
294
+ end
295
+
296
+ # Return a new string that will not be html escaped by the default serializer.
297
+ def raw(s)
298
+ Forme.raw(s)
299
+ end
300
+
301
+ # Marks the string as containing already escaped output. Returns string given
302
+ # by default, but subclasses for specific web frameworks can handle automatic
303
+ # html escaping by overriding this.
304
+ def raw_output(s)
305
+ s
306
+ end
307
+
308
+ # Temporarily override the given object and namespace for the form. Any given
309
+ # namespaces are appended to the form's current namespace.
310
+ def with_obj(obj, namespace=nil)
311
+ with_opts(:obj=>obj, :namespace=>@opts[:namespace]+Array(namespace)) do
312
+ yield obj
313
+ end
314
+ end
315
+
316
+ # Temporarily override the opts for the form for the duration of the block.
317
+ # This merges the given opts with the form's current opts, restoring
318
+ # the previous opts before returning.
319
+ def with_opts(opts)
320
+ orig_opts = @opts
321
+ @opts = orig_opts.merge(opts)
322
+ copy_inputs_wrapper_from_wrapper(opts, @opts)
323
+ yield
324
+ ensure
325
+ @opts = orig_opts if orig_opts
326
+ end
327
+
328
+ private
329
+
330
+ # Copy the :wrapper option to :inputs_wrapper in output_opts if only :wrapper
331
+ # is present in input_opts and the :wrapper option value is a shared wrapper.
332
+ def copy_inputs_wrapper_from_wrapper(input_opts, output_opts=input_opts)
333
+ if input_opts[:wrapper] && !input_opts[:inputs_wrapper] && SHARED_WRAPPERS.include?(input_opts[:wrapper])
334
+ output_opts[:inputs_wrapper] = output_opts[:wrapper]
335
+ end
336
+ end
337
+
338
+ # Return array of hidden tags to use for this form,
339
+ # or nil if the form does not have hidden tags added automatically.
340
+ def hidden_form_tags(form_tag)
341
+ if hidden_tags
342
+ tags = []
343
+ hidden_tags.each do |hidden_tag|
344
+ hidden_tag = hidden_tag.call(form_tag) if hidden_tag.respond_to?(:call)
345
+ tags.concat(parse_hidden_tags(hidden_tag))
346
+ end
347
+ tags
348
+ end
349
+ end
350
+
351
+ # Handle various types of hidden tags for the form.
352
+ def parse_hidden_tags(hidden_tag)
353
+ case hidden_tag
354
+ when Array
355
+ hidden_tag
356
+ when Tag, String
357
+ [hidden_tag]
358
+ when Hash
359
+ hidden_tag.map{|k,v| _tag(:input, :type=>:hidden, :name=>k, :value=>v)}
360
+ when nil
361
+ []
362
+ else
363
+ raise Error, "unhandled hidden_tag response: #{hidden_tag.inspect}"
364
+ end
365
+ end
366
+
367
+ # Make the given tag the currently open tag, and yield. After the
368
+ # block returns, make the previously open tag the currently open
369
+ # tag.
370
+ def nest(tag)
371
+ @nesting << tag
372
+ yield self
373
+ ensure
374
+ @nesting.pop
375
+ end
376
+
377
+ # Return a serialized opening tag for the given tag.
378
+ def serialize_open(tag)
379
+ raw_output(serializer.serialize_open(tag)) if serializer.respond_to?(:serialize_open)
380
+ end
381
+
382
+ # Return a serialized closing tag for the given tag.
383
+ def serialize_close(tag)
384
+ raw_output(serializer.serialize_close(tag)) if serializer.respond_to?(:serialize_close)
385
+ end
386
+ end
387
+ end
@@ -0,0 +1,48 @@
1
+ module Forme
2
+ # High level abstract tag form, transformed by formatters into the lower
3
+ # level +Tag+ form (or an array of them).
4
+ class Input
5
+ # The +Form+ object related to the receiver.
6
+ attr_reader :form
7
+
8
+ # The type of input, should be a symbol (e.g. :submit, :text, :select).
9
+ attr_reader :type
10
+
11
+ # The options hash for the Input.
12
+ attr_reader :opts
13
+
14
+ # The options hash in use by the form at the time of the Input's instantiation.
15
+ attr_reader :form_opts
16
+
17
+ # Set the +form+, +type+, and +opts+.
18
+ def initialize(form, type, opts={})
19
+ @form, @type = form, type
20
+ defaults = form.input_defaults
21
+ @opts = (defaults.fetch(type){defaults[type.to_s]} || {}).merge(opts)
22
+ @form_opts = form.opts
23
+ end
24
+
25
+ # Replace the +opts+ by merging the given +hash+ into +opts+,
26
+ # without modifying +opts+.
27
+ def merge_opts(hash)
28
+ @opts = @opts.merge(hash)
29
+ end
30
+
31
+ # Create a new +Tag+ instance with the given arguments and block
32
+ # related to the receiver's +form+.
33
+ def tag(*a, &block)
34
+ form._tag(*a, &block)
35
+ end
36
+
37
+ # Return a string containing the serialized content of the receiver.
38
+ def to_s
39
+ form.raw_output(Forme.transform(:serializer, @opts, @form_opts, self))
40
+ end
41
+
42
+ # Transform the receiver into a lower level +Tag+ form (or an array
43
+ # of them).
44
+ def format
45
+ Forme.transform(:formatter, @opts, @form_opts, self)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,12 @@
1
+ module Forme
2
+ # Empty module for marking objects as "raw", where they will no longer
3
+ # html escaped by the default serializer.
4
+ module Raw
5
+ end
6
+
7
+ # A String subclass that includes Raw, which will cause the default
8
+ # serializer to no longer html escape the string.
9
+ class RawString < ::String
10
+ include Raw
11
+ end
12
+ end
@@ -0,0 +1,60 @@
1
+ module Forme
2
+ # Low level abstract tag form, where each instance represents a
3
+ # html tag with attributes and children.
4
+ class Tag
5
+ # The +Form+ object related to the receiver.
6
+ attr_reader :form
7
+
8
+ # The type of tag, should be a symbol (e.g. :input, :select).
9
+ attr_reader :type
10
+
11
+ # The attributes hash of this receiver.
12
+ attr_reader :attr
13
+
14
+ # An array instance representing the children of the receiver,
15
+ # or possibly +nil+ if the receiver has no children.
16
+ attr_reader :children
17
+
18
+ # Set the +form+, +type+, +attr+, and +children+.
19
+ def initialize(form, type, attr={}, children=nil)
20
+ @form, @type, @attr = form, type, (attr||{})
21
+ @children = parse_children(children)
22
+ end
23
+
24
+ # Adds a child to the array of receiver's children.
25
+ def <<(child)
26
+ if children
27
+ children << child
28
+ else
29
+ @children = [child]
30
+ end
31
+ end
32
+
33
+ # Create a new +Tag+ instance with the given arguments and block
34
+ # related to the receiver's +form+.
35
+ def tag(*a, &block)
36
+ form._tag(*a, &block)
37
+ end
38
+
39
+ # Return a string containing the serialized content of the receiver.
40
+ def to_s
41
+ form.raw_output(Forme.transform(:serializer, @opts, @form.opts, self))
42
+ end
43
+
44
+ private
45
+
46
+ # Convert children constructor argument into the children to use for the tag.
47
+ def parse_children(children)
48
+ case children
49
+ when Array
50
+ children
51
+ when Proc, Method
52
+ parse_children(children.call(self))
53
+ when nil
54
+ nil
55
+ else
56
+ [children]
57
+ end
58
+ end
59
+ end
60
+ end