forme 1.8.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd9180036757136c1db6fac34b523812491b687be4395d87ef09efee4066d85b
4
- data.tar.gz: a627b8fdde2c50d73e28b9a7d9d66545de2df0e180dd06832beea3c4990db04c
3
+ metadata.gz: f9ee8a7a7efaf9ff59c55404e6c11aba0125cdf59972366b94aa3359b06c6c1f
4
+ data.tar.gz: dd9f90ad552c827b66a3d8146f8933e5258b34ece1db5a8a9515ac9e2aeada44
5
5
  SHA512:
6
- metadata.gz: 489e5bf2d0d1d3af91e85c36e864775395ff6aede61c591e519c951118add014491e6b9d1b01e10364408fafcc8cc51aeffdcf43a21ea8fe2f247e0932d6acd7
7
- data.tar.gz: f6aa875870c75b068b2fd91846f2deba8e3f3304797c945895469e0903797ad0a9eb7a425b8185a3e707e13f30c02f3cb879a43c0d412b1c09f630d9eeefc526
6
+ metadata.gz: 5867cc14f49ea0a2419e4f7d30a518a8886753f11e4e6af46f7565106ef54869c9bf9d5ba691f7f08ee7fab5d729bb16e8b1463939dc043d7e91aeb5e8bf1f2e
7
+ data.tar.gz: 25bf44d57e37ef994cab9a6c2bf532b4dddd209fff6b1542b21596e2cdbfa1e20a1ae04af9d2b4c0ad113f322c06fe1e8a662a4d2facd36439f2c1a45d2e4250
data/CHANGELOG CHANGED
@@ -1,3 +1,53 @@
1
+ === 1.12.0 (2021-08-25)
2
+
3
+ * Make forme_set Sequel plugin handle frozen Sequel::Model instances (jeremyevans)
4
+
5
+ * Do not override an error on a field when using the Sequel plugin if :error option is already given (jeremyevans)
6
+
7
+ * Avoid error when creating label text when using Sequel input on non-Sequel form without an explicit :label option (jeremyevans)
8
+
9
+ * Make :select_options option for date/datetime selects support providing both option texts and option values using a 2 element array (jeremyevans)
10
+
11
+ === 1.11.0 (2020-01-03)
12
+
13
+ * Add Roda forme_set plugin, using HMACed form metadata to automatically handle submitted form parameters (jeremyevans)
14
+
15
+ === 1.10.0 (2019-05-13)
16
+
17
+ * Make readonly formatter ignore hidden inputs (jeremyevans)
18
+
19
+ * Add :select_labels for date inputs using :as=>:select to use labels for the inputs for better accessibility (jeremyevans)
20
+
21
+ * Add :after_legend error_handler for adding error message after legend when using :legend labeler (jeremyevans)
22
+
23
+ * Add aria-describedby to all inputs with errors where possible for better accessibility (jeremyevans)
24
+
25
+ * Add aria-invalid to all inputs with errors for better accessibility (jeremyevans)
26
+
27
+ * Support :fieldset wrapper and :legend labeler, can be used for accessible radioset/checkboxset (jeremyevans)
28
+
29
+ * Support :tag_label_attr option for radioset and checkbox set for label attributes for each radio/checkbox label (jeremyevans)
30
+
31
+ * Support custom :error_handler in radioset and checkboxset inputs (jeremyevans)
32
+
33
+ * Support custom :labeler in radioset and checkboxset inputs (jeremyevans)
34
+
35
+ * Avoid calling Proc.new with an implicit block, which is deprecated starting in ruby 2.7 (jeremyevans)
36
+
37
+ === 1.9.0 (2018-11-16)
38
+
39
+ * Automatically add maxlength attributes to text and textarea inputs in the Sequel plugin based on maximum database column length (jeremyevans)
40
+
41
+ * Make forme_set Sequel plugin recognize default formatter changes set via with_opts (jeremyevans)
42
+
43
+ * Use div with nested p tags instead of spans for readonly textarea inputs (jeremyevans)
44
+
45
+ * Make readonly text input spans use the readonly-text class for easier styling (jeremyevans)
46
+
47
+ * Add Forme.h for HTML escaping, using cgi/escape if available for faster escaping (jeremyevans)
48
+
49
+ * Correctly handle :value=>false option and false option values in select, radioset, and checkboxset inputs (jeremyevans)
50
+
1
51
  === 1.8.0 (2018-06-11)
