padrino-helpers 0.9.20 → 0.9.21
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.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
|