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 +4 -4
- data/CHANGELOG +50 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +181 -9
- data/lib/forme/bs3.rb +1 -1
- data/lib/forme/form.rb +1 -1
- data/lib/forme/transformers/error_handler.rb +46 -1
- data/lib/forme/transformers/formatter.rb +57 -43
- data/lib/forme/transformers/inputs_wrapper.rb +2 -2
- data/lib/forme/transformers/labeler.rb +19 -0
- data/lib/forme/transformers/serializer.rb +1 -12
- data/lib/forme/transformers/wrapper.rb +1 -1
- data/lib/forme/version.rb +1 -1
- data/lib/forme.rb +27 -0
- data/lib/roda/plugins/forme_route_csrf.rb +15 -1
- data/lib/roda/plugins/forme_set.rb +214 -0
- data/lib/sequel/plugins/forme.rb +10 -5
- data/lib/sequel/plugins/forme_set.rb +50 -28
- data/spec/bs3_reference_spec.rb +4 -4
- data/spec/bs3_sequel_plugin_spec.rb +45 -45
- data/spec/bs3_spec.rb +1 -1
- data/spec/erb_helper.rb +2 -2
- data/spec/forme_spec.rb +75 -18
- data/spec/rails_integration_spec.rb +43 -26
- data/spec/roda_integration_spec.rb +357 -1
- data/spec/sequel_i18n_plugin_spec.rb +5 -4
- data/spec/sequel_plugin_spec.rb +61 -54
- data/spec/sequel_set_plugin_spec.rb +63 -14
- data/spec/spec_helper.rb +2 -2
- metadata +25 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9ee8a7a7efaf9ff59c55404e6c11aba0125cdf59972366b94aa3359b06c6c1f
|
4
|
+
data.tar.gz: dd9f90ad552c827b66a3d8146f8933e5258b34ece1db5a8a9515ac9e2aeada44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
-
|
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
|
699
|
-
recommended to use forme_route_csrf, as that uses Roda's route_csrf
|
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
|
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
|
-
|
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
data/lib/forme/form.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
191
|
-
attrs[:value] = 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
|
-
|
225
|
-
|
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
|
-
|
237
|
-
|
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!(
|
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]
|
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
|
-
|
260
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
566
|
+
# Format the text as separate paragraphs.
|
564
567
|
def format_textarea
|
565
|
-
|
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
|