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.
@@ -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