2
52
 
3
53
  * Add support for :errors form option for setting error information for multiple inputs, similar to :values form option (adam12) (#32)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2018 Jeremy Evans
1
+ Copyright (c) 2011-2021 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
data/README.rdoc CHANGED
@@ -101,8 +101,7 @@ by creating your own transformer and then calling the existing transformer.
101
101
  Demo Site :: http://forme-demo.jeremyevans.net
102
102
  RDoc :: http://forme.jeremyevans.net
103
103
  Source :: https://github.com/jeremyevans/forme
104
- IRC :: irc://irc.freenode.net/forme
105
- Google Group :: https://groups.google.com/forum/#!forum/ruby-forme
104
+ Discussion Forum :: https://github.com/jeremyevans/forme/discussions
106
105
  Bug Tracker :: https://github.com/jeremyevans/forme/issues
107
106
 
108
107
  = Basic Usage
@@ -589,7 +588,7 @@ As you can see, you basically need to recreate the conditionals used when creati
589
588
  the form, so that that the processing of the form submission handles only the
590
589
  inputs that were displayed on the form.
591
590
 
592
- === forme_set plugin
591
+ === forme_set Sequel plugin
593
592
 
594
593
  The forme_set plugin is designed to make handling form submissions easier. What it does
595
594
  is record the form fields that are used on the object, and then it uses those fields
@@ -686,6 +685,166 @@ internally). forme_parse returns a hash with the following keys:
686
685
  It is possible to use forme_set for the values it can handle, and set other fields manually
687
686
  using set_fields.
688
687
 
