forme 0.6.0 → 0.7.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.
- data/CHANGELOG +28 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -2
- data/Rakefile +14 -8
- data/lib/forme.rb +65 -14
- data/lib/forme/rails.rb +99 -0
- data/lib/forme/sinatra.rb +41 -23
- data/lib/forme/version.rb +1 -1
- data/lib/sequel/plugins/forme.rb +22 -10
- data/spec/forme_spec.rb +44 -0
- data/spec/rails_integration_spec.rb +195 -0
- data/spec/sequel_helper.rb +71 -0
- data/spec/sequel_plugin_spec.rb +41 -96
- data/spec/sinatra_integration_spec.rb +94 -2
- metadata +31 -44
data/CHANGELOG
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
=== 0.7.0 (2012-05-02)
|
2
|
+
|
3
|
+
* Support :label_position option in both of the labelers, can be set to :before or :after to override the default (jeremyevans)
|
4
|
+
|
5
|
+
* Add Rails integration (jeremyevans)
|
6
|
+
|
7
|
+
* Make explicit labeler put label after checkboxes and radio buttons instead of before (jeremyevans)
|
8
|
+
|
9
|
+
* Make implicit labeler not include hidden checkbox inside label (jeremyevans)
|
10
|
+
|
11
|
+
* Recognize :cols and :rows options as attributes for textarea inputs in the default formatter (jeremyevans)
|
12
|
+
|
13
|
+
* Recognize :size and :maxlength options as attributes for text inputs in the default formatter (jeremyevans)
|
14
|
+
|
15
|
+
* Recognize :style option as attributes in the default formatter (jeremyevans)
|
16
|
+
|
17
|
+
* Join attribute values specified as arrays with spaces instead of the empty string (jeremyevans)
|
18
|
+
|
19
|
+
* Make Sinatra ERB integration work with partials (jeremyevans)
|
20
|
+
|
21
|
+
* Add id attributes for association :as=>:radio or :as=>:checkbox fields (jeremyevans)
|
22
|
+
|
23
|
+
* Add an html class attribute for radio/checkbox labels in :as=>:radio or :as=>:checkbox fields (jeremyevans)
|
24
|
+
|
25
|
+
* Wrap text acting as a label in a span with class label for :as=>:radio or :as=>:checkbox fields (jeremyevans)
|
26
|
+
|
27
|
+
* Support overriding the true/false label and values for select boolean fields in the Sequel plugin (jeremyevans)
|
28
|
+
|
1
29
|
=== 0.6.0 (2011-08-01)
|
2
30
|
|
3
31
|
* Fix wrapping for :as=>:radio boolean fields to handle them like association :as=>:radio fields (jeremyevans)
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -200,7 +200,7 @@ The Forme Sequel plugin also integerates with Sequel's validation reflection sup
|
|
200
200
|
+validation_class_methods+ plugin that ships with Sequel. It will add +pattern+ and +maxlength+ attributes
|
201
201
|
based on the format, numericality, and length validations.
|
202
202
|
|
203
|
-
= Sinatra
|
203
|
+
= Sinatra Support
|
204
204
|
|
205
205
|
Forme ships with a Sinatra extension that you can get by <tt>require "forme/sinatra"</tt> and using
|
206
206
|
<tt>helpers Forme::Sinatra::ERB</tt> in your Sinatra::Base subclass. It allows you to use the
|
@@ -213,7 +213,23 @@ following API in your Sinatra ERB forms:
|
|
213
213
|
<% end %>
|
214
214
|
<% end %>
|
215
215
|
|
216
|
-
|
216
|
+
This example is for ERB/Erubis. Other Sinatra template libraries work differently and
|
217
|
+
probably do not support this integration.
|
218
|
+
|
219
|
+
= Rails Support
|
220
|
+
|
221
|
+
Forme ships with a Rails extension that you can get by <tt>require "forme/rails"</tt> and using
|
222
|
+
<tt>helpers Forme::Rails::ERB</tt> in your controller. If allows you to use the following API
|
223
|
+
in your Rails forms:
|
224
|
+
|
225
|
+
<%= forme(@obj, :action=>'/foo') do |f| %>
|
226
|
+
<%= f.input(:field) %>
|
227
|
+
<%= f.tag(:fieldset) do %>
|
228
|
+
<%= f.input(:field_two) %>
|
229
|
+
<% end %>
|
230
|
+
<% end %>
|
231
|
+
|
232
|
+
This has been tested on Rails 3.2, but should hopefully work on Rails 3.0+.
|
217
233
|
|
218
234
|
= Other Similar Projects
|
219
235
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
require "rake"
|
2
2
|
require "rake/clean"
|
3
|
-
begin
|
4
|
-
require "hanna/rdoctask"
|
5
|
-
rescue LoadError
|
6
|
-
require "rake/rdoctask"
|
7
|
-
end
|
8
3
|
|
9
4
|
NAME = 'forme'
|
10
5
|
VERS = lambda do
|
@@ -12,8 +7,6 @@ VERS = lambda do
|
|
12
7
|
Forme.version
|
13
8
|
end
|
14
9
|
CLEAN.include ["#{NAME}-*.gem", "rdoc", "coverage", '**/*.rbc']
|
15
|
-
RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'Forme']
|
16
|
-
RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
|
17
10
|
|
18
11
|
# Gem Packaging and Release
|
19
12
|
|
@@ -29,7 +22,20 @@ end
|
|
29
22
|
|
30
23
|
### RDoc
|
31
24
|
|
32
|
-
|
25
|
+
RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'Forme']
|
26
|
+
|
27
|
+
rdoc_task_class = begin
|
28
|
+
require "rdoc/task"
|
29
|
+
RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
|
30
|
+
RDoc::Task
|
31
|
+
rescue LoadError
|
32
|
+
require "rake/rdoctask"
|
33
|
+
Rake::RDocTask
|
34
|
+
end
|
35
|
+
|
36
|
+
RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
|
37
|
+
|
38
|
+
rdoc_task_class.new do |rdoc|
|
33
39
|
rdoc.rdoc_dir = "rdoc"
|
34
40
|
rdoc.options += RDOC_OPTS
|
35
41
|
rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb"
|
data/lib/forme.rb
CHANGED
@@ -205,7 +205,7 @@ module Forme
|
|
205
205
|
button = opts[:button]
|
206
206
|
if ins || button
|
207
207
|
block = Proc.new do |form|
|
208
|
-
form.
|
208
|
+
form._inputs(ins, opts) if ins
|
209
209
|
yield form if block_given?
|
210
210
|
form.emit(form.button(button)) if button
|
211
211
|
end
|
@@ -354,7 +354,13 @@ module Forme
|
|
354
354
|
# The given +opts+ are passed to the +inputs_wrapper+, and the default
|
355
355
|
# +inputs_wrapper+ supports a <tt>:legend</tt> option that is used to
|
356
356
|
# set the legend for the fieldset.
|
357
|
-
def inputs(
|
357
|
+
def inputs(*a, &block)
|
358
|
+
_inputs(*a, &block)
|
359
|
+
end
|
360
|
+
|
361
|
+
# Internals of #inputs, should be used internally by the library, where #inputs
|
362
|
+
# is designed for external use.
|
363
|
+
def _inputs(inputs=[], opts={})
|
358
364
|
if inputs.is_a?(Hash)
|
359
365
|
opts = inputs.merge(opts)
|
360
366
|
inputs = []
|
@@ -396,6 +402,10 @@ module Forme
|
|
396
402
|
tag
|
397
403
|
end
|
398
404
|
|
405
|
+
def tag_(*a, &block)
|
406
|
+
tag(*a, &block)
|
407
|
+
end
|
408
|
+
|
399
409
|
# Creates a :submit +Input+ with the given opts, adding it to the list
|
400
410
|
# of children for the currently open tag.
|
401
411
|
def button(opts={})
|
@@ -585,7 +595,7 @@ module Forme
|
|
585
595
|
# attributes hash, so they don't need to be specified in the :attr
|
586
596
|
# option. However, they can be specified in both places, and if so,
|
587
597
|
# the :attr option version takes precedence.
|
588
|
-
ATTRIBUTE_OPTIONS = [:name, :id, :placeholder, :value]
|
598
|
+
ATTRIBUTE_OPTIONS = [:name, :id, :placeholder, :value, :style]
|
589
599
|
|
590
600
|
# Create a new instance and call it
|
591
601
|
def self.call(input)
|
@@ -719,6 +729,7 @@ module Forme
|
|
719
729
|
# with the type attribute set to input.
|
720
730
|
def _format_input(type)
|
721
731
|
@attr[:type] = type
|
732
|
+
copy_options_to_attributes([:size, :maxlength])
|
722
733
|
tag(:input)
|
723
734
|
end
|
724
735
|
|
@@ -789,6 +800,7 @@ module Forme
|
|
789
800
|
# Formats a textarea. Respects the following options:
|
790
801
|
# :value :: Sets value as the child of the textarea.
|
791
802
|
def format_textarea
|
803
|
+
copy_options_to_attributes([:cols, :rows])
|
792
804
|
if val = @attr.delete(:value)
|
793
805
|
tag(:textarea, @attr, [val])
|
794
806
|
else
|
@@ -796,15 +808,19 @@ module Forme
|
|
796
808
|
end
|
797
809
|
end
|
798
810
|
|
799
|
-
|
800
|
-
|
801
|
-
# :disabled :: Sets the +disabled+ attribute on the resulting tag if true.
|
802
|
-
def normalize_options
|
803
|
-
ATTRIBUTE_OPTIONS.each do |k|
|
811
|
+
def copy_options_to_attributes(attributes)
|
812
|
+
attributes.each do |k|
|
804
813
|
if @opts.has_key?(k) && !@attr.has_key?(k)
|
805
814
|
@attr[k] = @opts[k]
|
806
815
|
end
|
807
816
|
end
|
817
|
+
end
|
818
|
+
|
819
|
+
# Normalize the options used for all input types. Handles:
|
820
|
+
# :required :: Sets the +required+ attribute on the resulting tag if true.
|
821
|
+
# :disabled :: Sets the +disabled+ attribute on the resulting tag if true.
|
822
|
+
def normalize_options
|
823
|
+
copy_options_to_attributes(ATTRIBUTE_OPTIONS)
|
808
824
|
|
809
825
|
Forme.attr_classes(@attr, @opts[:class]) if @opts.has_key?(:class)
|
810
826
|
Forme.attr_classes(@attr, 'error') if @opts[:error]
|
@@ -928,10 +944,24 @@ module Forme
|
|
928
944
|
# the label occurs before the tag.
|
929
945
|
def call(tag, input)
|
930
946
|
label = input.opts[:label]
|
931
|
-
|
932
|
-
|
947
|
+
label_position = input.opts[:label_position]
|
948
|
+
if [:radio, :checkbox].include?(input.type)
|
949
|
+
if input.type == :checkbox && tag.is_a?(Array) && tag.length == 2 && tag.first.attr[:type].to_s == 'hidden'
|
950
|
+
t = if label_position == :before
|
951
|
+
[label, ' ', tag.last]
|
952
|
+
else
|
953
|
+
[tag.last, ' ', label]
|
954
|
+
end
|
955
|
+
return [tag.first , input.tag(:label, input.opts[:label_attr]||{}, t)]
|
956
|
+
elsif label_position == :before
|
957
|
+
t = [label, ' ', tag]
|
958
|
+
else
|
959
|
+
t = [tag, ' ', label]
|
960
|
+
end
|
961
|
+
elsif label_position == :after
|
962
|
+
t = [tag, ' ', label]
|
933
963
|
else
|
934
|
-
[label, ": ", tag]
|
964
|
+
t = [label, ": ", tag]
|
935
965
|
end
|
936
966
|
input.tag(:label, input.opts[:label_attr]||{}, t)
|
937
967
|
end
|
@@ -951,7 +981,18 @@ module Forme
|
|
951
981
|
# :label_for option is used, the label created will not be
|
952
982
|
# associated with an input.
|
953
983
|
def call(tag, input)
|
954
|
-
[
|
984
|
+
if [:radio, :checkbox].include?(input.type)
|
985
|
+
t = [tag, input.tag(:label, {:for=>input.opts.fetch(:label_for, input.opts[:id])}.merge(input.opts[:label_attr]||{}), [input.opts[:label]])]
|
986
|
+
p = :before
|
987
|
+
else
|
988
|
+
t = [input.tag(:label, {:for=>input.opts.fetch(:label_for, input.opts[:id])}.merge(input.opts[:label_attr]||{}), [input.opts[:label]]), tag]
|
989
|
+
p = :after
|
990
|
+
end
|
991
|
+
if input.opts[:label_position] == p
|
992
|
+
t.reverse
|
993
|
+
else
|
994
|
+
t
|
995
|
+
end
|
955
996
|
end
|
956
997
|
end
|
957
998
|
|
@@ -995,7 +1036,7 @@ module Forme
|
|
995
1036
|
|
996
1037
|
# Wrap the inputs in an ol tag
|
997
1038
|
def call(form, opts)
|
998
|
-
super(form, opts){form.
|
1039
|
+
super(form, opts){form.tag_(:ol){yield}}
|
999
1040
|
end
|
1000
1041
|
end
|
1001
1042
|
|
@@ -1106,11 +1147,21 @@ module Forme
|
|
1106
1147
|
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
1107
1148
|
end
|
1108
1149
|
|
1150
|
+
# Join attribute values that are arrays with spaces instead of an empty
|
1151
|
+
# string.
|
1152
|
+
def attr_value(v)
|
1153
|
+
if v.is_a?(Array)
|
1154
|
+
v.map{|c| attr_value(c)}.join(' ')
|
1155
|
+
else
|
1156
|
+
call(v)
|
1157
|
+
end
|
1158
|
+
end
|
1159
|
+
|
1109
1160
|
# Transforms the +tag+'s attributes into an html string, sorting by the keys
|
1110
1161
|
# and quoting and html escaping the values.
|
1111
1162
|
def attr_html(tag)
|
1112
1163
|
attr = tag.attr.to_a.reject{|k,v| v.nil?}
|
1113
|
-
" #{attr.map{|k, v| "#{k}=\"#{
|
1164
|
+
" #{attr.map{|k, v| "#{k}=\"#{attr_value(v)}\""}.sort.join(' ')}" unless attr.empty?
|
1114
1165
|
end
|
1115
1166
|
end
|
1116
1167
|
|
data/lib/forme/rails.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'forme'
|
2
|
+
|
3
|
+
module Forme
|
4
|
+
module Rails # :nodoc:
|
5
|
+
# Subclass used when using Forme/Rails ERB integration,
|
6
|
+
# handling integration with the view template.
|
7
|
+
class Form < ::Forme::Form
|
8
|
+
# The Rails template that created this form.
|
9
|
+
attr_reader :template
|
10
|
+
|
11
|
+
# Set the template object when initializing.
|
12
|
+
def initialize(*)
|
13
|
+
super
|
14
|
+
@template = @opts[:template]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Serialize and mark as already escaped the string version of
|
18
|
+
# the input.
|
19
|
+
def emit(tag)
|
20
|
+
template.output_buffer << template.raw(tag.to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Capture the inputs into a new output buffer, and return
|
24
|
+
# the buffer if not given a block
|
25
|
+
def inputs(*)
|
26
|
+
if block_given?
|
27
|
+
super
|
28
|
+
else
|
29
|
+
template.send(:with_output_buffer){super}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# If a block is not given, emit the inputs into the current output
|
34
|
+
# buffer.
|
35
|
+
def _inputs(*)
|
36
|
+
if block_given?
|
37
|
+
super
|
38
|
+
else
|
39
|
+
emit(super)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return a string version of the input that is already marked as safe.
|
44
|
+
def input(*)
|
45
|
+
template.raw(super.to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return a string version of the button that is already marked as safe.
|
49
|
+
def button(*)
|
50
|
+
template.raw(super.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
# If a block is given, create a new output buffer and make sure all the
|
54
|
+
# output of the tag goes into that buffer, and return the buffer.
|
55
|
+
# Otherwise, just return a string version of the tag that is already
|
56
|
+
# marked as safe.
|
57
|
+
def tag(type, attr={}, children=[], &block)
|
58
|
+
if block_given?
|
59
|
+
template.send(:with_output_buffer){tag_(type, attr, children, &block)}
|
60
|
+
else
|
61
|
+
tag = _tag(type, attr, children)
|
62
|
+
template.raw(tag.to_s)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def tag_(type, attr={}, children=[])
|
67
|
+
tag = _tag(type, attr, children)
|
68
|
+
emit(serializer.serialize_open(tag)) if serializer.respond_to?(:serialize_open)
|
69
|
+
Array(children).each{|c| emit(c)}
|
70
|
+
yield self if block_given?
|
71
|
+
emit(serializer.serialize_close(tag)) if serializer.respond_to?(:serialize_close)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module ERB
|
76
|
+
# Create a +Form+ object and yield it to the block,
|
77
|
+
# injecting the opening form tag before yielding and
|
78
|
+
# the closing form tag after yielding.
|
79
|
+
#
|
80
|
+
# Argument Handling:
|
81
|
+
# No args :: Creates a +Form+ object with no options and not associated
|
82
|
+
# to an +obj+, and with no attributes in the opening tag.
|
83
|
+
# 1 hash arg :: Treated as opening form tag attributes, creating a
|
84
|
+
# +Form+ object with no options.
|
85
|
+
# 1 non-hash arg :: Treated as the +Form+'s +obj+, with empty options
|
86
|
+
# and no attributes in the opening tag.
|
87
|
+
# 2 hash args :: First hash is opening attributes, second hash is +Form+
|
88
|
+
# options.
|
89
|
+
# 1 non-hash arg, 1-2 hash args :: First argument is +Form+'s obj, second is
|
90
|
+
# opening attributes, third if provided is
|
91
|
+
# +Form+'s options.
|
92
|
+
def forme(obj=nil, attr={}, opts={}, &block)
|
93
|
+
h = {:template=>self}
|
94
|
+
(obj.is_a?(Hash) ? attr = attr.merge(h) : opts = opts.merge(h))
|
95
|
+
Form.form(obj, attr, opts, &block)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/forme/sinatra.rb
CHANGED
@@ -13,7 +13,7 @@ module Forme
|
|
13
13
|
# Set the template output object when initializing.
|
14
14
|
def initialize(*)
|
15
15
|
super
|
16
|
-
@output = @opts[:output]
|
16
|
+
@output = @opts[:output] ? @opts[:output] : ''
|
17
17
|
end
|
18
18
|
|
19
19
|
# Serialize the tag and inject it into the output.
|
@@ -21,37 +21,51 @@ module Forme
|
|
21
21
|
output << tag.to_s
|
22
22
|
end
|
23
23
|
|
24
|
-
#
|
25
|
-
#
|
26
|
-
def inputs(*a)
|
27
|
-
|
28
|
-
|
24
|
+
# Capture the inside of the inputs, injecting it into the template
|
25
|
+
# if a block is given, or returning it as a string if not.
|
26
|
+
def inputs(*a, &block)
|
27
|
+
if block
|
28
|
+
capture(block){super}
|
29
|
+
else
|
30
|
+
capture{super}
|
31
|
+
end
|
29
32
|
end
|
30
33
|
|
31
|
-
#
|
32
|
-
#
|
34
|
+
# Capture the inside of the form, injecting it into the template if
|
35
|
+
# a block is given, or returning it as a string if not.
|
33
36
|
def form(*a, &block)
|
34
|
-
|
35
|
-
|
37
|
+
if block
|
38
|
+
capture(block){super}
|
39
|
+
else
|
40
|
+
capture{super}
|
41
|
+
end
|
36
42
|
end
|
37
43
|
|
38
|
-
# If a block is
|
44
|
+
# If a block is given, inject an opening tag into the
|
39
45
|
# output, inject any given children into the output, yield to the
|
40
|
-
# block, inject a closing tag into the output
|
41
|
-
# so that usage with <%= doesn't cause multiple things to be output.
|
46
|
+
# block, inject a closing tag into the output.
|
42
47
|
# If a block is not given, just return the tag created.
|
43
|
-
def tag(type, attr={}, children=[])
|
48
|
+
def tag(type, attr={}, children=[], &block)
|
44
49
|
tag = _tag(type, attr, children)
|
45
|
-
if
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
50
|
+
if block
|
51
|
+
capture(block) do
|
52
|
+
emit(serializer.serialize_open(tag)) if serializer.respond_to?(:serialize_open)
|
53
|
+
children.each{|c| emit(c)}
|
54
|
+
yield self
|
55
|
+
emit(serializer.serialize_close(tag)) if serializer.respond_to?(:serialize_close)
|
56
|
+
end
|
51
57
|
else
|
52
58
|
tag
|
53
59
|
end
|
54
60
|
end
|
61
|
+
|
62
|
+
def capture(block='')
|
63
|
+
buf_was, @output = @output, block.is_a?(Proc) ? (eval("@_out_buf", block.binding) || @output) : block
|
64
|
+
yield
|
65
|
+
ret = @output
|
66
|
+
@output = buf_was
|
67
|
+
ret
|
68
|
+
end
|
55
69
|
end
|
56
70
|
|
57
71
|
# This is the module used to add the Forme integration
|
@@ -77,9 +91,13 @@ module Forme
|
|
77
91
|
# opening attributes, third if provided is
|
78
92
|
# +Form+'s options.
|
79
93
|
def form(obj=nil, attr={}, opts={}, &block)
|
80
|
-
|
81
|
-
|
82
|
-
|
94
|
+
if block
|
95
|
+
h = {:output=>@_out_buf}
|
96
|
+
(obj.is_a?(Hash) ? attr = attr.merge(h) : opts = opts.merge(h))
|
97
|
+
Form.form(obj, attr, opts, &block)
|
98
|
+
else
|
99
|
+
Form.form(obj, attr, opts)
|
100
|
+
end
|
83
101
|
end
|
84
102
|
end
|
85
103
|
|