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,17 @@
1
+ module Forme
2
+ # Default helper used by the library, using a spam with "helper" class
3
+ #
4
+ # Registered as :default.
5
+ class Helper
6
+ Forme.register_transformer(:helper, :default, new)
7
+
8
+ # Return tag with error message span tag after it.
9
+ def call(tag, input)
10
+ attr = input.opts[:helper_attr]
11
+ attr = attr ? attr.dup : {}
12
+ Forme.attr_classes(attr, 'helper')
13
+ [tag, input.tag(:span, attr, input.opts[:help])]
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,95 @@
1
+ module Forme
2
+ # Default inputs_wrapper used by the library, uses a <fieldset>.
3
+ #
4
+ # Registered as :default.
5
+ class InputsWrapper
6
+ Forme.register_transformer(:inputs_wrapper, :default, new)
7
+
8
+ # Wrap the inputs in a <fieldset>. If the :legend
9
+ # option is given, add a <legend> tag as the first
10
+ # child of the fieldset.
11
+ def call(form, opts)
12
+ attr = opts[:attr] ? opts[:attr].dup : {}
13
+ Forme.attr_classes(attr, 'inputs')
14
+ if legend = opts[:legend]
15
+ form.tag(:fieldset, attr) do
16
+ form.emit(form.tag(:legend, opts[:legend_attr], legend))
17
+ yield
18
+ end
19
+ else
20
+ form.tag(:fieldset, attr, &Proc.new)
21
+ end
22
+ end
23
+ end
24
+
25
+ # Use a <fieldset> and an <ol> tag to wrap the inputs.
26
+ #
27
+ # Registered as :fieldset_ol.
28
+ class InputsWrapper::FieldSetOL < InputsWrapper
29
+ Forme.register_transformer(:inputs_wrapper, :fieldset_ol, new)
30
+
31
+ # Wrap the inputs in a <fieldset> and a <ol> tag.
32
+ def call(form, opts)
33
+ super(form, opts){form.tag_(:ol){yield}}
34
+ end
35
+ end
36
+
37
+ # Use an <ol> tag to wrap the inputs.
38
+ #
39
+ # Registered as :ol.
40
+ class InputsWrapper::OL
41
+ Forme.register_transformer(:inputs_wrapper, :ol, new)
42
+
43
+ # Wrap the inputs in an <ol> tag
44
+ def call(form, opts, &block)
45
+ form.tag(:ol, opts[:attr], &block)
46
+ end
47
+ end
48
+
49
+ # Use a <div> tag to wrap the inputs.
50
+ #
51
+ # Registered as :div.
52
+ class InputsWrapper::Div
53
+ Forme.register_transformer(:inputs_wrapper, :div, new)
54
+
55
+ # Wrap the inputs in an <div> tag
56
+ def call(form, opts, &block)
57
+ form.tag(:div, opts[:attr], &block)
58
+ end
59
+ end
60
+
61
+ # Use a <tr> tag to wrap the inputs.
62
+ #
63
+ # Registered as :tr.
64
+ class InputsWrapper::TR
65
+ Forme.register_transformer(:inputs_wrapper, :tr, new)
66
+
67
+ # Wrap the inputs in an <tr> tag
68
+ def call(form, opts, &block)
69
+ form.tag(:tr, opts[:attr], &block)
70
+ end
71
+ end
72
+
73
+ # Use a <table> tag to wrap the inputs.
74
+ #
75
+ # Registered as :table.
76
+ class InputsWrapper::Table
77
+ Forme.register_transformer(:inputs_wrapper, :table, new)
78
+
79
+ # Wrap the inputs in a <table> tag.
80
+ def call(form, opts, &block)
81
+ attr = opts[:attr] ? opts[:attr].dup : {}
82
+ form.tag(:table, attr) do
83
+ if legend = opts[:legend]
84
+ form.emit(form.tag(:caption, opts[:legend_attr], legend))
85
+ end
86
+
87
+ if (labels = opts[:labels]) && !labels.empty?
88
+ form.emit(form.tag(:tr, {}, labels.map{|l| form._tag(:th, {}, l)}))
89
+ end
90
+
91
+ yield
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,78 @@
1
+ module Forme
2
+ # Default labeler used by the library, using implicit labels (where the
3
+ # label tag encloses the other tag).
4
+ #
5
+ # Registered as :default.
6
+ class Labeler
7
+ Forme.register_transformer(:labeler, :default, new)
8
+
9
+ # Return a label tag wrapping the given tag. For radio and checkbox
10
+ # inputs, the label occurs directly after the tag, for all other types,
11
+ # the label occurs before the tag.
12
+ def call(tag, input)
13
+ label = input.opts[:label]
14
+ label_position = input.opts[:label_position]
15
+ if [:radio, :checkbox].include?(input.type)
16
+ if input.type == :checkbox && tag.is_a?(Array) && tag.length == 2 && tag.first.attr[:type].to_s == 'hidden'
17
+ t = if label_position == :before
18
+ [label, ' ', tag.last]
19
+ else
20
+ [tag.last, ' ', label]
21
+ end
22
+ return [tag.first , input.tag(:label, input.opts[:label_attr]||{}, t)]
23
+ elsif label_position == :before
24
+ t = [label, ' ', tag]
25
+ else
26
+ t = [tag, ' ', label]
27
+ end
28
+ elsif label_position == :after
29
+ t = [tag, ' ', label]
30
+ else
31
+ t = [label, ": ", tag]
32
+ end
33
+ input.tag(:label, input.opts[:label_attr]||{}, t)
34
+ end
35
+ end
36
+
37
+ # Explicit labeler that creates a separate label tag that references
38
+ # the given tag's id using a +for+ attribute. Requires that all tags
39
+ # with labels have +id+ fields.
40
+ #
41
+ # Registered as :explicit.
42
+ class Labeler::Explicit
43
+ Forme.register_transformer(:labeler, :explicit, new)
44
+
45
+ # Return an array with a label tag as the first entry and +tag+ as
46
+ # a second entry. If the +input+ has a :label_for option, use that,
47
+ # otherwise use the input's :id option. If neither the :id or
48
+ # :label_for option is used, the label created will not be
49
+ # associated with an input.
50
+ def call(tag, input)
51
+ unless id = input.opts[:id]
52
+ if key = input.opts[:key]
53
+ namespaces = input.form_opts[:namespace]
54
+ id = "#{namespaces.join('_')}#{'_' unless namespaces.empty?}#{key}"
55
+ if key_id = input.opts[:key_id]
56
+ id << "_#{key_id.to_s}"
57
+ end
58
+ end
59
+ end
60
+
61
+ label_attr = input.opts[:label_attr]
62
+ label_attr = label_attr ? label_attr.dup : {}
63
+ label_attr[:for] ||= input.opts.fetch(:label_for, id)
64
+ lpos = input.opts[:label_position] || ([:radio, :checkbox].include?(input.type) ? :after : :before)
65
+
66
+ Forme.attr_classes(label_attr, "label-#{lpos}")
67
+ label = input.tag(:label, label_attr, [input.opts[:label]])
68
+
69
+ t = if lpos == :before
70
+ [label, tag]
71
+ else
72
+ [tag, label]
73
+ end
74
+
75
+ t
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,171 @@
1
+ module Forme
2
+ # Default serializer class used by the library. Any other serializer
3
+ # classes that want to produce html should probably subclass this class.
4
+ #
5
+ # Registered as :default.
6
+ class Serializer
7
+ Forme.register_transformer(:serializer, :default, new)
8
+
9
+ # Borrowed from Rack::Utils, map of single character strings to html escaped versions.
10
+ ESCAPE_HTML = {"&" => "&amp;", "<" => "&lt;", ">" => "&gt;", "'" => "&#39;", '"' => "&quot;"}
11
+
12
+ # A regexp that matches all html characters requiring escaping.
13
+ ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
14
+
15
+ # Which tags are self closing (such tags ignore children).
16
+ SELF_CLOSING = [:img, :input]
17
+
18
+ # Serialize the tag object to an html string. Supports +Tag+ instances,
19
+ # +Input+ instances (recursing into +call+ with the result of formatting the input),
20
+ # arrays (recurses into +call+ for each entry and joins the result), and
21
+ # (html escapes the string version of them, unless they include the +Raw+
22
+ # module, in which case no escaping is done).
23
+ def call(tag)
24
+ case tag
25
+ when Tag
26
+ if SELF_CLOSING.include?(tag.type)
27
+ "<#{tag.type}#{attr_html(tag.attr)}/>"
28
+ else
29
+ "#{serialize_open(tag)}#{call(tag.children)}#{serialize_close(tag)}"
30
+ end
31
+ when Input
32
+ call(tag.format)
33
+ when Array
34
+ tag.map{|x| call(x)}.join
35
+ when DateTime, Time
36
+ format_time(tag)
37
+ when Date
38
+ format_date(tag)
39
+ when BigDecimal
40
+ tag.to_s('F')
41
+ when Raw
42
+ tag.to_s
43
+ else
44
+ h tag
45
+ end
46
+ end
47
+
48
+ # Returns the opening part of the given tag.
49
+ def serialize_open(tag)
50
+ "<#{tag.type}#{attr_html(tag.attr)}>"
51
+ end
52
+
53
+ # Returns the closing part of the given tag.
54
+ def serialize_close(tag)
55
+ "</#{tag.type}>"
56
+ end
57
+
58
+ private
59
+
60
+ # Return a string in ISO format representing the +Date+ instance.
61
+ def format_date(date)
62
+ date.strftime("%F")
63
+ end
64
+
65
+ # Return a string in ISO format representing the +Time+ or +DateTime+ instance.
66
+ def format_time(time)
67
+ time.is_a?(Time) ? (time.strftime('%Y-%m-%dT%H:%M:%S') + sprintf(".%06d", time.usec)) : (time.strftime('%Y-%m-%dT%H:%M:%S.') + time.strftime('%N')[0...6])
68
+ end
69
+
70
+ # Escape ampersands, brackets and quotes to their HTML/XML entities.
71
+ def h(string)
72
+ string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
73
+ end
74
+
75
+ # Join attribute values that are arrays with spaces instead of an empty
76
+ # string.
77
+ def attr_value(v)
78
+ if v.is_a?(Array)
79
+ v.map{|c| attr_value(c)}.join(' ')
80
+ else
81
+ call(v)
82
+ end
83
+ end
84
+
85
+ # Transforms the +tag+'s attributes into an html string, sorting by the keys
86
+ # and quoting and html escaping the values.
87
+ def attr_html(attr)
88
+ attr = attr.to_a.reject{|k,v| v.nil?}
89
+ " #{attr.map{|k, v| "#{k}=\"#{attr_value(v)}\""}.sort.join(' ')}" unless attr.empty?
90
+ end
91
+ end
92
+
93
+ # Overrides formatting of dates and times to use an American format without
94
+ # timezones.
95
+ class Serializer::AmericanTime < Serializer
96
+ Forme.register_transformer(:serializer, :html_usa, new)
97
+
98
+ def call(tag)
99
+ case tag
100
+ when Tag
101
+ if tag.type.to_s == 'input' && %w'date datetime datetime-local'.include?((tag.attr[:type] || tag.attr['type']).to_s)
102
+ attr = tag.attr.dup
103
+ attr.delete(:type)
104
+ attr.delete('type')
105
+ attr['type'] = 'text'
106
+ "<#{tag.type}#{attr_html(attr)}/>"
107
+ else
108
+ super
109
+ end
110
+ else
111
+ super
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ # Return a string in American format representing the +Date+ instance.
118
+ def format_date(date)
119
+ date.strftime("%m/%d/%Y")
120
+ end
121
+
122
+ # Return a string in American format representing the +Time+ or +DateTime+ instance, without the timezone.
123
+ def format_time(time)
124
+ time.strftime("%m/%d/%Y %I:%M:%S%p")
125
+ end
126
+ end
127
+
128
+ # Serializer class that converts tags to plain text strings.
129
+ #
130
+ # Registered at :text.
131
+ class Serializer::PlainText
132
+ Forme.register_transformer(:serializer, :text, new)
133
+
134
+ # Serialize the tag to plain text string.
135
+ def call(tag)
136
+ case tag
137
+ when Tag
138
+ case tag.type.to_sym
139
+ when :input
140
+ case tag.attr[:type].to_sym
141
+ when :radio, :checkbox
142
+ tag.attr[:checked] ? '_X_' : '___'
143
+ when :submit, :reset, :hidden
144
+ ''
145
+ when :password
146
+ "********\n"
147
+ else
148
+ "#{tag.attr[:value].to_s}\n"
149
+ end
150
+ when :select
151
+ "\n#{call(tag.children)}"
152
+ when :option
153
+ "#{call([tag.attr[:selected] ? '_X_ ' : '___ ', tag.children])}\n"
154
+ when :textarea, :label
155
+ "#{call(tag.children)}\n"
156
+ when :legend
157
+ v = call(tag.children)
158
+ "#{v}\n#{'-' * v.length}\n"
159
+ else
160
+ call(tag.children)
161
+ end
162
+ when Input
163
+ call(tag.format)
164
+ when Array
165
+ tag.map{|x| call(x)}.join
166
+ else
167
+ tag.to_s
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,52 @@
1
+ module Forme
2
+ # Default wrapper doesn't wrap input in any tag
3
+ class Wrapper
4
+ # Return an array containing the tag
5
+ def call(tag, input)
6
+ Array(tag)
7
+ end
8
+
9
+ Forme.register_transformer(:wrapper, :default, new)
10
+ end
11
+
12
+ # Wraps inputs using the given tag type.
13
+ class Wrapper::Tag < Wrapper
14
+ # Set the tag type to use.
15
+ def initialize(type)
16
+ @type = type
17
+ end
18
+
19
+ # Wrap the input in the tag of the given type.
20
+ def call(tag, input)
21
+ input.tag(@type, input.opts[:wrapper_attr], super)
22
+ end
23
+
24
+ [:li, :p, :div, :span, :td].each do |x|
25
+ Forme.register_transformer(:wrapper, x, new(x))
26
+ end
27
+ end
28
+
29
+ class Wrapper::TableRow < Wrapper
30
+ # Wrap the input in tr and td tags.
31
+ def call(tag, input)
32
+ a = super.flatten
33
+ labels, other = a.partition{|e| e.is_a?(Tag) && e.type.to_s == 'label'}
34
+ if labels.length == 1
35
+ ltd = labels
36
+ rtd = other
37
+ elsif a.length == 1
38
+ ltd = [a.first]
39
+ rtd = a[1..-1]
40
+ else
41
+ ltd = a
42
+ end
43
+ input.tag(:tr, input.opts[:wrapper_attr], [input.tag(:td, {}, ltd), input.tag(:td, {}, rtd)])
44
+ end
45
+
46
+ Forme.register_transformer(:wrapper, :trtd, new)
47
+ end
48
+
49
+ {:tr=>:td, :table=>:trtd, :ol=>:li, :fieldset_ol=>:li}.each do |k, v|
50
+ register_transformer(:wrapper, k, TRANSFORMERS[:wrapper][v])
51
+ end
52
+ end
@@ -1,6 +1,6 @@
1
1
  module Forme