688
+ === forme_set Roda plugin
689
+
690
+ The forme_set Roda plugin builds on the forme_set Sequel plugin and is designed to make
691
+ handling form submissions even easier. This plugin uses a hidden form input to store which
692
+ fields were used to build the form, as well as some other metadata. It uses another hidden
693
+ form input with an HMAC, so that on submission, if the HMAC matches, you can be sure that an
694
+ attacker didn't add extra fields.
695
+
696
+ There are a couple advantages to this plugin over using just the Sequel forme_set plugin.
697
+ One is that you do not need to record the form fields when processing the submission of a
698
+ form, since the information you need is included in the form submission. Another is that
699
+ calling the forme_set method is simpler, since it can determine the necessary parameters.
700
+
701
+ While you need code like this when using just the Sequel forme_set plugin:
702
+
703
+ album = Album[1]
704
+ Forme.form(album, :action=>'/foo') do |f|
705
+ f.input :name
706
+ f.input :copies_sold if album.released?
707
+ end
708
+ album.forme_set(params['album'])
709
+
710
+ when you also use the Roda forme_set plugin, you can simplify it to:
711
+
712
+ album = Album[1]
713
+ forme_set(album)
714
+
715
+ ==== Validations
716
+
717
+ The Roda forme_set plugin supports and uses the same validations as the Sequel forme_set
718
+ plugin. However, the Roda plugin is more accurate because it uses the options that were
719
+ present on the form when it was originally built, instead of the options that would be
720
+ present on the form when the form was submitted. However, note that that can be a
721
+ negative if you are dynamically adding values to both the database and the form between
722
+ when the form was built and when it was submitted.
723
+
724
+ ==== Usage
725
+
726
+ Because the Roda forme_set plugin includes the metadata needed to process the form in form
727
+ submissions, you don't need to rearrange code to use it, or rerender templates.
728
+ You can do:
729
+
730
+ album = Album[1]
731
+ forme_set(album)
732
+
733
+ And the method will update the +album+ object using the appropriate form values.
734
+
735
+ Note that using the Roda forme_set plugin requires you set a secret for the HMAC. It
736
+ is important that you keep this value secret, because if an attacker has access to this,
737
+ they would be able to set arbitrary attributes for model objects. In your Roda class,
738
+ you can load the plugin via:
739
+
740
+ plugin :forme_set, :secret => ENV["APP_FORME_HMAC_SECRET"]
741
+
742
+ By default, invalid form submissions will raise an exception. If you want to change
743
+ that behavior (i.e. to display a nice error page), pass a block when loading the plugin:
744
+
745
+ plugin :forme_set do |error_type, obj|
746
+ # ...
747
+ end
748
+
749
+ The block arguments will be a symbol for the type of error (:missing_data, :missing_hmac,
750
+ :hmac_mismatch, :csrf_mismatch, or :missing_namespace) and the object passed to +forme_set+.
751
+ This block should raise or halt. If it does not, the default behavior of raising an
752
+ exception will be taken.
753
+
754
+ === Form Versions
755
+
756
+ The Roda forme_set plugin supports form versions. This allows you to gracefully handle
757
+ changes to forms, processing submissions of the form generated before the change (if
758
+ possible) as well as the processing submissions of the form generated after the change.
759
+
760
+ For example, maybe you have an existing form with just an input for the name:
761
+
762
+ form(album) do |f|
763
+ f.input(:name)
764
+ end
765
+
766
+ Then later, you want to add an input for the number of copies sold:
767
+
768
+ form(album) do |f|
769
+ f.input(:name)
770
+ f.input(:copies_sold)
771
+ end
772
+
773
+ Using the Roda forme_set plugin, submissions of the old form would only set the
774
+ name field, it wouldn't set the copies_sold field, since when the form was created,
775
+ only the name field was used.
776
+
777
+ You can handle this case be versioning the form when making changes to it:
778
+
779
+ form(album, {}, :form_version=>1) do |f|
780
+ f.input(:name)
781
+ f.input(:copies_sold)
782
+ end
783
+
784
+ When you are processing the form submission with forme_set, you pass a block, which
785
+ will be yielded the version for the form (nil if no version was set):
786
+
787
+ forme_set(album) do |version|
788
+ if version == nil
789
+ album.copies_sold = 0
790
+ end
791
+ end
792
+
793
+ The block is also yielded the object passed for forme_set, useful if you don't keep
794
+ a reference to it:
795
+
796
+ album = forme_set(Album.new) do |version, obj|
797
+ if version == nil
798
+ obj.copies_sold = 0
799
+ end
800
+ end
801
+
802
+ You only need to support old versions of the form for as long as their could be
803
+ active sessions that could use the old versions of the form. As long you as
804
+ are expiring sessions to prevent session fixation, you can remove the version
805
+ handling after the expiration period has passed since the change to the form
806
+ was made.
807
+
808
+ Note that this issue with handling changes to forms is not specific to the Roda
809
+ forme_set plugin, it affects pretty much all form submissions. The Roda forme_set
810
+ plugin just makes this issue easier to handle.
811
+
812
+ ==== Caveats
813
+
814
+ The Roda forme_set plugin has basically the same caveats as Sequel forme_set plugin.
815
+ Additionally, it has a couple other restrictions that the Sequel forme_set plugin
816
+ does not have.
817
+
818
+ First, the Roda forme_set plugin only handles a single object in forms,
819
+ which must be provided when creating the form. It does not handle multiple
820
+ objects in the same form, and ignores any fields set for an object different
821
+ from the one passed when creating the form. You can use the Sequel forme_set
822
+ plugin to handle form submissions involving multiple objects, or for the
823
+ objects that were not passed when creating the form.
824
+
825
+ Second, the Roda forme_set plugin does not handle cases where the field values
826
+ are placed outside the forms default namespace. The Sequel forme_set plugin
827
+ can handle those issues, as long as all values are in the same namespace, since
828
+ the Sequel forme_set plugin requires you pass in the specific hash to use (the
829
+ Roda forme_set plugin use the form's namespace information and the submitted
830
+ parameters to determine the hash to use).
831
+
832
+ In cases where the Roda forme_set does not handle things correctly, you can use
833
+ forme_parse, which will return metadata in the same format as the Sequel plugin
834
+ forme_parse method, with the addition of a :form_version key in the hash for the
835
+ form version.
836
+
837
+ It is possible to use the Roda forme_set plugin for the submissions it can handle, the
838
+ Sequel forme_set plugin for the submissions it can handle, and set other fields manually
839
+ using the Sequel set_fields methods.
840
+
841
+ Note that when using the Roda forme_set plugin with an existing form, you should first
842
+ enable the Roda plugin without actually using the Roda forme_set method. Do not
843
+ start using the Roda forme_set method until all currently valid sessions were
844
+ established after the Roda forme_set plugin was enabled. Otherwise, sessions that
845
+ access the form before the Roda forme_set plugin was enabled will not work if they
846
+ submit the form after the Roda forme_set plugin is enabled.
847
+
689
848
  == Other Sequel Plugins
