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 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)
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011 Jeremy Evans
1
+ Copyright (c) 2011-2012 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
@@ -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 ERB Support
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
- In addition to ERB, it also works with Sinatra's Erubis support.
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
- Rake::RDocTask.new do |rdoc|
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"
@@ -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.inputs(ins, opts) if ins
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(inputs=[], opts={})
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
- # Normalize the options used for all input types. Handles:
800
- # :required :: Sets the +required+ attribute on the resulting tag if true.
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
- t = if [:radio, :checkbox].include?(input.type)
932
- [tag, ' ', label]
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
- [input.tag(:label, {:for=>input.opts.fetch(:label_for, input.opts[:id])}.merge(input.opts[:label_attr]||{}), [input.opts[:label]]), tag]
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.tag(:ol){yield}}
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}=\"#{call(v)}\""}.sort.join(' ')}" unless attr.empty?
1164
+ " #{attr.map{|k, v| "#{k}=\"#{attr_value(v)}\""}.sort.join(' ')}" unless attr.empty?
1114
1165
  end
1115
1166
  end
1116
1167
 
@@ -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
@@ -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
- # Always return nil, so that use with <%= doesn't cause
25
- # multiple things to be output.
26
- def inputs(*a)
27
- super
28
- nil
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
- # Always return nil, so that use with <%= doesn't cause
32
- # multiple things to be output.
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
- super
35
- nil
37
+ if block
38
+ capture(block){super}
39
+ else
40
+ capture{super}
41
+ end
36
42
  end
37
43
 
38
- # If a block is provided, inject an opening tag into the
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, and the return nil
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 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
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
- h = {:output=>@_out_buf}
81
- (obj.is_a?(Hash) ? attr = attr.merge(h) : opts = opts.merge(h))
82
- Form.form(obj, attr, opts, &block)
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