padrino-helpers 0.9.20 → 0.9.21
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -0
- data/lib/padrino-helpers.rb +1 -0
- data/lib/padrino-helpers/form_builder/abstract_form_builder.rb +84 -24
- data/lib/padrino-helpers/form_helpers.rb +20 -14
- data/lib/padrino-helpers/locale/zh_tw.yml +103 -0
- data/lib/padrino-helpers/output_helpers.rb +21 -68
- data/lib/padrino-helpers/output_helpers/abstract_handler.rb +96 -0
- data/lib/padrino-helpers/output_helpers/erb_handler.rb +80 -0
- data/lib/padrino-helpers/output_helpers/haml_handler.rb +54 -0
- data/test/fixtures/markup_app/app.rb +13 -0
- data/test/fixtures/markup_app/views/fields_for.erb +12 -0
- data/test/fixtures/markup_app/views/fields_for.haml +10 -1
- data/test/helper.rb +8 -0
- data/test/test_form_builder.rb +144 -4
- data/test/test_form_helpers.rb +32 -4
- metadata +13 -9
data/README.rdoc
CHANGED
@@ -152,6 +152,8 @@ A form_for using these basic fields might look like:
|
|
152
152
|
%p
|
153
153
|
= f.submit "Create", :class => 'button'
|
154
154
|
|
155
|
+
Forms can also accept nested attributes using `fields_for` within the form builder in recent releases. Check out the guide for {Padrino Helpers}[http://www.padrinorb.com/guides/application-helpers] to learn more about nested forms.
|
156
|
+
|
155
157
|
There is also an additional StandardFormBuilder which builds on the abstract fields that can be used within a form_for.
|
156
158
|
|
157
159
|
A form_for using these standard fields might be:
|
data/lib/padrino-helpers.rb
CHANGED
@@ -4,9 +4,10 @@ module Padrino
|
|
4
4
|
class AbstractFormBuilder #:nodoc:
|
5
5
|
attr_accessor :template, :object
|
6
6
|
|
7
|
-
def initialize(template, object)
|
7
|
+
def initialize(template, object, options={})
|
8
8
|
@template = template
|
9
9
|
@object = build_object(object)
|
10
|
+
@options = options
|
10
11
|
raise "FormBuilder template must be initialized!" unless template
|
11
12
|
raise "FormBuilder object must be not be nil value. If there's no object, use a symbol instead! (i.e :user)" unless object
|
12
13
|
end
|
@@ -19,7 +20,7 @@ module Padrino
|
|
19
20
|
|
20
21
|
# f.error_message_on(field)
|
21
22
|
def error_message_on(field, options={})
|
22
|
-
@template.error_message_on(
|
23
|
+
@template.error_message_on(object, field, options)
|
23
24
|
end
|
24
25
|
|
25
26
|
# f.label :username, :caption => "Nickname"
|
@@ -91,57 +92,105 @@ module Padrino
|
|
91
92
|
@template.submit_tag caption, options
|
92
93
|
end
|
93
94
|
|
94
|
-
# f.
|
95
|
+
# f.image_submit "buttons/submit.png", :class => 'large'
|
95
96
|
def image_submit(source, options={})
|
96
97
|
@template.image_submit_tag source, options
|
97
98
|
end
|
98
99
|
|
100
|
+
# Supports nested fields for a child model within a form
|
101
|
+
# f.fields_for :addresses
|
102
|
+
# f.fields_for :addresses, address
|
103
|
+
# f.fields_for :addresses, @addresses
|
104
|
+
def fields_for(child_association, instance_or_collection=nil, &block)
|
105
|
+
default_collection = self.object.send(child_association)
|
106
|
+
include_index = default_collection.respond_to?(:each)
|
107
|
+
nested_options = { :parent => self, :association => child_association }
|
108
|
+
nested_objects = instance_or_collection ? Array(instance_or_collection) : Array(default_collection)
|
109
|
+
result = nested_objects.each_with_index.map do |child_instance, index|
|
110
|
+
nested_options[:index] = include_index ? index : nil
|
111
|
+
@template.fields_for(child_instance, { :nested => nested_options }, &block)
|
112
|
+
end.join("\n")
|
113
|
+
end
|
114
|
+
|
99
115
|
protected
|
100
116
|
# Returns the known field types for a formbuilder
|
101
117
|
def self.field_types
|
102
118
|
[:hidden_field, :text_field, :text_area, :password_field, :file_field, :radio_button, :check_box, :select]
|
103
119
|
end
|
104
120
|
|
105
|
-
# Returns the object's models name
|
106
|
-
# => user_assignment
|
107
|
-
def object_name
|
108
|
-
object.is_a?(Symbol) ? object : object.class.to_s.underscore.gsub('/', '-')
|
109
|
-
end
|
110
|
-
|
111
121
|
# Returns true if the value matches the value in the field
|
112
122
|
# field_has_value?(:gender, 'male')
|
113
123
|
def values_matches_field?(field, value)
|
114
124
|
value.present? && (field_value(field).to_s == value.to_s || field_value(field).to_s == 'true')
|
115
125
|
end
|
116
126
|
|
117
|
-
# Returns the value for the object's field
|
118
|
-
# field_value(:username) => "Joey"
|
119
|
-
def field_value(field)
|
120
|
-
@object && @object.respond_to?(field) ? @object.send(field) : ""
|
121
|
-
end
|
122
|
-
|
123
127
|
# Add a :invalid css class to the field if it contain an error
|
124
128
|
def field_error(field, options)
|
125
129
|
error = @object.errors[field] rescue nil
|
126
130
|
error.blank? ? options[:class] : [options[:class], :invalid].flatten.compact.join(" ")
|
127
131
|
end
|
128
132
|
|
129
|
-
# Returns the name for the given field
|
130
|
-
# field_name(:username) => "user[username]"
|
131
|
-
def field_name(field)
|
132
|
-
"#{object_name}[#{field}]"
|
133
|
-
end
|
134
|
-
|
135
133
|
# Returns the human name of the field. Look that use builtin I18n.
|
136
134
|
def field_human_name(field)
|
137
|
-
I18n.translate("#{
|
135
|
+
I18n.translate("#{object_model_name}.attributes.#{field}", :count => 1, :default => field.to_s.humanize, :scope => :models)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the name for the given field
|
139
|
+
# field_name(:username) => "user[username]"
|
140
|
+
# field_name(:number) => "user[telephone_attributes][number]"
|
141
|
+
# field_name(:street) => "user[addresses_attributes][0][street]"
|
142
|
+
def field_name(field=nil)
|
143
|
+
result = []
|
144
|
+
if root_form?
|
145
|
+
result << object_model_name
|
146
|
+
elsif nested_form?
|
147
|
+
parent_form = @options[:nested][:parent]
|
148
|
+
attributes_name = "#{@options[:nested][:association]}_attributes"
|
149
|
+
nested_index = @options[:nested][:index]
|
150
|
+
fragment = [parent_form.field_name, "[#{attributes_name}", "]"]
|
151
|
+
fragment.insert(2, "][#{nested_index}") if nested_index
|
152
|
+
result << fragment
|
153
|
+
end
|
154
|
+
result << "[#{field}]" unless field.blank?
|
155
|
+
result.flatten.join
|
138
156
|
end
|
139
157
|
|
140
158
|
# Returns the id for the given field
|
141
159
|
# field_id(:username) => "user_username"
|
142
160
|
# field_id(:gender, :male) => "user_gender_male"
|
143
|
-
|
144
|
-
|
161
|
+
# field_name(:number) => "user_telephone_attributes_number"
|
162
|
+
# field_name(:street) => "user_addresses_attributes_0_street"
|
163
|
+
def field_id(field=nil, value=nil)
|
164
|
+
result = []
|
165
|
+
if root_form?
|
166
|
+
result << object_model_name
|
167
|
+
elsif nested_form?
|
168
|
+
parent_form = @options[:nested][:parent]
|
169
|
+
attributes_name = "#{@options[:nested][:association]}_attributes"
|
170
|
+
nested_index = @options[:nested][:index]
|
171
|
+
fragment = [parent_form.field_id, "_#{attributes_name}"]
|
172
|
+
fragment.push("_#{nested_index}") if nested_index
|
173
|
+
result << fragment
|
174
|
+
end
|
175
|
+
result << "_#{field}" unless field.blank?
|
176
|
+
result << "_#{value}" unless value.blank?
|
177
|
+
result.flatten.join
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns the child object if it exists
|
181
|
+
def nested_object_id
|
182
|
+
nested_form? && object.respond_to?(:new_record?) && !object.new_record? && object.id
|
183
|
+
end
|
184
|
+
|
185
|
+
# Returns true if this form object is nested in a parent form
|
186
|
+
def nested_form?
|
187
|
+
@options[:nested] && @options[:nested][:parent] && @options[:nested][:parent].respond_to?(:object)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns the value for the object's field
|
191
|
+
# field_value(:username) => "Joey"
|
192
|
+
def field_value(field)
|
193
|
+
@object && @object.respond_to?(field) ? @object.send(field) : ""
|
145
194
|
end
|
146
195
|
|
147
196
|
# explicit_object is either a symbol or a record
|
@@ -150,10 +199,21 @@ module Padrino
|
|
150
199
|
object_or_symbol.is_a?(Symbol) ? @template.instance_variable_get("@#{object_or_symbol}") || object_class(object_or_symbol).new : object_or_symbol
|
151
200
|
end
|
152
201
|
|
202
|
+
# Returns the object's models name
|
203
|
+
# => user_assignment
|
204
|
+
def object_model_name(explicit_object=object)
|
205
|
+
explicit_object.is_a?(Symbol) ? explicit_object : explicit_object.class.to_s.underscore.gsub('/', '-')
|
206
|
+
end
|
207
|
+
|
153
208
|
# Returns the class type for the given object
|
154
209
|
def object_class(explicit_object)
|
155
210
|
explicit_object.is_a?(Symbol) ? explicit_object.to_s.camelize.constantize : explicit_object.class
|
156
211
|
end
|
212
|
+
|
213
|
+
# Returns true if this form is the top-level (not nested)
|
214
|
+
def root_form?
|
215
|
+
!nested_form?
|
216
|
+
end
|
157
217
|
end # AbstractFormBuilder
|
158
218
|
end # FormBuilder
|
159
219
|
end # Helpers
|
@@ -10,8 +10,7 @@ module Padrino
|
|
10
10
|
# form_for @user, '/register', :id => 'register' do |f| ... end
|
11
11
|
#
|
12
12
|
def form_for(object, url, settings={}, &block)
|
13
|
-
|
14
|
-
form_html = capture_html(builder_class.new(self, object), &block)
|
13
|
+
form_html = capture_html(builder_instance(object, settings), &block)
|
15
14
|
form_tag(url, settings) { form_html }
|
16
15
|
end
|
17
16
|
|
@@ -25,8 +24,9 @@ module Padrino
|
|
25
24
|
# fields_for :assignment do |assigment| ... end
|
26
25
|
#
|
27
26
|
def fields_for(object, settings={}, &block)
|
28
|
-
|
29
|
-
fields_html = capture_html(
|
27
|
+
instance = builder_instance(object, settings)
|
28
|
+
fields_html = capture_html(instance, &block)
|
29
|
+
fields_html << instance.hidden_field(:id) if instance.send(:nested_object_id)
|
30
30
|
concat_content fields_html
|
31
31
|
end
|
32
32
|
|
@@ -46,7 +46,7 @@ module Padrino
|
|
46
46
|
options["data-method"] = data_method if data_method
|
47
47
|
options["accept-charset"] ||= "UTF-8"
|
48
48
|
inner_form_html = hidden_form_method_field(desired_method)
|
49
|
-
inner_form_html +=
|
49
|
+
inner_form_html += capture_html(&block)
|
50
50
|
concat_content content_tag('form', inner_form_html, options)
|
51
51
|
end
|
52
52
|
|
@@ -163,6 +163,7 @@ module Padrino
|
|
163
163
|
#
|
164
164
|
# # => <span class="error">can't be blank</div>
|
165
165
|
# error_message_on :post, :title
|
166
|
+
# error_message_on @post, :title
|
166
167
|
#
|
167
168
|
# # => <div class="custom" style="border:1px solid red">can't be blank</div>
|
168
169
|
# error_message_on :post, :title, :tag => :id, :class => :custom, :style => "border:1px solid red"
|
@@ -171,7 +172,7 @@ module Padrino
|
|
171
172
|
# error_message_on :post, :title, :prepend => "This title", :append => "(or it won't work)"
|
172
173
|
#
|
173
174
|
def error_message_on(object, field, options={})
|
174
|
-
object = instance_variable_get("@#{object}")
|
175
|
+
object = object.is_a?(Symbol) ? instance_variable_get("@#{object}") : object
|
175
176
|
error = object.errors[field] rescue nil
|
176
177
|
if error
|
177
178
|
options.reverse_merge!(:tag => :span, :class => :error)
|
@@ -377,17 +378,22 @@ module Padrino
|
|
377
378
|
configured_builder
|
378
379
|
end
|
379
380
|
|
380
|
-
|
381
|
+
##
|
382
|
+
# Returns an initialized builder instance for the given object and settings
|
381
383
|
#
|
384
|
+
# builder_instance(@account, :nested => { ... }) => <FormBuilder>
|
385
|
+
#
|
386
|
+
def builder_instance(object, settings={})
|
387
|
+
builder_class = configured_form_builder_class(settings.delete(:builder))
|
388
|
+
builder_class.new(self, object, settings)
|
389
|
+
end
|
390
|
+
|
391
|
+
##
|
382
392
|
# Returns whether the option should be selected or not
|
383
393
|
#
|
384
|
-
def option_is_selected?(value, caption,
|
385
|
-
|
386
|
-
|
387
|
-
selected.to_s =~ /^(#{value}|#{caption})$/
|
388
|
-
end
|
389
|
-
else
|
390
|
-
selected_value.to_s =~ /^(#{value}|#{caption})$/
|
394
|
+
def option_is_selected?(value, caption, selected_values)
|
395
|
+
Array(selected_values).any? do |selected|
|
396
|
+
[value.to_s, caption.to_s].include?(selected.to_s)
|
391
397
|
end
|
392
398
|
end
|
393
399
|
end # FormHelpers
|
@@ -0,0 +1,103 @@
|
|
1
|
+
zh_tw:
|
2
|
+
number:
|
3
|
+
# Used in number_with_delimiter()
|
4
|
+
# These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
|
5
|
+
format:
|
6
|
+
# Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
|
7
|
+
separator: "."
|
8
|
+
# Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three)
|
9
|
+
delimiter: ","
|
10
|
+
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
|
11
|
+
precision: 3
|
12
|
+
|
13
|
+
# Used in number_to_currency()
|
14
|
+
currency:
|
15
|
+
format:
|
16
|
+
# Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
|
17
|
+
format: "%u %n"
|
18
|
+
unit: "NT$"
|
19
|
+
# These three are to override number.format and are optional
|
20
|
+
separator: "."
|
21
|
+
delimiter: ","
|
22
|
+
precision: 2
|
23
|
+
|
24
|
+
# Used in number_to_percentage()
|
25
|
+
percentage:
|
26
|
+
format:
|
27
|
+
# These three are to override number.format and are optional
|
28
|
+
# separator:
|
29
|
+
delimiter: ""
|
30
|
+
# precision:
|
31
|
+
|
32
|
+
# Used in number_to_precision()
|
33
|
+
precision:
|
34
|
+
format:
|
35
|
+
# These three are to override number.format and are optional
|
36
|
+
# separator:
|
37
|
+
delimiter: ""
|
38
|
+
# precision:
|
39
|
+
|
40
|
+
# Used in number_to_human_size()
|
41
|
+
human:
|
42
|
+
format:
|
43
|
+
# These three are to override number.format and are optional
|
44
|
+
# separator:
|
45
|
+
delimiter: ""
|
46
|
+
precision: 1
|
47
|
+
storage_units:
|
48
|
+
# Storage units output formatting.
|
49
|
+
# %u is the storage unit, %n is the number (default: 2 MB)
|
50
|
+
format: "%n %u"
|
51
|
+
units:
|
52
|
+
byte:
|
53
|
+
one: "Byte"
|
54
|
+
other: "Bytes"
|
55
|
+
kb: "KB"
|
56
|
+
mb: "MB"
|
57
|
+
gb: "GB"
|
58
|
+
tb: "TB"
|
59
|
+
|
60
|
+
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
|
61
|
+
datetime:
|
62
|
+
distance_in_words:
|
63
|
+
half_a_minute: "半分鐘"
|
64
|
+
less_than_x_seconds:
|
65
|
+
one: "不到一秒"
|
66
|
+
other: "不到 %{count} 秒"
|
67
|
+
x_seconds:
|
68
|
+
one: "一秒"
|
69
|
+
other: "%{count} 秒"
|
70
|
+
less_than_x_minutes:
|
71
|
+
one: "不到一分鐘"
|
72
|
+
other: "不到 %{count} 分鐘"
|
73
|
+
x_minutes:
|
74
|
+
one: "一分鐘"
|
75
|
+
other: "%{count} 分鐘"
|
76
|
+
about_x_hours:
|
77
|
+
one: "大約一小時"
|
78
|
+
other: "大約 %{count} 小時"
|
79
|
+
x_days:
|
80
|
+
one: "一天"
|
81
|
+
other: "%{count} 天"
|
82
|
+
about_x_months:
|
83
|
+
one: "大約一個月"
|
84
|
+
other: "大約 %{count} 個月"
|
85
|
+
x_months:
|
86
|
+
one: "一個月"
|
87
|
+
other: "%{count} 個月"
|
88
|
+
about_x_years:
|
89
|
+
one: "大約一年"
|
90
|
+
other: "大約 %{count} 年"
|
91
|
+
over_x_years:
|
92
|
+
one: "一年多"
|
93
|
+
other: "%{count} 年多"
|
94
|
+
almost_x_years:
|
95
|
+
one: "接近一年"
|
96
|
+
other: "接近 %{count} 年"
|
97
|
+
models:
|
98
|
+
errors:
|
99
|
+
template:
|
100
|
+
header:
|
101
|
+
one: "有 1 個錯誤發生使得「%{model}」無法被儲存。"
|
102
|
+
other: "有 %{count} 個錯誤發生使得「%{model}」無法被儲存。"
|
103
|
+
body: "以下欄位發生問題:"
|
@@ -2,21 +2,20 @@ module Padrino
|
|
2
2
|
module Helpers
|
3
3
|
module OutputHelpers
|
4
4
|
##
|
5
|
-
# Captures the html from a block of template code for
|
5
|
+
# Captures the html from a block of template code for any available handler
|
6
6
|
#
|
7
7
|
# ==== Examples
|
8
8
|
#
|
9
9
|
# capture_html(&block) => "...html..."
|
10
10
|
#
|
11
11
|
def capture_html(*args, &block)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
result_text = capture_erb(*args, &block)
|
16
|
-
result_text.present? ? result_text : (block_given? && block.call(*args))
|
17
|
-
else # theres no template to capture, invoke the block directly
|
18
|
-
block.call(*args)
|
12
|
+
handler = self.find_proper_handler
|
13
|
+
if handler && handler.is_type? && handler.block_is_type?(block)
|
14
|
+
captured_html = handler.capture_from_template(*args, &block)
|
19
15
|
end
|
16
|
+
# invoking the block directly if there was no template
|
17
|
+
captured_html = block_given? && block.call(*args) if captured_html.blank?
|
18
|
+
captured_html
|
20
19
|
end
|
21
20
|
|
22
21
|
##
|
@@ -24,28 +23,28 @@ module Padrino
|
|
24
23
|
#
|
25
24
|
# ==== Examples
|
26
25
|
#
|
27
|
-
# concat_content("This will be output to the template buffer
|
26
|
+
# concat_content("This will be output to the template buffer")
|
28
27
|
#
|
29
28
|
def concat_content(text="")
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
erb_concat(text)
|
29
|
+
handler = self.find_proper_handler
|
30
|
+
if handler && handler.is_type?
|
31
|
+
handler.concat_to_template(text)
|
34
32
|
else # theres no template to concat, return the text directly
|
35
33
|
text
|
36
34
|
end
|
37
35
|
end
|
38
36
|
|
39
37
|
##
|
40
|
-
# Returns true if the block is from
|
41
|
-
# Used to determine if html should be returned or
|
38
|
+
# Returns true if the block is from a supported template type; false otherwise.
|
39
|
+
# Used to determine if html should be returned or concatenated to the view
|
42
40
|
#
|
43
41
|
# ==== Examples
|
44
42
|
#
|
45
43
|
# block_is_template?(block)
|
46
44
|
#
|
47
45
|
def block_is_template?(block)
|
48
|
-
|
46
|
+
handler = self.find_proper_handler
|
47
|
+
block && handler && handler.block_is_type?(block)
|
49
48
|
end
|
50
49
|
|
51
50
|
##
|
@@ -81,7 +80,7 @@ module Padrino
|
|
81
80
|
}.join
|
82
81
|
end
|
83
82
|
|
84
|
-
|
83
|
+
protected
|
85
84
|
##
|
86
85
|
# Retrieves content_blocks stored by content_for or within yield_content
|
87
86
|
#
|
@@ -94,61 +93,15 @@ module Padrino
|
|
94
93
|
end
|
95
94
|
|
96
95
|
##
|
97
|
-
#
|
96
|
+
# Retrieves the template handler for the given output context.
|
97
|
+
# Can handle any output related to capturing or concating in a given template.
|
98
98
|
#
|
99
99
|
# ==== Examples
|
100
100
|
#
|
101
|
-
#
|
101
|
+
# find_proper_handler => <OutputHelpers::HamlHandler>
|
102
102
|
#
|
103
|
-
def
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
##
|
108
|
-
# Concats directly to an erb template
|
109
|
-
#
|
110
|
-
# ==== Examples
|
111
|
-
#
|
112
|
-
# erb_concat("Direct to buffer")
|
113
|
-
#
|
114
|
-
def erb_concat(text)
|
115
|
-
@_out_buf << text if has_erb_buffer?
|
116
|
-
end
|
117
|
-
|
118
|
-
##
|
119
|
-
# Returns true if an erb buffer is detected
|
120
|
-
#
|
121
|
-
# ==== Examples
|
122
|
-
#
|
123
|
-
# has_erb_buffer? => true
|
124
|
-
#
|
125
|
-
def has_erb_buffer?
|
126
|
-
!@_out_buf.nil?
|
127
|
-
end
|
128
|
-
|
129
|
-
if RUBY_VERSION < '1.9.0'
|
130
|
-
# Check whether we're called from an erb template.
|
131
|
-
# We'd return a string in any other case, but erb <%= ... %>
|
132
|
-
# can't take an <% end %> later on, so we have to use <% ... %>
|
133
|
-
# and implicitly concat.
|
134
|
-
def block_is_erb?(block)
|
135
|
-
has_erb_buffer? || (block && eval('defined? __in_erb_template', block))
|
136
|
-
end
|
137
|
-
else
|
138
|
-
def block_is_erb?(block)
|
139
|
-
has_erb_buffer? || (block && eval('defined? __in_erb_template', block.binding))
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
##
|
144
|
-
# Used to direct the buffer for the erb capture
|
145
|
-
#
|
146
|
-
def erb_with_output_buffer(buf = '')
|
147
|
-
@_out_buf, old_buffer = buf, @_out_buf
|
148
|
-
yield
|
149
|
-
@_out_buf
|
150
|
-
ensure
|
151
|
-
@_out_buf = old_buffer
|
103
|
+
def find_proper_handler
|
104
|
+
OutputHelpers.handlers.map { |h| h.new(self) }.find { |h| h.is_type? }
|
152
105
|
end
|
153
106
|
end # OutputHelpers
|
154
107
|
end # Helpers
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
module OutputHelpers
|
5
|
+
@template_handlers = []
|
6
|
+
|
7
|
+
##
|
8
|
+
# Returns the list of all available template handlers
|
9
|
+
#
|
10
|
+
# ==== Examples
|
11
|
+
#
|
12
|
+
# OutputHelpers.handlers => [<OutputHelpers::HamlHandler>, <OutputHelpers::ErbHandler>]
|
13
|
+
#
|
14
|
+
def self.handlers
|
15
|
+
@template_handlers
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Registers a new handler as available to the output helpers
|
20
|
+
#
|
21
|
+
# ==== Examples
|
22
|
+
#
|
23
|
+
# OutputHelpers.register(OutputHelpers::HamlHandler)
|
24
|
+
#
|
25
|
+
def self.register(handler)
|
26
|
+
handlers << handler
|
27
|
+
end
|
28
|
+
|
29
|
+
class AbstractHandler
|
30
|
+
attr_reader :template
|
31
|
+
|
32
|
+
def initialize(template)
|
33
|
+
@template = template
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Returns extension of the template
|
38
|
+
#
|
39
|
+
# ==== Examples
|
40
|
+
#
|
41
|
+
# @handler.template_extension => "erubis"
|
42
|
+
#
|
43
|
+
def template_extension
|
44
|
+
caller.find { |c| c =~ /\/views\// }[/\.([\w]*?)\:/, 1] rescue nil
|
45
|
+
# "/some/path/app/views/posts/foo.html.erubis:3:in `evaluate_source'"
|
46
|
+
# => "erubis"
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Returns true if the current template type is same as this handlers; false otherwise.
|
51
|
+
#
|
52
|
+
# ==== Examples
|
53
|
+
#
|
54
|
+
# @handler.is_type? => true
|
55
|
+
#
|
56
|
+
def is_type?
|
57
|
+
# Implemented in subclass
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Returns true if the block given is of the handler's template type; false otherwise.
|
62
|
+
#
|
63
|
+
# ==== Examples
|
64
|
+
#
|
65
|
+
# @handler.block_is_type?(block) => true
|
66
|
+
#
|
67
|
+
def block_is_type?(block)
|
68
|
+
# Implemented in subclass
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Captures the html from a block of template code for this handler
|
73
|
+
#
|
74
|
+
# ==== Examples
|
75
|
+
#
|
76
|
+
# @handler.capture_from_template(&block) => "...html..."
|
77
|
+
#
|
78
|
+
def capture_from_template(*args, &block)
|
79
|
+
# Implemented in subclass
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Outputs the given text to the templates buffer directly
|
84
|
+
#
|
85
|
+
# ==== Examples
|
86
|
+
#
|
87
|
+
# @handler.concat_to_template("This will be output to the template buffer")
|
88
|
+
#
|
89
|
+
def concat_to_template(text="")
|
90
|
+
# Implemented in subclass
|
91
|
+
end
|
92
|
+
end # AbstractHandler
|
93
|
+
end # OutputHelpers
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
module OutputHelpers
|
5
|
+
class ErbHandler < AbstractHandler
|
6
|
+
attr_reader :output_buffer
|
7
|
+
|
8
|
+
def initialize(template)
|
9
|
+
super
|
10
|
+
@output_buffer = template.instance_variable_get(:@_out_buf)
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Returns true if the current template type is same as this handlers; false otherwise.
|
15
|
+
#
|
16
|
+
# ==== Examples
|
17
|
+
#
|
18
|
+
# @handler.is_type? => true
|
19
|
+
#
|
20
|
+
def is_type?
|
21
|
+
self.output_buffer.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Captures the html from a block of template code for this handler
|
25
|
+
#
|
26
|
+
# ==== Examples
|
27
|
+
#
|
28
|
+
# @handler.capture_from_template(&block) => "...html..."
|
29
|
+
#
|
30
|
+
def capture_from_template(*args, &block)
|
31
|
+
erb_with_output_buffer { block_given? && block.call(*args) }
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Outputs the given text to the templates buffer directly
|
36
|
+
#
|
37
|
+
# ==== Examples
|
38
|
+
#
|
39
|
+
# @handler.concat_to_template("This will be output to the template buffer")
|
40
|
+
#
|
41
|
+
def concat_to_template(text="")
|
42
|
+
self.output_buffer << text if self.is_type? && text
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
if RUBY_VERSION < '1.9.0'
|
47
|
+
# Check whether we're called from an erb template.
|
48
|
+
# We'd return a string in any other case, but erb <%= ... %>
|
49
|
+
# can't take an <% end %> later on, so we have to use <% ... %>
|
50
|
+
# and implicitly concat.
|
51
|
+
def block_is_type?(block)
|
52
|
+
self.is_type? || (block && eval('defined? __in_erb_template', block))
|
53
|
+
end
|
54
|
+
else
|
55
|
+
def block_is_type?(block)
|
56
|
+
self.is_type? || (block && eval('defined? __in_erb_template', block.binding))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
##
|
63
|
+
# Used to direct the buffer for the erb capture
|
64
|
+
#
|
65
|
+
def erb_with_output_buffer(buf = '')
|
66
|
+
self.output_buffer, old_buffer = buf, self.output_buffer
|
67
|
+
yield
|
68
|
+
buf
|
69
|
+
ensure
|
70
|
+
self.output_buffer = old_buffer
|
71
|
+
end
|
72
|
+
|
73
|
+
def output_buffer=(val)
|
74
|
+
template.instance_variable_set(:@_out_buf, val)
|
75
|
+
end
|
76
|
+
end # ErbHandler
|
77
|
+
OutputHelpers.register(ErbHandler)
|
78
|
+
end # OutputHelpers
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
module OutputHelpers
|
5
|
+
class HamlHandler < AbstractHandler
|
6
|
+
##
|
7
|
+
# Returns true if the current template type is same as this handlers; false otherwise.
|
8
|
+
#
|
9
|
+
# ==== Examples
|
10
|
+
#
|
11
|
+
# @handler.is_type? => true
|
12
|
+
#
|
13
|
+
def is_type?
|
14
|
+
template.respond_to?(:is_haml?) && template.is_haml?
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Returns true if the block given is of the handler's template type; false otherwise.
|
19
|
+
#
|
20
|
+
# ==== Examples
|
21
|
+
#
|
22
|
+
# @handler.block_is_type?(block) => true
|
23
|
+
#
|
24
|
+
def block_is_type?(block)
|
25
|
+
template.block_is_haml?(block)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Captures the html from a block of template code for this handler
|
29
|
+
#
|
30
|
+
# ==== Examples
|
31
|
+
#
|
32
|
+
# @handler.capture_from_template(&block) => "...html..."
|
33
|
+
#
|
34
|
+
def capture_from_template(*args, &block)
|
35
|
+
template.capture_haml(*args, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Outputs the given text to the templates buffer directly
|
40
|
+
#
|
41
|
+
# ==== Examples
|
42
|
+
#
|
43
|
+
# @handler.concat_to_template("This will be output to the template buffer")
|
44
|
+
#
|
45
|
+
def concat_to_template(text="")
|
46
|
+
template.haml_concat(text)
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end # HamlHandler
|
50
|
+
|
51
|
+
OutputHelpers.register(HamlHandler)
|
52
|
+
end # OutputHelpers
|
53
|
+
end
|
54
|
+
end
|
@@ -46,6 +46,19 @@ class MarkupUser
|
|
46
46
|
def gender; 'male'; end
|
47
47
|
def remember_me; '1'; end
|
48
48
|
def permission; Permission.new; end
|
49
|
+
def telephone; Telephone.new; end
|
50
|
+
def addresses; [Address.new('Greenfield', true), Address.new('Willowrun', false)]; end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Telephone
|
54
|
+
def number; "62634576545"; end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Address
|
58
|
+
attr_accessor :name
|
59
|
+
def initialize(name, existing); @name, @existing = name, existing; end
|
60
|
+
def new_record?; !@existing; end
|
61
|
+
def id; @existing ? 25 : nil; end
|
49
62
|
end
|
50
63
|
|
51
64
|
class Permission
|
@@ -5,4 +5,16 @@
|
|
5
5
|
<%= permission.check_box :can_edit %>
|
6
6
|
<%= permission.check_box :can_delete %>
|
7
7
|
<% end %>
|
8
|
+
<% f.fields_for :telephone do |child_form| %>
|
9
|
+
<%= child_form.label :number %>
|
10
|
+
<%= child_form.text_field :number %>
|
11
|
+
<% end %>
|
12
|
+
<% f.fields_for :addresses do |child_form| %>
|
13
|
+
<%= child_form.label :name %>
|
14
|
+
<%= child_form.text_field :name %>
|
15
|
+
<% unless child_form.object.new_record? %>
|
16
|
+
<%= child_form.check_box '_destroy' %>
|
17
|
+
<%= child_form.label '_destroy', :caption => 'Remove' %>
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
8
20
|
<% end %>
|
@@ -3,4 +3,13 @@
|
|
3
3
|
= f.text_field :gender
|
4
4
|
- fields_for @user.permission do |permission|
|
5
5
|
= permission.check_box :can_edit
|
6
|
-
= permission.check_box :can_delete
|
6
|
+
= permission.check_box :can_delete
|
7
|
+
- f.fields_for :telephone do |child_form|
|
8
|
+
= child_form.label :number
|
9
|
+
= child_form.text_field :number
|
10
|
+
- f.fields_for :addresses do |child_form|
|
11
|
+
= child_form.label :name
|
12
|
+
= child_form.text_field :name
|
13
|
+
- unless child_form.object.new_record?
|
14
|
+
= child_form.check_box '_destroy'
|
15
|
+
= child_form.label '_destroy', :caption => 'Remove'
|
data/test/helper.rb
CHANGED
@@ -70,6 +70,14 @@ class Test::Unit::TestCase
|
|
70
70
|
assert File.exist?(file), "File '#{file}' does not exist!"
|
71
71
|
assert_match pattern, File.read(file)
|
72
72
|
end
|
73
|
+
|
74
|
+
# mock_model("Business", :new_record? => true) => <Business>
|
75
|
+
def mock_model(klazz, options={})
|
76
|
+
options.reverse_merge!(:class => klazz, :new_record? => false, :id => 20, :errors => {})
|
77
|
+
record = stub(options)
|
78
|
+
record.stubs(:to_ary => [record])
|
79
|
+
record
|
80
|
+
end
|
73
81
|
end
|
74
82
|
|
75
83
|
module Webrat
|
data/test/test_form_builder.rb
CHANGED
@@ -9,10 +9,12 @@ class TestFormBuilder < Test::Unit::TestCase
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def setup
|
12
|
-
role_types = [
|
13
|
-
|
12
|
+
role_types = [mock_model('Role', :name => "Admin", :id => 1),
|
13
|
+
mock_model('Role', :name => 'Moderate', :id => 2), mock_model('Role', :name => 'Limited', :id => 3)]
|
14
|
+
@user = mock_model("User", :first_name => "Joe", :email => '', :session_id => 54)
|
15
|
+
@user.stubs(:errors => {:a => "must be present", :b => "must be valid", :email => "Must be valid", :first_name => []})
|
14
16
|
@user.stubs(:role_types => role_types, :role => "1")
|
15
|
-
@user_none =
|
17
|
+
@user_none = mock_model("User")
|
16
18
|
end
|
17
19
|
|
18
20
|
def standard_builder(object=@user)
|
@@ -184,6 +186,24 @@ class TestFormBuilder < Test::Unit::TestCase
|
|
184
186
|
end
|
185
187
|
end
|
186
188
|
|
189
|
+
context 'for #error_message_on method' do
|
190
|
+
should "display correct form html with no record" do
|
191
|
+
actual_html = standard_builder(@user_none).error_message_on(:name)
|
192
|
+
assert actual_html.blank?
|
193
|
+
end
|
194
|
+
|
195
|
+
should "display error for specified invalid object" do
|
196
|
+
actual_html = standard_builder(@user).error_message_on(:a, :prepend => "foo", :append => "bar")
|
197
|
+
assert_has_tag('span.error', :content => "foo must be present bar") { actual_html }
|
198
|
+
end
|
199
|
+
|
200
|
+
should "display error for specified invalid object not matching class name" do
|
201
|
+
@bob = mock_model("User", :first_name => "Frank", :errors => { :foo => "must be bob" })
|
202
|
+
actual_html = standard_builder(@bob).error_message_on(:foo, :prepend => "foo", :append => "bar")
|
203
|
+
assert_has_tag('span.error', :content => "foo must be bob bar") { actual_html }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
187
207
|
context 'for #label method' do
|
188
208
|
should "display correct label html" do
|
189
209
|
actual_html = standard_builder.label(:first_name, :class => 'large', :caption => "F. Name: ")
|
@@ -520,6 +540,126 @@ class TestFormBuilder < Test::Unit::TestCase
|
|
520
540
|
end
|
521
541
|
end
|
522
542
|
|
543
|
+
context 'for #fields_for method' do
|
544
|
+
setup do
|
545
|
+
@telephone = mock_model("Telephone", :number => "4568769876")
|
546
|
+
@user.stubs(:telephone).returns(@telephone)
|
547
|
+
@businesses = [ mock_model("Business", :name => "Silver", :new_record? => false, :id => 20) ]
|
548
|
+
@businesses << mock_model("Business", :name => "Gold", :new_record? => true)
|
549
|
+
@addresses = [ mock_model("Address", :name => "Foo", :new_record? => false, :id => 20, :businesses => @businesses) ]
|
550
|
+
@addresses << mock_model("Address", :name => "Bar", :new_record? => true, :businesses => @businesses)
|
551
|
+
@user.stubs(:addresses).returns(@addresses)
|
552
|
+
end
|
553
|
+
|
554
|
+
should "display nested children fields one-to-one within form" do
|
555
|
+
actual_html = standard_builder.fields_for :telephone do |child_form|
|
556
|
+
child_form.label(:number) +
|
557
|
+
child_form.text_field(:number) +
|
558
|
+
child_form.check_box('_destroy')
|
559
|
+
end
|
560
|
+
assert_has_tag('label', :for => 'user_telephone_attributes_number') { actual_html }
|
561
|
+
assert_has_tag('input', :type => 'text', :id => 'user_telephone_attributes_number', :name => 'user[telephone_attributes][number]', :value => "4568769876") { actual_html }
|
562
|
+
assert_has_tag('input', :type => 'hidden', :name => 'user[telephone_attributes][_destroy]', :value => '0') { actual_html }
|
563
|
+
assert_has_tag('input', :type => 'checkbox', :id => 'user_telephone_attributes__destroy', :name => 'user[telephone_attributes][_destroy]', :value => '1') { actual_html }
|
564
|
+
end
|
565
|
+
|
566
|
+
should "display nested children fields one-to-many within form" do
|
567
|
+
actual_html = standard_builder.fields_for(:addresses) do |child_form|
|
568
|
+
html = child_form.label(:name)
|
569
|
+
html << child_form.check_box('_destroy') unless child_form.object.new_record?
|
570
|
+
html << child_form.text_field(:name)
|
571
|
+
end
|
572
|
+
# Address 1 (Saved)
|
573
|
+
assert_has_tag('input', :type => 'hidden', :id => 'user_addresses_attributes_0_id', :name => "user[addresses_attributes][0][id]", :value => '20') { actual_html }
|
574
|
+
assert_has_tag('label', :for => 'user_addresses_attributes_0_name', :content => 'Name') { actual_html }
|
575
|
+
assert_has_tag('input', :type => 'text', :id => 'user_addresses_attributes_0_name', :name => 'user[addresses_attributes][0][name]') { actual_html }
|
576
|
+
assert_has_tag('input', :type => 'checkbox', :id => 'user_addresses_attributes_0__destroy', :name => 'user[addresses_attributes][0][_destroy]') { actual_html }
|
577
|
+
# Address 2 (New)
|
578
|
+
assert_has_no_tag('input', :type => 'hidden', :id => 'user_addresses_attributes_1_id') { actual_html }
|
579
|
+
assert_has_tag('label', :for => 'user_addresses_attributes_1_name', :content => 'Name') { actual_html }
|
580
|
+
assert_has_tag('input', :type => 'text', :id => 'user_addresses_attributes_1_name', :name => 'user[addresses_attributes][1][name]') { actual_html }
|
581
|
+
assert_has_no_tag('input', :type => 'checkbox', :id => 'user_addresses_attributes_1__destroy') { actual_html }
|
582
|
+
end
|
583
|
+
|
584
|
+
should "display fields for explicit instance object" do
|
585
|
+
address = mock_model("Address", :name => "Page", :new_record? => false, :id => 40)
|
586
|
+
actual_html = standard_builder.fields_for(:addresses, address) do |child_form|
|
587
|
+
html = child_form.label(:name)
|
588
|
+
html << child_form.text_field(:name)
|
589
|
+
html << child_form.check_box('_destroy')
|
590
|
+
end
|
591
|
+
assert_has_tag('input', :type => 'hidden', :id => 'user_addresses_attributes_0_id', :name => "user[addresses_attributes][0][id]", :value => '40') { actual_html }
|
592
|
+
assert_has_tag('label', :for => 'user_addresses_attributes_0_name', :content => 'Name') { actual_html }
|
593
|
+
assert_has_tag('input', :type => 'text', :id => 'user_addresses_attributes_0_name', :name => 'user[addresses_attributes][0][name]', :value => "Page") { actual_html }
|
594
|
+
assert_has_tag('input', :type => 'checkbox', :id => 'user_addresses_attributes_0__destroy', :name => 'user[addresses_attributes][0][_destroy]', :value => '1') { actual_html }
|
595
|
+
end
|
596
|
+
|
597
|
+
should "display fields for collection object" do
|
598
|
+
addresses = @addresses + [mock_model("Address", :name => "Walter", :new_record? => false, :id => 50)]
|
599
|
+
actual_html = standard_builder.fields_for(:addresses, addresses) do |child_form|
|
600
|
+
child_form.label(:name) +
|
601
|
+
child_form.text_field(:name) +
|
602
|
+
child_form.check_box('_destroy')
|
603
|
+
end
|
604
|
+
# Address 1
|
605
|
+
assert_has_tag('input', :type => 'hidden', :id => 'user_addresses_attributes_0_id', :name => "user[addresses_attributes][0][id]", :value => '20') { actual_html }
|
606
|
+
assert_has_tag('label', :for => 'user_addresses_attributes_0_name', :content => 'Name') { actual_html }
|
607
|
+
assert_has_tag('input', :type => 'text', :id => 'user_addresses_attributes_0_name', :name => 'user[addresses_attributes][0][name]', :value => "Foo") { actual_html }
|
608
|
+
assert_has_tag('input', :type => 'checkbox', :id => 'user_addresses_attributes_0__destroy', :name => 'user[addresses_attributes][0][_destroy]') { actual_html }
|
609
|
+
# Address 3
|
610
|
+
assert_has_tag('input', :type => 'hidden', :id => 'user_addresses_attributes_2_id', :value => '50') { actual_html }
|
611
|
+
assert_has_tag('label', :for => 'user_addresses_attributes_2_name', :content => 'Name') { actual_html }
|
612
|
+
assert_has_tag('input', :type => 'text', :id => 'user_addresses_attributes_2_name', :name => 'user[addresses_attributes][2][name]', :value => "Walter") { actual_html }
|
613
|
+
assert_has_tag('input', :type => 'checkbox', :id => 'user_addresses_attributes_2__destroy') { actual_html }
|
614
|
+
end
|
615
|
+
|
616
|
+
should "display fields for arbitrarily deep nested forms" do
|
617
|
+
actual_html = standard_builder.fields_for :addresses do |child_form|
|
618
|
+
child_form.fields_for(:businesses) do |second_child_form|
|
619
|
+
second_child_form.label(:name) +
|
620
|
+
second_child_form.text_field(:name) +
|
621
|
+
second_child_form.check_box('_destroy')
|
622
|
+
end
|
623
|
+
end
|
624
|
+
assert_has_tag('label', :for => 'user_addresses_attributes_1_businesses_attributes_0_name', :content => 'Name') { actual_html }
|
625
|
+
assert_has_tag('input', :type => 'text', :id => 'user_addresses_attributes_1_businesses_attributes_0_name', :name => 'user[addresses_attributes][1][businesses_attributes][0][name]') { actual_html }
|
626
|
+
end
|
627
|
+
|
628
|
+
should "display nested children fields in erb" do
|
629
|
+
visit '/erb/fields_for'
|
630
|
+
# Telephone
|
631
|
+
assert_have_selector('label', :for => 'markup_user_telephone_attributes_number')
|
632
|
+
assert_have_selector('input', :type => 'text', :id => 'markup_user_telephone_attributes_number', :name => 'markup_user[telephone_attributes][number]', :value => "62634576545")
|
633
|
+
# Address 1 (Saved)
|
634
|
+
assert_have_selector('input', :type => 'hidden', :id => 'markup_user_addresses_attributes_0_id', :name => "markup_user[addresses_attributes][0][id]", :value => '25')
|
635
|
+
assert_have_selector('label', :for => 'markup_user_addresses_attributes_0_name', :content => 'Name')
|
636
|
+
assert_have_selector('input', :type => 'text', :id => 'markup_user_addresses_attributes_0_name', :name => 'markup_user[addresses_attributes][0][name]')
|
637
|
+
assert_have_selector('input', :type => 'checkbox', :id => 'markup_user_addresses_attributes_0__destroy', :name => 'markup_user[addresses_attributes][0][_destroy]')
|
638
|
+
# Address 2 (New)
|
639
|
+
assert_have_no_selector('input', :type => 'hidden', :id => 'markup_user_addresses_attributes_1_id')
|
640
|
+
assert_have_selector('label', :for => 'markup_user_addresses_attributes_1_name', :content => 'Name')
|
641
|
+
assert_have_selector('input', :type => 'text', :id => 'markup_user_addresses_attributes_1_name', :name => 'markup_user[addresses_attributes][1][name]')
|
642
|
+
assert_have_no_selector('input', :type => 'checkbox', :id => 'markup_user_addresses_attributes_1__destroy')
|
643
|
+
end
|
644
|
+
|
645
|
+
should "display nested children fields in haml" do
|
646
|
+
visit '/haml/fields_for'
|
647
|
+
# Telephone
|
648
|
+
assert_have_selector('label', :for => 'markup_user_telephone_attributes_number')
|
649
|
+
assert_have_selector('input', :type => 'text', :id => 'markup_user_telephone_attributes_number', :name => 'markup_user[telephone_attributes][number]', :value => "62634576545")
|
650
|
+
# Address 1 (Saved)
|
651
|
+
assert_have_selector('input', :type => 'hidden', :id => 'markup_user_addresses_attributes_0_id', :name => "markup_user[addresses_attributes][0][id]", :value => '25')
|
652
|
+
assert_have_selector('label', :for => 'markup_user_addresses_attributes_0_name', :content => 'Name')
|
653
|
+
assert_have_selector('input', :type => 'text', :id => 'markup_user_addresses_attributes_0_name', :name => 'markup_user[addresses_attributes][0][name]')
|
654
|
+
assert_have_selector('input', :type => 'checkbox', :id => 'markup_user_addresses_attributes_0__destroy', :name => 'markup_user[addresses_attributes][0][_destroy]')
|
655
|
+
# Address 2 (New)
|
656
|
+
assert_have_no_selector('input', :type => 'hidden', :id => 'markup_user_addresses_attributes_1_id')
|
657
|
+
assert_have_selector('label', :for => 'markup_user_addresses_attributes_1_name', :content => 'Name')
|
658
|
+
assert_have_selector('input', :type => 'text', :id => 'markup_user_addresses_attributes_1_name', :name => 'markup_user[addresses_attributes][1][name]')
|
659
|
+
assert_have_no_selector('input', :type => 'checkbox', :id => 'markup_user_addresses_attributes_1__destroy')
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
523
663
|
# ===========================
|
524
664
|
# StandardFormBuilder
|
525
665
|
# ===========================
|
@@ -685,4 +825,4 @@ class TestFormBuilder < Test::Unit::TestCase
|
|
685
825
|
assert_have_selector '#demo2 p input', :type => 'image', :class => 'image', :src => "/images/buttons/ok.png?#{@stamp}"
|
686
826
|
end
|
687
827
|
end
|
688
|
-
end
|
828
|
+
end
|
data/test/test_form_helpers.rb
CHANGED
@@ -42,13 +42,11 @@ class TestFormHelpers < Test::Unit::TestCase
|
|
42
42
|
actual_html = form_tag('/remove', :"accept-charset" => "UTF-8", :class => 'delete-form', :method => "delete") { "Demo" }
|
43
43
|
assert_has_tag(:form, :class => "delete-form", :"accept-charset" => "UTF-8", :method => 'post') { actual_html }
|
44
44
|
assert_has_tag('form input', :type => 'hidden', :name => "_method", :value => 'delete') { actual_html }
|
45
|
-
assert_has_tag('form input', :type => 'hidden', :name => "_utf8") { actual_html }
|
46
45
|
end
|
47
46
|
|
48
47
|
should "display correct form with charset" do
|
49
48
|
actual_html = form_tag('/charset', :"accept-charset" => "UTF-8", :class => 'charset-form') { "Demo" }
|
50
49
|
assert_has_tag(:form, :class => "charset-form", :"accept-charset" => "UTF-8", :method => 'post') { actual_html }
|
51
|
-
assert_has_tag('form input', :type => 'hidden', :name => "_utf8") { actual_html }
|
52
50
|
end
|
53
51
|
|
54
52
|
should "display correct form with multipart encoding" do
|
@@ -95,7 +93,7 @@ class TestFormHelpers < Test::Unit::TestCase
|
|
95
93
|
|
96
94
|
context 'for #error_messages_for method' do
|
97
95
|
should "display correct error messages list in ruby" do
|
98
|
-
user =
|
96
|
+
user = mock_model("User", :errors => { :a => "1", :b => "2" }, :blank? => false)
|
99
97
|
actual_html = error_messages_for(user)
|
100
98
|
assert_has_tag('div.field-errors') { actual_html }
|
101
99
|
assert_has_tag('div.field-errors h2', :content => "2 errors prohibited this User from being saved") { actual_html }
|
@@ -133,6 +131,26 @@ class TestFormHelpers < Test::Unit::TestCase
|
|
133
131
|
end
|
134
132
|
end
|
135
133
|
|
134
|
+
context 'for #error_message_on method' do
|
135
|
+
should "display correct error message on specified model name in ruby" do
|
136
|
+
@user = mock_model("User", :errors => { :a => "1", :b => "2" }, :blank? => false)
|
137
|
+
actual_html = error_message_on(:user, :a, :prepend => "foo", :append => "bar")
|
138
|
+
assert_has_tag('span.error', :content => "foo 1 bar") { actual_html }
|
139
|
+
end
|
140
|
+
|
141
|
+
should "display correct error message on specified object in ruby" do
|
142
|
+
@bob = mock_model("User", :errors => { :a => "1", :b => "2" }, :blank? => false)
|
143
|
+
actual_html = error_message_on(@bob, :a, :prepend => "foo", :append => "bar")
|
144
|
+
assert_has_tag('span.error', :content => "foo 1 bar") { actual_html }
|
145
|
+
end
|
146
|
+
|
147
|
+
should "display no message when error isn't present" do
|
148
|
+
@user = mock_model("User", :errors => { :a => "1", :b => "2" }, :blank? => false)
|
149
|
+
actual_html = error_message_on(:user, :fake, :prepend => "foo", :append => "bar")
|
150
|
+
assert actual_html.blank?
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
136
154
|
context 'for #label_tag method' do
|
137
155
|
should "display label tag in ruby" do
|
138
156
|
actual_html = label_tag(:username, :class => 'long-label', :caption => "Nickname")
|
@@ -349,7 +367,7 @@ class TestFormHelpers < Test::Unit::TestCase
|
|
349
367
|
assert_has_tag(:select, :multiple => 'multiple', :name => 'favorite_color[]') { actual_html }
|
350
368
|
end
|
351
369
|
|
352
|
-
should "display options with values and selected" do
|
370
|
+
should "display options with values and single selected" do
|
353
371
|
options = [['Green', 'green1'], ['Blue', 'blue1'], ['Black', "black1"]]
|
354
372
|
actual_html = select_tag(:favorite_color, :options => options, :selected => 'green1')
|
355
373
|
assert_has_tag(:select, :name => 'favorite_color') { actual_html }
|
@@ -359,6 +377,16 @@ class TestFormHelpers < Test::Unit::TestCase
|
|
359
377
|
assert_has_tag('select option', :content => 'Black', :value => 'black1') { actual_html }
|
360
378
|
end
|
361
379
|
|
380
|
+
should "display option with values and multiple selected" do
|
381
|
+
options = [['Green', 'green1'], ['Blue', 'blue1'], ['Black', "black1"]]
|
382
|
+
actual_html = select_tag(:favorite_color, :options => options, :selected => ['green1', 'Black'])
|
383
|
+
assert_has_tag(:select, :name => 'favorite_color') { actual_html }
|
384
|
+
assert_has_tag('select option', :selected => 'selected', :count => 2) { actual_html }
|
385
|
+
assert_has_tag('select option', :content => 'Green', :value => 'green1', :selected => 'selected') { actual_html }
|
386
|
+
assert_has_tag('select option', :content => 'Blue', :value => 'blue1') { actual_html }
|
387
|
+
assert_has_tag('select option', :content => 'Black', :value => 'black1', :selected => 'selected') { actual_html }
|
388
|
+
end
|
389
|
+
|
362
390
|
should "display options selected only for exact match" do
|
363
391
|
options = [['One', '1'], ['1', '10'], ['Two', "-1"]]
|
364
392
|
actual_html = select_tag(:range, :options => options, :selected => '-1')
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: padrino-helpers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 17
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 9
|
9
|
-
-
|
10
|
-
version: 0.9.
|
9
|
+
- 21
|
10
|
+
version: 0.9.21
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Padrino Team
|
@@ -18,7 +18,7 @@ autorequire:
|
|
18
18
|
bindir: bin
|
19
19
|
cert_chain: []
|
20
20
|
|
21
|
-
date: 2011-
|
21
|
+
date: 2011-02-28 00:00:00 -08:00
|
22
22
|
default_executable:
|
23
23
|
dependencies:
|
24
24
|
- !ruby/object:Gem::Dependency
|
@@ -29,12 +29,12 @@ dependencies:
|
|
29
29
|
requirements:
|
30
30
|
- - "="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
hash:
|
32
|
+
hash: 17
|
33
33
|
segments:
|
34
34
|
- 0
|
35
35
|
- 9
|
36
|
-
-
|
37
|
-
version: 0.9.
|
36
|
+
- 21
|
37
|
+
version: 0.9.21
|
38
38
|
type: :runtime
|
39
39
|
version_requirements: *id001
|
40
40
|
- !ruby/object:Gem::Dependency
|
@@ -88,7 +88,11 @@ files:
|
|
88
88
|
- lib/padrino-helpers/locale/ru.yml
|
89
89
|
- lib/padrino-helpers/locale/tr.yml
|
90
90
|
- lib/padrino-helpers/locale/uk.yml
|
91
|
+
- lib/padrino-helpers/locale/zh_tw.yml
|
91
92
|
- lib/padrino-helpers/number_helpers.rb
|
93
|
+
- lib/padrino-helpers/output_helpers/abstract_handler.rb
|
94
|
+
- lib/padrino-helpers/output_helpers/erb_handler.rb
|
95
|
+
- lib/padrino-helpers/output_helpers/haml_handler.rb
|
92
96
|
- lib/padrino-helpers/output_helpers.rb
|
93
97
|
- lib/padrino-helpers/render_helpers.rb
|
94
98
|
- lib/padrino-helpers/tag_helpers.rb
|
@@ -161,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
161
165
|
requirements: []
|
162
166
|
|
163
167
|
rubyforge_project: padrino-helpers
|
164
|
-
rubygems_version: 1.
|
168
|
+
rubygems_version: 1.5.2
|
165
169
|
signing_key:
|
166
170
|
specification_version: 3
|
167
171
|
summary: Helpers for padrino
|