690
849
 
691
850
  In addition to the Sequel plugins mentioned above, Forme also ships with additional Sequel
@@ -695,9 +854,9 @@ forme_i18n :: Handles translations for labels using i18n.
695
854
 
696
855
  = Roda Support
697
856
 
698
- Forme ships with two Roda plugins, forme and forme_route_csrf. For new code, it is
699
- recommended to use forme_route_csrf, as that uses Roda's route_csrf plugin, which
700
- supports more secure request-specific CSRF tokens. In both cases, usage in ERB
857
+ Forme ships with three Roda plugins: forme_set (discussed above), forme, and forme_route_csrf.
858
+ For new code, it is recommended to use forme_route_csrf, as that uses Roda's route_csrf
859
+ plugin, which supports more secure request-specific CSRF tokens. In both cases, usage in ERB
701
860
  templates is the same:
702
861
 
703
862
  <% form(@obj, :action=>'/foo') do |f| %>
@@ -780,6 +939,7 @@ These options are supported by all of the input types:
780
939
  :disabled :: Set the disabled attribute if true
781
940
  :error :: Set an error message, invoking the error_handler
782
941
  :error_handler :: Set a custom error_handler, overriding the form's default
942
+ :help :: Set help text to use, invoking the helper
783
943
  :helper :: Set a custom helper, overriding the form's default
784
944
  :id :: The id attribute to use
785
945
  :key :: The base to use for the name and id attributes, based on the current
@@ -822,8 +982,13 @@ creates multiple select options. Options:
822
982
  for the select field and string to use a string
823
983
  (:date default: <tt>[:year, '-', :month, '-', :day]</tt>)
824
984
  (:datetime default: <tt>[:year, '-', :month, '-', :day, ' ', :hour, ':', :minute, ':', :second]</tt>)
985
+ :select_labels :: The labels to use for the select boxes. Should be a hash keyed by the
986
+ symbol used (e.g. <tt>{:month=>'Month'}</tt>). By default, no labels are used.
825
987
  :select_options :: The options to use for the select boxes. Should be a hash keyed by the
826
- symbol used in order (e.g. <tt>{:year=>1970..2020}</tt>)
988
+ symbol used in order (e.g. <tt>{:year=>1970..2020}</tt>). The values
989
+ can be a number used as both the value and the text of the option or
990
+ an array with two elements, the first of which is the value for the option
991
+ and the second of which is the text for the option.
827
992
 
828
993
  === :select
829
994
 
@@ -843,7 +1008,8 @@ Creates a select tag, containing option tags specified by the :options option.
843
1008
  :options :: An enumerable of options used for creating option tags.
844
1009
  If the :text_method and :value_method are not given and the entry is an
845
1010
  array, uses the first entry of the array as the text of the option, and
846
- the second entry of the array as the value of the option.
1011
+ the last entry of the array as the value of the option. If the last entry
1012
+ of the array is a hash, uses the hash as the attributes for the option.
847
1013
  :selected :: The value that should be selected. Any options that are equal to
848
1014
  this value (or included in this value if a multiple select box),
849
1015
  are set to selected.
@@ -862,6 +1028,7 @@ the following options:
862
1028
 
863
1029
  :tag_wrapper :: The wrapper transformer for individual tags in the set
864
1030
  :tag_labeler :: The labeler transformer for individual tags in the set
1031
+ :tag_label_attr :: The attributes to use for labels for individual tags in the set
865
1032
 
866
1033
  === :radioset
867
1034
 
@@ -980,7 +1147,9 @@ Forme ships with a bunch of built-in transformers that you can use:
980
1147
 
981
1148
  === +error_handler+
982
1149
 
1150
+ :after_legend :: designed for usage with :legend labeler, putting error message after legend, adding error for first input in the set
983
1151
  :default :: modifies tag to add an error class and adds a span with the error message
