formtastic 1.0.0.beta → 1.0.0.beta2
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/README.textile +4 -0
- data/lib/formtastic.rb +22 -11
- data/lib/formtastic/util.rb +5 -2
- data/spec/input_spec.rb +17 -0
- data/spec/inputs/check_boxes_input_spec.rb +25 -2
- data/spec/inputs/radio_input_spec.rb +24 -0
- data/spec/label_spec.rb +39 -0
- data/spec/spec_helper.rb +4 -0
- metadata +3 -3
data/README.textile
CHANGED
@@ -517,7 +517,11 @@ If you want to add your own input types to encapsulate your own logic or interfa
|
|
517
517
|
|
518
518
|
@Formtastic::SemanticFormHelper.builder = MyCustomBuilder@
|
519
519
|
|
520
|
+
h2. Security
|
520
521
|
|
522
|
+
By default formtastic escapes html entities in both labels and hints unless a string is marked as html_safe. If you are using an older rails version which doesn't know html_safe, or you want to globally turn this feature off, you can set the following in your initializer:
|
523
|
+
|
524
|
+
Formtastic::SemanticFormBuilder.escape_html_entities_in_hints_and_labels = false
|
521
525
|
|
522
526
|
|
523
527
|
h2. Status
|
data/lib/formtastic.rb
CHANGED
@@ -19,11 +19,12 @@ module Formtastic #:nodoc:
|
|
19
19
|
@@file_methods = [ :file?, :public_filename, :filename ]
|
20
20
|
@@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
|
21
21
|
@@i18n_lookups_by_default = false
|
22
|
+
@@escape_html_entities_in_hints_and_labels = true
|
22
23
|
@@default_commit_button_accesskey = nil
|
23
24
|
|
24
25
|
cattr_accessor :default_text_field_size, :default_text_area_height, :all_fields_required_by_default, :include_blank_for_select_by_default,
|
25
26
|
:required_string, :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
|
26
|
-
:inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :default_commit_button_accesskey
|
27
|
+
:inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :escape_html_entities_in_hints_and_labels, :default_commit_button_accesskey
|
27
28
|
|
28
29
|
RESERVED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
|
29
30
|
|
@@ -478,7 +479,7 @@ module Formtastic #:nodoc:
|
|
478
479
|
# Collects association columns (relation columns) for the current form object class.
|
479
480
|
#
|
480
481
|
def association_columns(*by_associations) #:nodoc:
|
481
|
-
if @object.present?
|
482
|
+
if @object.present? && @object.class.respond_to?(:reflections)
|
482
483
|
@object.class.reflections.collect do |name, _|
|
483
484
|
if by_associations.present?
|
484
485
|
name if by_associations.include?(_.macro)
|
@@ -878,7 +879,7 @@ module Formtastic #:nodoc:
|
|
878
879
|
html_options[:checked] = selected_value == value if selected_option_is_present
|
879
880
|
|
880
881
|
li_content = template.content_tag(:label,
|
881
|
-
Formtastic::Util.html_safe("#{self.radio_button(input_name, value, html_options)} #{label}"),
|
882
|
+
Formtastic::Util.html_safe("#{self.radio_button(input_name, value, html_options)} #{escape_html_entities(label)}"),
|
882
883
|
:for => input_id
|
883
884
|
)
|
884
885
|
|
@@ -888,9 +889,9 @@ module Formtastic #:nodoc:
|
|
888
889
|
|
889
890
|
template.content_tag(:fieldset,
|
890
891
|
template.content_tag(:legend,
|
891
|
-
template.label_tag(nil, localized_string(method,
|
892
|
+
template.label_tag(nil, localized_string(method, options[:label], :label) || humanized_attribute_name(method), :for => nil), :class => :label
|
892
893
|
) <<
|
893
|
-
template.content_tag(:ol, list_item_content)
|
894
|
+
template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
|
894
895
|
)
|
895
896
|
end
|
896
897
|
alias :boolean_radio_input :radio_input
|
@@ -1149,7 +1150,7 @@ module Formtastic #:nodoc:
|
|
1149
1150
|
html_options[:id] = input_id
|
1150
1151
|
|
1151
1152
|
li_content = template.content_tag(:label,
|
1152
|
-
Formtastic::Util.html_safe("#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}"),
|
1153
|
+
Formtastic::Util.html_safe("#{self.check_box(input_name, html_options, value, unchecked_value)} #{escape_html_entities(label)}"),
|
1153
1154
|
:for => input_id
|
1154
1155
|
)
|
1155
1156
|
|
@@ -1159,9 +1160,9 @@ module Formtastic #:nodoc:
|
|
1159
1160
|
|
1160
1161
|
template.content_tag(:fieldset,
|
1161
1162
|
template.content_tag(:legend,
|
1162
|
-
template.label_tag(nil, localized_string(method,
|
1163
|
+
template.label_tag(nil, localized_string(method, options[:label], :label) || humanized_attribute_name(method), :for => nil), :class => :label
|
1163
1164
|
) <<
|
1164
|
-
template.content_tag(:ol, list_item_content)
|
1165
|
+
template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
|
1165
1166
|
)
|
1166
1167
|
end
|
1167
1168
|
|
@@ -1223,7 +1224,7 @@ module Formtastic #:nodoc:
|
|
1223
1224
|
#
|
1224
1225
|
def inline_hints_for(method, options) #:nodoc:
|
1225
1226
|
options[:hint] = localized_string(method, options[:hint], :hint)
|
1226
|
-
return if options[:hint].blank?
|
1227
|
+
return if options[:hint].blank? or options[:hint].kind_of? Hash
|
1227
1228
|
template.content_tag(:p, Formtastic::Util.html_safe(options[:hint]), :class => 'inline-hints')
|
1228
1229
|
end
|
1229
1230
|
|
@@ -1401,7 +1402,8 @@ module Formtastic #:nodoc:
|
|
1401
1402
|
|
1402
1403
|
# Return if we have an Array of strings, fixnums or arrays
|
1403
1404
|
return collection if (collection.instance_of?(Array) || collection.instance_of?(Range)) &&
|
1404
|
-
[Array, Fixnum, String, Symbol].include?(collection.first.class)
|
1405
|
+
[Array, Fixnum, String, Symbol].include?(collection.first.class) &&
|
1406
|
+
!(options.include?(:label_method) || options.include?(:value_method))
|
1405
1407
|
|
1406
1408
|
label, value = detect_label_and_value_method!(collection, options)
|
1407
1409
|
collection.map { |o| [send_or_call(label, o), send_or_call(value, o)] }
|
@@ -1622,7 +1624,7 @@ module Formtastic #:nodoc:
|
|
1622
1624
|
key = value if value.is_a?(::Symbol)
|
1623
1625
|
|
1624
1626
|
if value.is_a?(::String)
|
1625
|
-
value
|
1627
|
+
escape_html_entities(value)
|
1626
1628
|
else
|
1627
1629
|
use_i18n = value.nil? ? @@i18n_lookups_by_default : (value != false)
|
1628
1630
|
|
@@ -1644,6 +1646,7 @@ module Formtastic #:nodoc:
|
|
1644
1646
|
|
1645
1647
|
i18n_value = ::Formtastic::I18n.t(defaults.shift,
|
1646
1648
|
options.merge(:default => defaults, :scope => type.to_s.pluralize.to_sym))
|
1649
|
+
i18n_value = escape_html_entities(i18n_value) if i18n_value.is_a?(::String)
|
1647
1650
|
i18n_value.blank? ? nil : i18n_value
|
1648
1651
|
end
|
1649
1652
|
end
|
@@ -1676,6 +1679,14 @@ module Formtastic #:nodoc:
|
|
1676
1679
|
options
|
1677
1680
|
end
|
1678
1681
|
|
1682
|
+
def escape_html_entities(string) #:nodoc:
|
1683
|
+
if @@escape_html_entities_in_hints_and_labels
|
1684
|
+
# Acceppt html_safe flag as indicator to skip escaping
|
1685
|
+
string = template.escape_once(string) unless string.respond_to?(:html_safe?) && string.html_safe? == true
|
1686
|
+
end
|
1687
|
+
string
|
1688
|
+
end
|
1689
|
+
|
1679
1690
|
end
|
1680
1691
|
|
1681
1692
|
# Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
|
data/lib/formtastic/util.rb
CHANGED
@@ -18,8 +18,11 @@ module Formtastic
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def rails_safe_buffer_class
|
21
|
-
|
22
|
-
|
21
|
+
# It's important that we check ActiveSupport first,
|
22
|
+
# because in Rails 2.3.6 ActionView::SafeBuffer exists
|
23
|
+
# but is a deprecated proxy object.
|
24
|
+
return ActiveSupport::SafeBuffer if defined?(ActiveSupport::SafeBuffer)
|
25
|
+
return ActionView::SafeBuffer
|
23
26
|
end
|
24
27
|
|
25
28
|
end
|
data/spec/input_spec.rb
CHANGED
@@ -571,6 +571,23 @@ describe 'SemanticFormBuilder#input' do
|
|
571
571
|
end
|
572
572
|
end
|
573
573
|
|
574
|
+
describe 'when localized hint (I18n) is a model with attribute hints' do
|
575
|
+
it "should see the provided hash as a blank entry" do
|
576
|
+
::I18n.backend.store_translations :en,
|
577
|
+
:formtastic => {
|
578
|
+
:hints => {
|
579
|
+
:title => { # movie title
|
580
|
+
:summary => @localized_hint_text # summary of movie
|
581
|
+
}
|
582
|
+
}
|
583
|
+
}
|
584
|
+
semantic_form_for(@new_post) do |builder|
|
585
|
+
concat(builder.input(:title, :hint => true))
|
586
|
+
end
|
587
|
+
output_buffer.should_not have_tag('form li p.inline-hints', @localized_hint_text)
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
574
591
|
describe 'when localized hint (I18n) is not provided' do
|
575
592
|
it 'should not render a hint paragraph' do
|
576
593
|
semantic_form_for(@new_post) do |builder|
|
@@ -104,6 +104,16 @@ describe 'check_boxes input' do
|
|
104
104
|
output_buffer.should have_tag("form li fieldset ol li label input[@name='project[author_id][]']")
|
105
105
|
end
|
106
106
|
end
|
107
|
+
|
108
|
+
it 'should html escape the label string' do
|
109
|
+
output_buffer.replace ''
|
110
|
+
semantic_form_for(:project, :url => 'http://test.host') do |builder|
|
111
|
+
concat(builder.input(:author_id, :as => :check_boxes, :collection => [["<b>Item 1</b>", 1], ["<b>Item 2</b>", 2]]))
|
112
|
+
end
|
113
|
+
output_buffer.should have_tag('form li fieldset ol li label') do |label|
|
114
|
+
label.body.should match /<b>Item [12]<\/b>$/
|
115
|
+
end
|
116
|
+
end
|
107
117
|
end
|
108
118
|
|
109
119
|
describe 'when :selected is set' do
|
@@ -236,6 +246,7 @@ describe 'check_boxes input' do
|
|
236
246
|
|
237
247
|
before do
|
238
248
|
::I18n.backend.store_translations :en, :formtastic => { :labels => { :post => { :authors => "Translated!" }}}
|
249
|
+
Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
|
239
250
|
|
240
251
|
@new_post.stub!(:author_ids).and_return(nil)
|
241
252
|
semantic_form_for(@new_post) do |builder|
|
@@ -245,6 +256,7 @@ describe 'check_boxes input' do
|
|
245
256
|
|
246
257
|
after do
|
247
258
|
::I18n.backend.reload!
|
259
|
+
Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
|
248
260
|
end
|
249
261
|
|
250
262
|
it "should do foo" do
|
@@ -252,8 +264,19 @@ describe 'check_boxes input' do
|
|
252
264
|
end
|
253
265
|
|
254
266
|
end
|
255
|
-
|
256
|
-
end
|
257
267
|
|
268
|
+
describe "when :label option is set" do
|
269
|
+
before do
|
270
|
+
@new_post.stub!(:author_ids).and_return(nil)
|
271
|
+
semantic_form_for(@new_post) do |builder|
|
272
|
+
concat(builder.input(:authors, :as => :check_boxes, :label => 'The authors'))
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should output the correct label title" do
|
277
|
+
output_buffer.should have_tag("legend.label label", /The authors/)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
258
281
|
end
|
259
282
|
|
@@ -111,6 +111,16 @@ describe 'radio input' do
|
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
+
it 'should html escape the label string' do
|
115
|
+
output_buffer.replace ''
|
116
|
+
semantic_form_for(:project, :url => 'http://test.host') do |builder|
|
117
|
+
concat(builder.input(:author_id, :as => :radio, :collection => [["<b>Item 1</b>", 1], ["<b>Item 2</b>", 2]]))
|
118
|
+
end
|
119
|
+
output_buffer.should have_tag('form li fieldset ol li label') do |label|
|
120
|
+
label.body.should match /<b>Item [12]<\/b>$/
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
114
124
|
it 'should generate inputs for each item' do
|
115
125
|
::Author.find(:all).each do |author|
|
116
126
|
output_buffer.should have_tag("form li fieldset ol li label input#project_author_id_#{author.id}")
|
@@ -167,6 +177,7 @@ describe 'radio input' do
|
|
167
177
|
before do
|
168
178
|
::I18n.backend.store_translations :en, :formtastic => { :labels => { :post => { :authors => "Translated!" }}}
|
169
179
|
|
180
|
+
Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
|
170
181
|
@new_post.stub!(:author_ids).and_return(nil)
|
171
182
|
semantic_form_for(@new_post) do |builder|
|
172
183
|
concat(builder.input(:authors, :as => :radio))
|
@@ -175,6 +186,7 @@ describe 'radio input' do
|
|
175
186
|
|
176
187
|
after do
|
177
188
|
::I18n.backend.reload!
|
189
|
+
Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
|
178
190
|
end
|
179
191
|
|
180
192
|
it "should do foo" do
|
@@ -183,4 +195,16 @@ describe 'radio input' do
|
|
183
195
|
|
184
196
|
end
|
185
197
|
|
198
|
+
describe "when :label option is set" do
|
199
|
+
before do
|
200
|
+
@new_post.stub!(:author_ids).and_return(nil)
|
201
|
+
@form = semantic_form_for(@new_post) do |builder|
|
202
|
+
concat(builder.input(:authors, :as => :radio, :label => 'The authors'))
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should output the correct label title" do
|
207
|
+
output_buffer.should have_tag("legend.label label", /The authors/)
|
208
|
+
end
|
209
|
+
end
|
186
210
|
end
|
data/spec/label_spec.rb
CHANGED
@@ -30,6 +30,24 @@ describe 'SemanticFormBuilder#label' do
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
describe 'when a collection is given' do
|
34
|
+
it 'should use a supplied label_method for simple collections' do
|
35
|
+
semantic_form_for(:project, :url => 'http://test.host') do |builder|
|
36
|
+
concat(builder.input(:author_id, :as => :check_boxes, :collection => [:a, :b, :c], :value_method => :to_s, :label_method => proc {|f| ('Label_%s' % [f])}))
|
37
|
+
end
|
38
|
+
output_buffer.should have_tag('form li fieldset ol li label', :with => /Label_[abc]/, :count => 3)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should use a supplied value_method for simple collections' do
|
42
|
+
semantic_form_for(:project, :url => 'http://test.host') do |builder|
|
43
|
+
concat(builder.input(:author_id, :as => :check_boxes, :collection => [:a, :b, :c], :value_method => proc {|f| ('Value_%s' % [f.to_s])}))
|
44
|
+
end
|
45
|
+
output_buffer.should have_tag('form li fieldset ol li label input[value="Value_a"]')
|
46
|
+
output_buffer.should have_tag('form li fieldset ol li label input[value="Value_b"]')
|
47
|
+
output_buffer.should have_tag('form li fieldset ol li label input[value="Value_c"]')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
33
51
|
describe 'when label is given' do
|
34
52
|
it 'should allow the text to be given as label option' do
|
35
53
|
semantic_form_for(@new_post) do |builder|
|
@@ -42,6 +60,27 @@ describe 'SemanticFormBuilder#label' do
|
|
42
60
|
builder.label(:login, :label => false).should be_blank
|
43
61
|
end
|
44
62
|
end
|
63
|
+
|
64
|
+
it 'should html escape the label string by default' do
|
65
|
+
semantic_form_for(@new_post) do |builder|
|
66
|
+
builder.label(:login, :required => false, :label => '<b>My label</b>').should == "<label for=\"post_login\"><b>My label</b></label>"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should not html escape the label if configured that way' do
|
71
|
+
::Formtastic::SemanticFormBuilder.escape_html_entities_in_hints_and_labels = false
|
72
|
+
semantic_form_for(@new_post) do |builder|
|
73
|
+
builder.label(:login, :required => false, :label => '<b>My label</b>').should == "<label for=\"post_login\"><b>My label</b></label>"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should not html escape the label string for html_safe strings' do
|
78
|
+
::Formtastic::SemanticFormBuilder.escape_html_entities_in_hints_and_labels = true
|
79
|
+
semantic_form_for(@new_post) do |builder|
|
80
|
+
builder.label(:login, :required => false, :label => '<b>My label</b>'.html_safe).should == "<label for=\"post_login\"><b>My label</b></label>"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
45
84
|
end
|
46
85
|
|
47
86
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
require 'rubygems'
|
3
3
|
|
4
|
+
gem 'i18n', '< 0.4'
|
5
|
+
|
4
6
|
gem 'activesupport', '2.3.8'
|
5
7
|
gem 'actionpack', '2.3.8'
|
6
8
|
require 'active_support'
|
@@ -8,6 +10,8 @@ require 'action_pack'
|
|
8
10
|
require 'action_view'
|
9
11
|
require 'action_controller'
|
10
12
|
|
13
|
+
|
14
|
+
|
11
15
|
gem 'rspec', '>= 1.2.6'
|
12
16
|
gem 'rspec-rails', '>= 1.2.6'
|
13
17
|
gem 'hpricot', '>= 0.6.1'
|
metadata
CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
|
|
6
6
|
- 1
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.0.
|
9
|
+
- beta2
|
10
|
+
version: 1.0.0.beta2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Justin French
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-07-21 00:00:00 +10:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|