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