1152
+ :set :: default error_handler for checkboxset and radioset inputs, that adds an error to the last input in the set
984
1153
 
985
1154
  This supports the following options:
986
1155
 
@@ -998,8 +1167,10 @@ This supports the following options:
998
1167
 
999
1168
  :default :: uses implicit labels, where the tag is a child of the label tag
1000
1169
  :explicit :: uses explicit labels with the for attribute, where tag is a sibling of the label tag
1170
+ :legend :: adds a legend before the tags, mostly useful for accessible checkboxset and radioset inputs
1171
+ :span :: default labeler for checkboxset and radioset inputs that adds a span before the tags
1001
1172
 
1002
- Both of these respect the following options:
1173
+ The :default and :explicit labelers respect the following options:
1003
1174
 
1004
1175
  :label_position :: Can be set to :before or :after to place the label before or after the the input.
1005
1176
  :label_attr :: A hash of attributes to use for the label tag
@@ -1008,6 +1179,7 @@ Both of these respect the following options:
1008
1179
 
1009
1180
  :default :: returns tag without wrapping
1010
1181
  :div :: wraps tag in div tag
1182
+ :fieldset :: wraps tags in a fieldset, mostly useful for accessible checkboxset and radioset inputs
1011
1183
  :fieldset_ol :: same as :li, but also sets +inputs_wrapper+ to :fieldset_ol
1012
1184
  :li :: wraps tag in li tag
1013
1185
  :ol :: same as :li, but also sets +inputs_wrapper+ to :ol
data/lib/forme/bs3.rb CHANGED
@@ -214,7 +214,7 @@ module Forme
214
214
  yield
215
215
  end
216
216
  else
217
- form.tag(:fieldset, attr, &Proc.new)
217
+ form.tag(:fieldset, attr, &block)
218
218
  end
219
219
  end
220
220
  end
data/lib/forme/form.rb CHANGED
@@ -59,7 +59,7 @@ module Forme
59
59
  ins = opts[:inputs]
60
60
  button = opts[:button]
61
61
  if ins || button
62
- block = Proc.new do |form|
62
+ block = proc do |form|
63
63
  form._inputs(ins, opts) if ins
64
64
  yield form if block_given?
65
65
  form.emit(form.button(button)) if button
@@ -11,10 +11,55 @@ module Forme
11
11
 
12
12
  # Return tag with error message span tag after it.
13
13
  def call(tag, input)
14
+ [tag, error_tag(input)]
15
+ end
16
+
17
+ private
18
+
19
+ def error_tag(input)
14
20
  attr = input.opts[:error_attr]
15
21
  attr = attr ? attr.dup : {}
16
22
  Forme.attr_classes(attr, 'error_message')
17
- [tag, input.tag(:span, attr, input.opts[:error])]
23
+
24
+ if id = input.opts[:error_id]
25
+ unless attr['id'] || attr[:id]
26
+ attr['id'] = id
27
+ end
28
+ end
29
+
30
+ input.tag(:span, attr, input.opts[:error])
31
+ end
32
+ end
33
+
34
+ class ErrorHandler::Set < ErrorHandler
35
+ Forme.register_transformer(:error_handler, :set, new)
36
+
37
+ def call(tag, input)
38
+ return super unless last_input = input.opts[:last_input]
39
+
40
+ last_input.opts[:error] = input.opts[:error]
41
+ last_input.opts[:error_attr] = input.opts[:error_attr] if input.opts[:error_attr]
42
+ last_input.opts[:error_handler] = :default
43
+
44
+ tag
45
+ end
46
+ end
47
+
48
+ class ErrorHandler::AfterLegend < ErrorHandler
49
+ Forme.register_transformer(:error_handler, :after_legend, new)
50
+
51
+ def call(tag, input)
52
+ return super unless tag.is_a?(Array)
53
+ return super unless tag.first.is_a?(Tag)
54
+ return super unless tag.first.type == :legend
55
+
56
+ first_input = input.opts[:first_input]
57
+ attr = first_input.opts[:attr] ||= {}
58
+ Forme.attr_classes(attr, 'error')
59
+ attr['aria-invalid'] = 'true'
60
+ attr['aria-describedby'] = input.opts[:error_id] = "#{first_input.opts[:id]}_error_message"
61
+
62
+ tag.insert(1, error_tag(input))
18
63
  end
