forme 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|