2
2
  # Version constant, use <tt>Forme.version</tt> instead.
3
- VERSION = '1.2.0'.freeze
3
+ VERSION = '1.3.0'.freeze
4
4
 
5
5
  # Returns the version as a frozen string (e.g. '0.1.0')
6
6
  def self.version
@@ -53,6 +53,8 @@ module Sequel # :nodoc:
53
53
  # be created via the :inputs option. If you are not providing
54
54
  # an :inputs option or are using a block with additional inputs,
55
55
  # you should specify this option.
56
+ # :skip_primary_key :: Skip adding a hidden primary key field for existing
57
+ # objects.
56
58
  def subform(association, opts={}, &block)
57
59
  nested_obj = opts.has_key?(:obj) ? opts[:obj] : obj.send(association)
58
60
  ref = obj.class.association_reflection(association)
@@ -62,7 +64,7 @@ module Sequel # :nodoc:
62
64
 
63
65
  contents = proc do
64
66
  send(multiple ? :each_obj : :with_obj, nested_obj, ns) do |no, i|
65
- emit(input(ref.associated_class.primary_key, :type=>:hidden, :label=>nil, :wrapper=>nil)) unless no.new?
67
+ emit(input(ref.associated_class.primary_key, :type=>:hidden, :label=>nil, :wrapper=>nil)) unless no.new? || opts[:skip_primary_key]
66
68
  options = opts.dup