19
64
  end
20
65
  end
@@ -61,7 +61,6 @@ module Forme
61
61
  @attr = attr ? attr.dup : {}
62
62
  @opts = input.opts
63
63
  normalize_options
64
-
65
64
  tag = if html = input.opts[:html]
66
65
  html = html.call(input) if html.respond_to?(:call)
67
66
  form.raw(html)
@@ -158,9 +157,13 @@ module Forme
158
157
  ops = ops.merge(@opts[:select_options]) if @opts[:select_options]
159
158
  first_input = true
160
159
  format = DATE_SELECT_FORMAT
160
+ @opts[:select_labels] ||= {}
161
161
  order.map do |x|
162
162
  next x if x.is_a?(String)
163
- opts = @opts.merge(:label=>nil, :wrapper=>nil, :error=>nil, :name=>"#{name}[#{x}]", :value=>values[x], :options=>ops[x].map{|y| [sprintf(format, y), y]})
163
+ options = ops[x].map do |value, text|
164
+ [text || sprintf(format, value), value]
165
+ end
166
+ opts = @opts.merge(:label=>@opts[:select_labels][x], :wrapper=>nil, :error=>nil, :name=>"#{name}[#{x}]", :value=>values[x], :options=>options)
164
167
  opts[:id] = if first_input
165
168
  first_input = false
166
169
  id
@@ -186,9 +189,9 @@ module Forme
186
189
  copy_options_to_attributes([:size])
187
190
 
188
191
  os = process_select_optgroups(:_format_select_optgroup) do |label, value, sel, attrs|
189
- if value || sel
190
- attrs = attrs.dup
191
- attrs[:value] = value if value
192
+ if !value.nil? || sel
193
+ attrs = attrs.dup
194
+ attrs[:value] = value unless value.nil?
192
195
  attrs[:selected] = :selected if sel
193
196
  end
194
197
  tag(:option, attrs, [label])
@@ -221,24 +224,25 @@ module Forme
221
224
  key = @opts[:key]
222
225
  name = @opts[:name]
223
226
  id = @opts[:id]
224
- if @opts[:error]
225
- @opts[:set_error] = @opts.delete(:error)
226
- end
227
- if @opts[:label]
228
- @opts[:set_label] = @opts.delete(:label)
229
- end
227
+ @opts[:labeler] ||= :span
228
+ @opts[:error_handler] ||= :set
230
229
 
231
230
  tag_wrapper = Forme.transformer(:tag_wrapper, @opts.delete(:tag_wrapper), @input.form_opts) || :default
232
231
  tag_labeler = Forme.transformer(:labeler, @opts.delete(:tag_labeler), @input.form_opts) || :default
233
232
  wrapper = @opts.fetch(:wrapper){@opts[:wrapper] = @input.form_opts[:set_wrapper] || @input.form_opts[:wrapper]}
234
233
  wrapper = Forme.transformer(:wrapper, wrapper)
234
+ tag_label_attr = @opts[:tag_label_attr] || @opts[:label_attr]
235
235
 
236
- tags = process_select_optgroups(:_format_set_optgroup) do |label, value, sel, attrs|
237
- value ||= label
236
+ first_input = nil
237
+ last_input = nil
238
+ ret = process_select_optgroups(:_format_set_optgroup) do |label, value, sel, attrs|
239
+ value = label if value.nil?
238
240
  label_attr = {:class=>:option}
239
- label_attr.merge!(@opts[:label_attr]) if @opts[:label_attr]
240
- r_opts = attrs.merge(tag_attrs).merge(:label=>label||value, :label_attr=>label_attr, :wrapper=>tag_wrapper, :labeler=>tag_labeler)
241
- r_opts[:value] ||= value if value
241
+ label_attr.merge!(tag_label_attr) if tag_label_attr
242
+ r_opts = attrs.merge(tag_attrs).merge(:label=>label||value, :label_attr=>label_attr, :wrapper=>tag_wrapper, :labeler=>tag_labeler, :error=>nil, :error_attr=>nil)
243
+ if r_opts[:value].nil?
244
+ r_opts[:value] = value unless value.nil?
245
+ end
242
246
  r_opts[:checked] ||= :checked if sel
