forme 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1 -0
- data/MIT-LICENSE +18 -0
- data/README.rdoc +231 -0
- data/Rakefile +97 -0
- data/lib/forme.rb +1178 -0
- data/lib/forme/sinatra.rb +89 -0
- data/lib/forme/version.rb +9 -0
- data/lib/sequel/plugins/forme.rb +435 -0
- data/spec/forme_spec.rb +579 -0
- data/spec/sequel_plugin_spec.rb +405 -0
- data/spec/sinatra_integration_spec.rb +96 -0
- data/spec/spec_helper.rb +2 -0
- metadata +93 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'forme'
|
2
|
+
|
3
|
+
module Forme
|
4
|
+
module Sinatra # :nodoc:
|
5
|
+
# Subclass used when using Forme/Sinatra ERB integration.
|
6
|
+
# Handles integrating into the view template so that
|
7
|
+
# methods with blocks can inject strings into the output.
|
8
|
+
class Form < ::Forme::Form
|
9
|
+
# Template output object, where serialized output gets
|
10
|
+
# injected.
|
11
|
+
attr_reader :output
|
12
|
+
|
13
|
+
# Set the template output object when initializing.
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
@output = @opts[:output]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Serialize the tag and inject it into the output.
|
20
|
+
def emit(tag)
|
21
|
+
output << tag.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
# Always return nil, so that use with <%= doesn't cause
|
25
|
+
# multiple things to be output.
|
26
|
+
def inputs(*a)
|
27
|
+
super
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# Always return nil, so that use with <%= doesn't cause
|
32
|
+
# multiple things to be output.
|
33
|
+
def form(*a, &block)
|
34
|
+
super
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# If a block is provided, inject an opening tag into the
|
39
|
+
# output, inject any given children into the output, yield to the
|
40
|
+
# block, inject a closing tag into the output, and the return nil
|
41
|
+
# so that usage with <%= doesn't cause multiple things to be output.
|
42
|
+
# If a block is not given, just return the tag created.
|
43
|
+
def tag(type, attr={}, children=[])
|
44
|
+
tag = _tag(type, attr, children)
|
45
|
+
if block_given?
|
46
|
+
emit(serializer.serialize_open(tag)) if serializer.respond_to?(:serialize_open)
|
47
|
+
children.each{|c| emit(c)}
|
48
|
+
yield self
|
49
|
+
emit(serializer.serialize_close(tag)) if serializer.respond_to?(:serialize_close)
|
50
|
+
nil
|
51
|
+
else
|
52
|
+
tag
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# This is the module used to add the Forme integration
|
58
|
+
# to Sinatra. It should be enabled in Sinatra with the
|
59
|
+
# following code in your <tt>Sinatra::Base</tt> subclass:
|
60
|
+
#
|
61
|
+
# helpers Forme::Sinatra::ERB
|
62
|
+
module ERB
|
63
|
+
# Create a +Form+ object and yield it to the block,
|
64
|
+
# injecting the opening form tag before yielding and
|
65
|
+
# the closing form tag after yielding.
|
66
|
+
#
|
67
|
+
# Argument Handling:
|
68
|
+
# No args :: Creates a +Form+ object with no options and not associated
|
69
|
+
# to an +obj+, and with no attributes in the opening tag.
|
70
|
+
# 1 hash arg :: Treated as opening form tag attributes, creating a
|
71
|
+
# +Form+ object with no options.
|
72
|
+
# 1 non-hash arg :: Treated as the +Form+'s +obj+, with empty options
|
73
|
+
# and no attributes in the opening tag.
|
74
|
+
# 2 hash args :: First hash is opening attributes, second hash is +Form+
|
75
|
+
# options.
|
76
|
+
# 1 non-hash arg, 1-2 hash args :: First argument is +Form+'s obj, second is
|
77
|
+
# opening attributes, third if provided is
|
78
|
+
# +Form+'s options.
|
79
|
+
def form(obj=nil, attr={}, opts={}, &block)
|
80
|
+
h = {:output=>@_out_buf}
|
81
|
+
(obj.is_a?(Hash) ? attr = attr.merge(h) : opts = opts.merge(h))
|
82
|
+
Form.form(obj, attr, opts, &block)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Alias of <tt>Forme::Sinatra::ERB</tt>
|
87
|
+
Erubis = ERB
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,435 @@
|
|
1
|
+
require 'forme'
|
2
|
+
|
3
|
+
module Sequel # :nodoc:
|
4
|
+
module Plugins # :nodoc:
|
5
|
+
# This Sequel plugin allows easy use of Forme with Sequel.
|
6
|
+
module Forme
|
7
|
+
# Exception class raised by the plugin. It's important to
|
8
|
+
# note this descends from <tt>Forme::Error</tt> and not
|
9
|
+
# <tt>Sequel::Error</tt>, though in practice it's unlikely
|
10
|
+
# you will want to rescue these errors.
|
11
|
+
class Error < ::Forme::Error
|
12
|
+
end
|
13
|
+
|
14
|
+
# This module extends all <tt>Forme::Form</tt> instances
|
15
|
+
# that use a <tt>Sequel::Model</tt> instance as the form's
|
16
|
+
# +obj+.
|
17
|
+
module SequelForm
|
18
|
+
# Stack of objects used by subform. The current +obj+
|
19
|
+
# is added to the top of the stack on a call to +subform+,
|
20
|
+
# the nested associated object is set as the current +obj+ during the
|
21
|
+
# call to +subform+, and when +subform+ returns, the top of the
|
22
|
+
# stack is set as the current +obj+.
|
23
|
+
attr_accessor :nested_associations
|
24
|
+
|
25
|
+
# The namespaces that should be added to the id and name
|
26
|
+
# attributes for the receiver's inputs. Used as a stack
|
27
|
+
# by +subform+.
|
28
|
+
attr_accessor :namespaces
|
29
|
+
|
30
|
+
# Use the post method by default for Sequel forms, unless
|
31
|
+
# overridden with the :method attribute.
|
32
|
+
def form(attr={}, &block)
|
33
|
+
attr = {:method=>:post}.merge(attr)
|
34
|
+
attr[:class] = ::Forme.merge_classes(attr[:class], "forme", obj.model.send(:underscore, obj.model.name))
|
35
|
+
super(attr, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Call humanize on a string version of the argument if
|
39
|
+
# String#humanize exists. Otherwise, do some monkeying
|
40
|
+
# with the string manually.
|
41
|
+
def humanize(s)
|
42
|
+
s = s.to_s
|
43
|
+
s.respond_to?(:humanize) ? s.humanize : s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
44
|
+
end
|
45
|
+
|
46
|
+
# Handle nested association usage. The +association+ should be a name
|
47
|
+
# of the association for the form's +obj+. Inside the block, calls to
|
48
|
+
# the +input+ and +inputs+ methods for the receiver treat the associated
|
49
|
+
# object as the recevier's +obj+, using name and id attributes that work
|
50
|
+
# with the Sequel +nested_attributes+ plugin.
|
51
|
+
#
|
52
|
+
# The following options are currently supported:
|
53
|
+
# :inputs :: Automatically call +inputs+ with the given values. Using
|
54
|
+
# this, it is not required to pass a block to the method,
|
55
|
+
# though it will still work if you do.
|
56
|
+
# :legend :: If :inputs is also used, this is passed to it to override
|
57
|
+
# the default :legend used. You can also use a proc as the value,
|
58
|
+
# which will called with each associated object (and the position
|
59
|
+
# in the associated object already for *_to_many associations),
|
60
|
+
# and should return the legend string to use for that object.
|
61
|
+
def subform(association, opts={}, &block)
|
62
|
+
nested_obj = opts.has_key?(:obj) ? opts[:obj] : obj.send(association)
|
63
|
+
ref = obj.class.association_reflection(association)
|
64
|
+
multiple = ref.returns_array?
|
65
|
+
i = -1
|
66
|
+
ins = opts[:inputs]
|
67
|
+
Array(nested_obj).each do |no|
|
68
|
+
begin
|
69
|
+
nested_associations << obj
|
70
|
+
namespaces << "#{association}_attributes"
|
71
|
+
namespaces << (i+=1) if multiple
|
72
|
+
@obj = no
|
73
|
+
emit(input(ref.associated_class.primary_key, :type=>:hidden, :label=>nil)) unless no.new?
|
74
|
+
if ins
|
75
|
+
options = opts.dup
|
76
|
+
if options.has_key?(:legend)
|
77
|
+
if options[:legend].respond_to?(:call)
|
78
|
+
options[:legend] = multiple ? options[:legend].call(no, i) : options[:legend].call(no)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
if multiple
|
82
|
+
options[:legend] = humanize("#{obj.model.send(:singularize, association)} ##{i+1}")
|
83
|
+
else
|
84
|
+
options[:legend] = humanize(association)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
inputs(ins, options, &block)
|
88
|
+
else
|
89
|
+
yield
|
90
|
+
end
|
91
|
+
ensure
|
92
|
+
@obj = nested_associations.pop
|
93
|
+
namespaces.pop if multiple
|
94
|
+
namespaces.pop
|
95
|
+
end
|
96
|
+
end
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
# Return a unique id attribute for the +field+, handling
|
101
|
+
# nested attributes use.
|
102
|
+
def namespaced_id(field)
|
103
|
+
"#{namespaces.join('_')}_#{field}"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return a unique name attribute for the +field+, handling nested
|
107
|
+
# attribute use. If +multiple+ is true, end the name
|
108
|
+
# with [] so that param parsing will treat the name as part of an array.
|
109
|
+
def namespaced_name(field, multiple=false)
|
110
|
+
root, *nsps = namespaces
|
111
|
+
"#{root}#{nsps.map{|n| "[#{n}]"}.join}[#{field}]#{'[]' if multiple}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Helper class for dealing with Forme/Sequel integration.
|
116
|
+
# One instance is created for each call to <tt>Forme::Form#input</tt>
|
117
|
+
# for forms associated with <tt>Sequel::Model</tt> objects.
|
118
|
+
class SequelInput
|
119
|
+
include ::Forme
|
120
|
+
|
121
|
+
# The name methods that will be tried, in order, to get the
|
122
|
+
# text to use for the options in the select input created
|
123
|
+
# for associations.
|
124
|
+
FORME_NAME_METHODS = [:forme_name, :name, :title, :number]
|
125
|
+
|
126
|
+
# The <tt>Sequel::Model</tt> object related to the receiver.
|
127
|
+
attr_reader :obj
|
128
|
+
|
129
|
+
# The form related to the receiver.
|
130
|
+
attr_reader :form
|
131
|
+
|
132
|
+
# The field/column name related to the receiver. The type of
|
133
|
+
# input created usually depends upon this field.
|
134
|
+
attr_reader :field
|
135
|
+
|
136
|
+
# The options hash related to the receiver.
|
137
|
+
attr_reader :opts
|
138
|
+
|
139
|
+
# Set the +obj+, +form+, +field+, and +opts+ attributes.
|
140
|
+
def initialize(obj, form, field, opts)
|
141
|
+
@obj, @form, @field, @opts = obj, form, field, opts
|
142
|
+
end
|
143
|
+
|
144
|
+
# Determine which type of input to used based on the +field+.
|
145
|
+
# If the field is a column, use the column's type to determine
|
146
|
+
# an appropriate field type. If the field is an association,
|
147
|
+
# use either a regular or multiple select input (or multiple radios or
|
148
|
+
# checkboxes if the related :type option is used). If it's not a
|
149
|
+
# column or association, but the object responds to +field+,
|
150
|
+
# create a text input. Otherwise, raise an +Error+.
|
151
|
+
def input
|
152
|
+
opts[:attr] = opts[:attr] ? opts[:attr].dup : {}
|
153
|
+
opts[:wrapper_attr] = opts[:wrapper_attr] ? opts[:wrapper_attr].dup : {}
|
154
|
+
|
155
|
+
if sch = obj.db_schema[field]
|
156
|
+
handle_errors(field)
|
157
|
+
handle_validations(field)
|
158
|
+
meth = :"input_#{sch[:type]}"
|
159
|
+
opts[:id] = form.namespaced_id(field) unless opts.has_key?(:id)
|
160
|
+
opts[:name] = form.namespaced_name(field) unless opts.has_key?(:name)
|
161
|
+
opts[:required] = true if !opts.has_key?(:required) && sch[:allow_null] == false && sch[:type] != :boolean
|
162
|
+
handle_label(field)
|
163
|
+
|
164
|
+
::Forme.attr_classes(opts[:wrapper_attr], sch[:type])
|
165
|
+
::Forme.attr_classes(opts[:wrapper_attr], "required") if opts[:required]
|
166
|
+
|
167
|
+
if respond_to?(meth, true)
|
168
|
+
send(meth, sch)
|
169
|
+
else
|
170
|
+
input_other(sch)
|
171
|
+
end
|
172
|
+
elsif ref = obj.model.association_reflection(field)
|
173
|
+
::Forme.attr_classes(opts[:wrapper_attr], ref[:type])
|
174
|
+
meth = :"association_#{ref[:type]}"
|
175
|
+
if respond_to?(meth, true)
|
176
|
+
send(meth, ref)
|
177
|
+
else
|
178
|
+
raise Error, "Association type #{ref[:type]} not currently handled for association #{ref[:name]}"
|
179
|
+
end
|
180
|
+
elsif obj.respond_to?(field)
|
181
|
+
opts[:id] = form.namespaced_id(field) unless opts.has_key?(:id)
|
182
|
+
opts[:name] = form.namespaced_name(field) unless opts.has_key?(:name)
|
183
|
+
handle_label(field)
|
184
|
+
input_other({})
|
185
|
+
else
|
186
|
+
raise Error, "Unrecognized field used: #{field}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
# Create an +Input+ instance associated to the receiver's +form+
|
193
|
+
# with the given arguments.
|
194
|
+
def _input(*a)
|
195
|
+
form._input(*a)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Set the error option correctly if the field contains errors
|
199
|
+
def handle_errors(f)
|
200
|
+
if e = obj.errors.on(f)
|
201
|
+
opts[:error] = e.join(', ')
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Set the label option appropriately, adding a * if the field
|
206
|
+
# is required.
|
207
|
+
def handle_label(f)
|
208
|
+
unless opts.has_key?(:label)
|
209
|
+
opts[:label] = if opts[:required]
|
210
|
+
[humanize(field), form._tag(:abbr, {:title=>'required'}, '*')]
|
211
|
+
else
|
212
|
+
humanize(field)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Update the attributes and options for any recognized validations
|
218
|
+
def handle_validations(f)
|
219
|
+
m = obj.model
|
220
|
+
if m.respond_to?(:validation_reflections) and (vs = m.validation_reflections[f])
|
221
|
+
attr = opts[:attr]
|
222
|
+
vs.each do |type, options|
|
223
|
+
attr[:placeholder] = options[:placeholder] if options[:placeholder] && !attr.has_key?(:placeholder)
|
224
|
+
|
225
|
+
case type
|
226
|
+
when :format
|
227
|
+
attr[:pattern] = options[:with].source unless attr.has_key?(:pattern)
|
228
|
+
attr[:title] = options[:title] unless attr.has_key?(:title)
|
229
|
+
when :length
|
230
|
+
unless attr.has_key?(:maxlength)
|
231
|
+
if max =(options[:maximum] || options[:is])
|
232
|
+
attr[:maxlength] = max
|
233
|
+
elsif (w = options[:within]) && w.is_a?(Range)
|
234
|
+
attr[:maxlength] = if w.exclude_end? && w.end.is_a?(Integer)
|
235
|
+
w.end - 1
|
236
|
+
else
|
237
|
+
w.end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
when :numericality
|
242
|
+
unless attr.has_key?(:pattern)
|
243
|
+
attr[:pattern] = if options[:only_integer]
|
244
|
+
"^[+\\-]?\\d+$"
|
245
|
+
else
|
246
|
+
"^[+\\-]?\\d+(\\.\\d+)?$"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
attr[:title] = options[:title] || "must be a number" unless attr.has_key?(:title)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# If the :name_method option is provided, use that as the method.
|
256
|
+
# Otherwise, pick the first method in +FORME_NAME_METHODS+ that
|
257
|
+
# the associated class implements and use it. If none of the
|
258
|
+
# methods are implemented by the associated class, raise an +Error+.
|
259
|
+
def forme_name_method(ref)
|
260
|
+
if meth = opts.delete(:name_method)
|
261
|
+
meth
|
262
|
+
else
|
263
|
+
meths = FORME_NAME_METHODS & ref.associated_class.instance_methods.map{|s| s.to_sym}
|
264
|
+
if meths.empty?
|
265
|
+
raise Error, "No suitable name method found for association #{ref[:name]}"
|
266
|
+
else
|
267
|
+
meths.first
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Create a select input made up of options for all entries the object
|
273
|
+
# could be associated to, with the one currently associated to being selected.
|
274
|
+
# If the :type=>:radio option is used, use multiple radio buttons instead of
|
275
|
+
# a select box. For :type=>:radio, you can also provide a :tag_wrapper option
|
276
|
+
# used to wrap the individual radio buttons.
|
277
|
+
def association_many_to_one(ref)
|
278
|
+
key = ref[:key]
|
279
|
+
handle_errors(key)
|
280
|
+
opts[:name] = form.namespaced_name(key) unless opts.has_key?(:name)
|
281
|
+
opts[:value] = obj.send(key) unless opts.has_key?(:value)
|
282
|
+
opts[:options] = association_select_options(ref) unless opts.has_key?(:options)
|
283
|
+
if opts.delete(:as) == :radio
|
284
|
+
handle_label(field)
|
285
|
+
label = opts.delete(:label)
|
286
|
+
val = opts.delete(:value)
|
287
|
+
tag_wrapper = opts.delete(:tag_wrapper) || :default
|
288
|
+
wrapper = form.transformer(:wrapper, opts)
|
289
|
+
opts.delete(:wrapper)
|
290
|
+
radios = opts.delete(:options).map{|l, pk| _input(:radio, opts.merge(:value=>pk, :wrapper=>tag_wrapper, :label=>l, :checked=>(pk == val)))}
|
291
|
+
radios.unshift("#{label}: ")
|
292
|
+
wrapper ? wrapper.call(radios, _input(:radio, opts)) : radios
|
293
|
+
else
|
294
|
+
opts[:id] = form.namespaced_id(key) unless opts.has_key?(:id)
|
295
|
+
opts[:add_blank] = true if !opts.has_key?(:add_blank)
|
296
|
+
opts[:required] = true if !opts.has_key?(:required) && (sch = obj.model.db_schema[key]) && !sch[:allow_null]
|
297
|
+
handle_label(field)
|
298
|
+
::Forme.attr_classes(opts[:wrapper_attr], "required") if opts[:required]
|
299
|
+
_input(:select, opts)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Create a multiple select input made up of options for all entries the object
|
304
|
+
# could be associated to, with all of the ones currently associated to being selected.
|
305
|
+
# If the :type=>:checkbox option is used, use multiple checkboxes instead of
|
306
|
+
# a multiple select box. For :type=>:checkbox, you can also provide a :tag_wrapper option
|
307
|
+
# used to wrap the individual checkboxes.
|
308
|
+
def association_one_to_many(ref)
|
309
|
+
key = ref[:key]
|
310
|
+
klass = ref.associated_class
|
311
|
+
pk = klass.primary_key
|
312
|
+
field = "#{klass.send(:singularize, ref[:name])}_pks"
|
313
|
+
opts[:name] = form.namespaced_name(field, :multiple) unless opts.has_key?(:name)
|
314
|
+
opts[:value] = obj.send(ref[:name]).map{|x| x.send(pk)} unless opts.has_key?(:value)
|
315
|
+
opts[:options] = association_select_options(ref) unless opts.has_key?(:options)
|
316
|
+
handle_label(field)
|
317
|
+
if opts.delete(:as) == :checkbox
|
318
|
+
label = opts.delete(:label)
|
319
|
+
val = opts.delete(:value)
|
320
|
+
tag_wrapper = opts.delete(:tag_wrapper) || :default
|
321
|
+
wrapper = form.transformer(:wrapper, opts)
|
322
|
+
opts.delete(:wrapper)
|
323
|
+
cbs = opts.delete(:options).map{|l, pk| _input(:checkbox, opts.merge(:value=>pk, :wrapper=>tag_wrapper, :label=>l, :checked=>val.include?(pk), :no_hidden=>true))}
|
324
|
+
cbs.unshift("#{label}: ")
|
325
|
+
wrapper ? wrapper.call(cbs, _input(:checkbox, opts)) : cbs
|
326
|
+
else
|
327
|
+
opts[:id] = form.namespaced_id(field) unless opts.has_key?(:id)
|
328
|
+
opts[:multiple] = true
|
329
|
+
_input(:select, opts)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
alias association_many_to_many association_one_to_many
|
333
|
+
|
334
|
+
# Return an array of two element arrays representing the
|
335
|
+
# select options that should be created.
|
336
|
+
def association_select_options(ref)
|
337
|
+
name_method = forme_name_method(ref)
|
338
|
+
obj.send(:_apply_association_options, ref, ref.associated_class.dataset).unlimited.all.map{|a| [a.send(name_method), a.pk]}
|
339
|
+
end
|
340
|
+
|
341
|
+
# Delegate to the +form+.
|
342
|
+
def humanize(s)
|
343
|
+
form.humanize(s)
|
344
|
+
end
|
345
|
+
|
346
|
+
# If the column allows +NULL+ values, use a three-valued select
|
347
|
+
# input. If not, use a simple checkbox.
|
348
|
+
def input_boolean(sch)
|
349
|
+
if sch[:allow_null]
|
350
|
+
v = opts[:value] || obj.send(field)
|
351
|
+
opts[:value] = (v ? 't' : 'f') unless v.nil?
|
352
|
+
opts[:add_blank] = true
|
353
|
+
opts[:options] = [['True', 't'], ['False', 'f']]
|
354
|
+
_input(:select, opts)
|
355
|
+
else
|
356
|
+
opts[:checked] = obj.send(field)
|
357
|
+
opts[:value] = 't'
|
358
|
+
_input(:checkbox, opts)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# Use a file type for blobs.
|
363
|
+
def input_blob(sch)
|
364
|
+
opts[:value] = nil
|
365
|
+
standard_input(:file)
|
366
|
+
end
|
367
|
+
|
368
|
+
# Use the text type by default for other cases not handled.
|
369
|
+
def input_string(sch)
|
370
|
+
if opts[:as] == :textarea
|
371
|
+
standard_input(:textarea)
|
372
|
+
else
|
373
|
+
case field.to_s
|
374
|
+
when "password"
|
375
|
+
opts[:value] = nil
|
376
|
+
standard_input(:password)
|
377
|
+
when "email"
|
378
|
+
standard_input(:email)
|
379
|
+
when "phone", "fax"
|
380
|
+
standard_input(:tel)
|
381
|
+
when "url", "uri", "website"
|
382
|
+
standard_input(:url)
|
383
|
+
else
|
384
|
+
standard_input(:text)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
# Use number inputs for integers.
|
390
|
+
def input_integer(sch)
|
391
|
+
standard_input(:number)
|
392
|
+
end
|
393
|
+
|
394
|
+
# Use date inputs for dates.
|
395
|
+
def input_date(sch)
|
396
|
+
standard_input(:date)
|
397
|
+
end
|
398
|
+
|
399
|
+
# Use datetime inputs for datetimes.
|
400
|
+
def input_datetime(sch)
|
401
|
+
standard_input(:datetime)
|
402
|
+
end
|
403
|
+
|
404
|
+
# Use a text input for all other types.
|
405
|
+
def input_other(sch)
|
406
|
+
standard_input(:text)
|
407
|
+
end
|
408
|
+
|
409
|
+
# Allow overriding the given type using the :type option,
|
410
|
+
# and set the :value option to the field value unless it
|
411
|
+
# is overridden.
|
412
|
+
def standard_input(type)
|
413
|
+
type = opts.delete(:type) || type
|
414
|
+
opts[:value] = obj.send(field) unless opts.has_key?(:value)
|
415
|
+
_input(type, opts)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
module InstanceMethods
|
420
|
+
# Configure the +form+ with support for <tt>Sequel::Model</tt>
|
421
|
+
# specific code, such as support for nested attributes.
|
422
|
+
def forme_config(form)
|
423
|
+
form.extend(SequelForm)
|
424
|
+
form.nested_associations = []
|
425
|
+
form.namespaces = [model.send(:underscore, model.name)]
|
426
|
+
end
|
427
|
+
|
428
|
+
# Return <tt>Forme::Input</tt> instance based on the given arguments.
|
429
|
+
def forme_input(form, field, opts)
|
430
|
+
SequelInput.new(self, form, field, opts).input
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|