67
69
  if grid
68
70
  options.delete(:legend)
@@ -245,7 +247,7 @@ module Sequel # :nodoc:
245
247
  if meth = opts.delete(:name_method)
246
248
  meth
247
249
  else
248
- meths = FORME_NAME_METHODS & ref.associated_class.instance_methods.map{|s| s.to_sym}
250
+ meths = FORME_NAME_METHODS & ref.associated_class.instance_methods.map(&:to_sym)
249
251
  if meths.empty?
250
252
  raise Error, "No suitable name method found for association #{ref[:name]}"
251
253
  else
@@ -289,7 +291,8 @@ module Sequel # :nodoc:
289
291
  label = klass.send(:singularize, ref[:name])
290
292
 
291
293
  field = if ref[:type] == :pg_array_to_many
292
- ref[:key]
294
+ handle_errors(key)
295
+ key
293
296
  else
294
297
  "#{label}_pks"
295
298
  end
@@ -361,7 +364,7 @@ module Sequel # :nodoc:
361
364
  when :select
362
365
  v = opts[:value] || obj.send(field)
363
366
  opts[:value] = (v ? 't' : 'f') unless v.nil?
364
- opts[:add_blank] = true
367
+ opts[:add_blank] = true unless opts.has_key?(:add_blank)
365
368
  opts[:options] = [[opts[:true_label]||'True', opts[:true_value]||'t'], [opts[:false_label]||'False', opts[:false_value]||'f']]
366
369
  _input(:select, opts)
367
370
  else