243
247
  r_opts[:formatter] = @opts[:formatter] if @opts[:formatter]
244
248
 
@@ -253,31 +257,15 @@ module Forme
253
257
  r_opts[:key_id] ||= value
254
258
  end
255
259
 
256
- form._input(type, r_opts)
260
+ input = form._input(type, r_opts)
261
+ first_input ||= input
262
+ last_input = input
257
263
  end
258
264
 
259
- if @opts[:set_error]
260
- _add_set_error(tags)
261
- end
262
-
263
- tags.unshift(set_label) if @opts[:set_label]
264
-
265
- tags
266
- end
265
+ @opts[:first_input] = first_input
266
+ @opts[:last_input] = last_input
267
267
 
268
- def set_label
269
- form._tag(:span, {:class=>:label}, @opts[:set_label])
270
- end
271
-
272
- def _add_set_error(tags)
273
- if (last_input = tags.last) && last_input.is_a?(Input)
274
- last_input.opts[:error] = @opts[:set_error]
275
- last_input.opts[:error_attr] = @opts[:error_attr] if @opts[:error_attr]
276
- else
277
- attr = @opts[:error_attr] || {}
278
- Forme.attr_classes(attr, 'error_message')
279
- tags << form._tag(:span, attr, [@opts[:set_error]])
280
- end
268
+ ret
281
269
  end
282
270
 
283
271
  # Formats a textarea. Respects the following options:
@@ -319,7 +307,19 @@ module Forme
319
307
  handle_errors_option
320
308
 
321
309
  Forme.attr_classes(@attr, @opts[:class]) if @opts.has_key?(:class)
322
- Forme.attr_classes(@attr, 'error') if @opts[:error]
310
+
311
+ if @opts[:error]
312
+ Forme.attr_classes(@attr, 'error')
313
+ @attr["aria-invalid"] = "true"
314
+ if @opts.fetch(:error_handler, true)
315
+ unless @opts[:error_id]
316
+ if id = @attr[:id] || @attr['id']
317
+ error_id = @attr['aria-describedby'] ||= "#{id}_error_message"
318
+ @opts[:error_id] = error_id
319
+ end
320
+ end
321
+ end
322
+ end
323
323
 
324
324
  if data = opts[:data]
325
325
  data.each do |k, v|
@@ -462,7 +462,7 @@ module Forme
462
462
  text = x
463
463
  end
464
464
 
465
- yield [text, val, val ? cmp.call(val) : cmp.call(text), attr]
465
+ yield [text, val, !val.nil? ? cmp.call(val) : cmp.call(text), attr]
466
466
  end
467
467
  end
468
468
  end
@@ -538,8 +538,11 @@ module Forme
538
538
  end
539
539
 
540
540
  # Use a span with text instead of an input field.
541
+ # For hidden inputs, do not show anything
541
542
  def _format_input(type)
542
- tag(:span, {}, @attr[:value])
543
+ unless type.to_s == 'hidden'
544
+ tag(:span, {'class'=>'readonly-text'}, @attr[:value])
545
+ end
543
546
  end
544
547
 
545
548
  # Disabled radio button inputs.
@@ -560,9 +563,20 @@ module Forme
560
563
  ''
561
564
  end
562
565
 
563
- # Use a span with text instead of a text area.
566
+ # Format the text as separate paragraphs.
564
567
  def format_textarea
565
- tag(:span, {}, @attr[:value])
568
+ text = @attr[:value]
569
+ case text
570
+ when nil, Forme::Raw
571
+ # nothing
572
+ when String
573
+ text = text.gsub(/\A[\r\n]+|[\r\n]+\z/, '').split(/(?:\r?\n)(?:\r?\n)+/).map do |t|
574
+ t = Forme.h(t)
575
+ t.gsub!(/\r?\n/, "<br />")
576
+ tag(:p, {}, Forme.raw(t))
577
+ end
578
+ end
579
+ tag(:div, {'class'=>'readonly-textarea'}, text)
566
580
  end
567
581
  end
568
582
  end