bureaucrat 0.0.3 → 0.10.0
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.md +120 -105
- data/Rakefile +9 -0
- data/bureaucrat.gemspec +32 -0
- data/lib/bureaucrat.rb +26 -8
- data/lib/bureaucrat/fields.rb +572 -336
- data/lib/bureaucrat/forms.rb +296 -257
- data/lib/bureaucrat/formsets.rb +235 -194
- data/lib/bureaucrat/quickfields.rb +90 -62
- data/lib/bureaucrat/temporary_uploaded_file.rb +14 -0
- data/lib/bureaucrat/utils.rb +79 -62
- data/lib/bureaucrat/validators.rb +163 -0
- data/lib/bureaucrat/widgets.rb +459 -303
- data/test/fields_test.rb +519 -380
- data/test/forms_test.rb +78 -58
- data/test/formsets_test.rb +48 -22
- data/test/test_helper.rb +20 -12
- data/test/widgets_test.rb +224 -204
- metadata +27 -36
- data/lib/bureaucrat/validation.rb +0 -130
- data/lib/bureaucrat/validation_old.rb +0 -148
- data/lib/bureaucrat/wizard.rb +0 -220
data/lib/bureaucrat/widgets.rb
CHANGED
@@ -1,397 +1,553 @@
|
|
1
1
|
require 'uri'
|
2
2
|
|
3
|
-
require 'bureaucrat/utils'
|
4
|
-
|
5
3
|
module Bureaucrat
|
6
|
-
module Widgets
|
7
|
-
|
8
|
-
|
4
|
+
module Widgets
|
5
|
+
# Base class for widgets
|
6
|
+
class Widget
|
7
|
+
include Utils
|
9
8
|
|
10
|
-
|
11
|
-
|
9
|
+
attr_accessor :is_required
|
10
|
+
attr_reader :attrs
|
12
11
|
|
13
|
-
|
12
|
+
def initialize(attrs = nil)
|
13
|
+
@attrs = attrs.nil? ? {} : attrs.dup
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@js = []
|
16
|
+
def initialize_copy(original)
|
17
|
+
super(original)
|
18
|
+
@attrs = original.attrs.dup
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
21
|
+
def render(name, value, attrs = nil)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
26
24
|
|
27
|
-
|
28
|
-
|
25
|
+
def build_attrs(extra_attrs = nil, options = {})
|
26
|
+
attrs = @attrs.merge(options)
|
27
|
+
attrs.update(extra_attrs) if extra_attrs
|
28
|
+
attrs
|
29
|
+
end
|
30
|
+
|
31
|
+
def value_from_formdata(data, name)
|
32
|
+
data[name]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.id_for_label(id_)
|
36
|
+
id_
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_changed?(initial, data)
|
40
|
+
data_value = data || ''
|
41
|
+
initial_value = initial || ''
|
42
|
+
initial_value != data_value
|
43
|
+
end
|
44
|
+
|
45
|
+
def needs_multipart?
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def hidden?
|
50
|
+
false
|
51
|
+
end
|
29
52
|
end
|
30
53
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
54
|
+
# Base class for input widgets
|
55
|
+
class Input < Widget
|
56
|
+
def render(name, value, attrs=nil)
|
57
|
+
value ||= ''
|
58
|
+
final_attrs = build_attrs(attrs,
|
59
|
+
type: input_type.to_s,
|
60
|
+
name: name.to_s)
|
61
|
+
final_attrs[:value] = value.to_s unless value == ''
|
62
|
+
mark_safe("<input#{flatatt(final_attrs)} />")
|
63
|
+
end
|
64
|
+
|
65
|
+
def input_type
|
66
|
+
nil
|
67
|
+
end
|
35
68
|
end
|
36
69
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
70
|
+
# Class for text inputs
|
71
|
+
class TextInput < Input
|
72
|
+
def input_type
|
73
|
+
'text'
|
74
|
+
end
|
41
75
|
end
|
42
76
|
|
43
|
-
|
44
|
-
|
77
|
+
# Class for password inputs
|
78
|
+
class PasswordInput < Input
|
79
|
+
def initialize(attrs = nil, render_value = false)
|
80
|
+
super(attrs)
|
81
|
+
@render_value = render_value
|
82
|
+
end
|
83
|
+
|
84
|
+
def input_type
|
85
|
+
'password'
|
86
|
+
end
|
87
|
+
|
88
|
+
def render(name, value, attrs=nil)
|
89
|
+
value = nil unless @render_value
|
90
|
+
super(name, value, attrs)
|
91
|
+
end
|
45
92
|
end
|
46
93
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
94
|
+
# Class for hidden inputs
|
95
|
+
class HiddenInput < Input
|
96
|
+
def input_type
|
97
|
+
'hidden'
|
98
|
+
end
|
99
|
+
|
100
|
+
def hidden?
|
101
|
+
true
|
102
|
+
end
|
51
103
|
end
|
52
104
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
105
|
+
class MultipleHiddenInput < HiddenInput
|
106
|
+
# Used by choice fields
|
107
|
+
attr_accessor :choices
|
108
|
+
|
109
|
+
def initialize(attrs=nil, choices=[])
|
110
|
+
super(attrs)
|
111
|
+
# choices can be any enumerable
|
112
|
+
@choices = choices
|
113
|
+
end
|
114
|
+
|
115
|
+
def render(name, value, attrs=nil, choices=[])
|
116
|
+
value ||= []
|
117
|
+
final_attrs = build_attrs(attrs, type: input_type.to_s,
|
118
|
+
name: "#{name}[]")
|
119
|
+
|
120
|
+
id = final_attrs[:id]
|
121
|
+
inputs = []
|
122
|
+
|
123
|
+
value.each.with_index do |v, i|
|
124
|
+
input_attrs = final_attrs.merge(value: v.to_s)
|
125
|
+
|
126
|
+
if id
|
127
|
+
input_attrs[:id] = "#{id}_#{i}"
|
57
128
|
end
|
129
|
+
|
130
|
+
inputs << "<input#{flatatt(input_attrs)} />"
|
58
131
|
end
|
59
|
-
fragments.empty? ? fragments : fragments.inject(&:+)
|
60
|
-
end
|
61
132
|
|
62
|
-
|
63
|
-
|
133
|
+
mark_safe(inputs.join("\n"))
|
134
|
+
end
|
64
135
|
end
|
65
136
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
137
|
+
class FileInput < Input
|
138
|
+
def render(name, value, attrs=nil)
|
139
|
+
super(name, nil, attrs)
|
140
|
+
end
|
71
141
|
|
72
|
-
|
73
|
-
|
74
|
-
|
142
|
+
def value_from_formdata(data, name)
|
143
|
+
data[name] && TemporaryUploadedFile.new(data[name])
|
144
|
+
end
|
145
|
+
|
146
|
+
def has_changed?(initial, data)
|
147
|
+
data.nil?
|
148
|
+
end
|
75
149
|
|
76
|
-
|
77
|
-
|
150
|
+
def input_type
|
151
|
+
'file'
|
152
|
+
end
|
153
|
+
|
154
|
+
def needs_multipart?
|
155
|
+
true
|
156
|
+
end
|
78
157
|
end
|
79
158
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
159
|
+
class ClearableFileInput < FileInput
|
160
|
+
FILE_INPUT_CONTRADICTION = Object.new
|
161
|
+
|
162
|
+
def initial_text
|
163
|
+
'Currently'
|
164
|
+
end
|
165
|
+
|
166
|
+
def input_text
|
167
|
+
'Change'
|
168
|
+
end
|
169
|
+
|
170
|
+
def clear_checkbox_label
|
171
|
+
'Clear'
|
172
|
+
end
|
173
|
+
|
174
|
+
def template_with_initial
|
175
|
+
'%(initial_text)s: %(initial)s %(clear_template)s<br />%(input_text)s: %(input)s'
|
176
|
+
end
|
177
|
+
|
178
|
+
def template_with_clear
|
179
|
+
'%(clear)s <label for="%(clear_checkbox_id)s">%(clear_checkbox_label)s</label>'
|
180
|
+
end
|
181
|
+
|
182
|
+
def clear_checkbox_name(name)
|
183
|
+
"#{name}-clear"
|
184
|
+
end
|
185
|
+
|
186
|
+
def clear_checkbox_id(checkbox_name)
|
187
|
+
"#{checkbox_name}_id"
|
188
|
+
end
|
189
|
+
|
190
|
+
def render(name, value, attrs = nil)
|
191
|
+
substitutions = {
|
192
|
+
initial_text: initial_text,
|
193
|
+
input_text: input_text,
|
194
|
+
clear_template: '',
|
195
|
+
clear_checkbox_label: clear_checkbox_label
|
196
|
+
}
|
197
|
+
template = '%(input)s'
|
198
|
+
substitutions[:input] = super(name, value, attrs)
|
199
|
+
|
200
|
+
if value && value.respond_to?(:url) && value.url
|
201
|
+
template = template_with_initial
|
202
|
+
substitutions[:initial] = '<a href="%s">%s</a>' % [escape(value.url),
|
203
|
+
escape(value.to_s)]
|
204
|
+
unless is_required
|
205
|
+
checkbox_name = clear_checkbox_name(name)
|
206
|
+
checkbox_id = clear_checkbox_id(checkbox_name)
|
207
|
+
substitutions[:clear_checkbox_name] = conditional_escape(checkbox_name)
|
208
|
+
substitutions[:clear_checkbox_id] = conditional_escape(checkbox_id)
|
209
|
+
substitutions[:clear] = CheckboxInput.new.
|
210
|
+
render(checkbox_name, false, {id: checkbox_id})
|
211
|
+
substitutions[:clear_template] =
|
212
|
+
Utils.format_string(template_with_clear, substitutions)
|
213
|
+
end
|
85
214
|
end
|
86
|
-
end
|
87
215
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
216
|
+
mark_safe(Utils.format_string(template, substitutions))
|
217
|
+
end
|
218
|
+
|
219
|
+
def value_from_formdata(data, name)
|
220
|
+
upload = super(data, name)
|
221
|
+
checked = CheckboxInput.new.
|
222
|
+
value_from_formdata(data, clear_checkbox_name(name))
|
223
|
+
|
224
|
+
if !is_required && checked
|
225
|
+
if upload
|
226
|
+
# If the user contradicts themselves (uploads a new file AND
|
227
|
+
# checks the "clear" checkbox), we return a unique marker
|
228
|
+
# object that FileField will turn into a ValidationError.
|
229
|
+
FILE_INPUT_CONTRADICTION
|
230
|
+
else
|
231
|
+
# False signals to clear any existing value, as opposed to just None
|
232
|
+
false
|
233
|
+
end
|
234
|
+
else
|
235
|
+
upload
|
93
236
|
end
|
94
|
-
|
237
|
+
end
|
95
238
|
end
|
96
|
-
end
|
97
239
|
|
98
|
-
|
99
|
-
|
240
|
+
class Textarea < Widget
|
241
|
+
def initialize(attrs=nil)
|
242
|
+
# The 'rows' and 'cols' attributes are required for HTML correctness.
|
243
|
+
default_attrs = {cols: '40', rows: '10'}
|
244
|
+
default_attrs.merge!(attrs) if attrs
|
100
245
|
|
101
|
-
|
102
|
-
|
246
|
+
super(default_attrs)
|
247
|
+
end
|
103
248
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
107
|
-
|
249
|
+
def render(name, value, attrs=nil)
|
250
|
+
value ||= ''
|
251
|
+
final_attrs = build_attrs(attrs, name: name)
|
252
|
+
mark_safe("<textarea#{flatatt(final_attrs)}>#{conditional_escape(value.to_s)}</textarea>")
|
108
253
|
end
|
109
254
|
end
|
110
255
|
|
111
|
-
|
112
|
-
|
256
|
+
# DateInput
|
257
|
+
# DateTimeInput
|
258
|
+
# TimeInput
|
113
259
|
|
114
|
-
|
260
|
+
class CheckboxInput < Widget
|
261
|
+
def initialize(attrs=nil, check_test=nil)
|
262
|
+
super(attrs)
|
263
|
+
@check_test = check_test || lambda {|v| make_bool(v)}
|
264
|
+
end
|
115
265
|
|
116
|
-
|
117
|
-
|
118
|
-
end
|
266
|
+
def render(name, value, attrs=nil)
|
267
|
+
final_attrs = build_attrs(attrs, type: 'checkbox', name: name.to_s)
|
119
268
|
|
120
|
-
|
121
|
-
|
122
|
-
@attrs = original.attrs.dup
|
123
|
-
end
|
269
|
+
# FIXME: this is horrible, shouldn't just rescue everything
|
270
|
+
result = @check_test.call(value) rescue false
|
124
271
|
|
125
|
-
|
126
|
-
|
127
|
-
|
272
|
+
if result
|
273
|
+
final_attrs[:checked] = 'checked'
|
274
|
+
end
|
128
275
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
attrs
|
133
|
-
end
|
276
|
+
unless ['', true, false, nil].include?(value)
|
277
|
+
final_attrs[:value] = value.to_s
|
278
|
+
end
|
134
279
|
|
135
|
-
|
136
|
-
|
137
|
-
end
|
280
|
+
mark_safe("<input#{flatatt(final_attrs)} />")
|
281
|
+
end
|
138
282
|
|
139
|
-
|
140
|
-
|
141
|
-
|
283
|
+
def value_from_formdata(data, name)
|
284
|
+
if data.include?(name)
|
285
|
+
value = data[name]
|
142
286
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
287
|
+
if value.is_a?(String)
|
288
|
+
case value.downcase
|
289
|
+
when 'true' then true
|
290
|
+
when 'false' then false
|
291
|
+
else value
|
292
|
+
end
|
293
|
+
else
|
294
|
+
value
|
295
|
+
end
|
296
|
+
else
|
297
|
+
false
|
298
|
+
end
|
299
|
+
end
|
148
300
|
|
149
|
-
|
150
|
-
|
301
|
+
def has_changed(initial, data)
|
302
|
+
make_bool(initial) != make_bool(data)
|
303
|
+
end
|
151
304
|
end
|
152
305
|
|
153
|
-
|
154
|
-
|
155
|
-
end
|
156
|
-
end
|
306
|
+
class Select < Widget
|
307
|
+
attr_accessor :choices
|
157
308
|
|
158
|
-
|
159
|
-
|
160
|
-
|
309
|
+
def initialize(attrs=nil, choices=[])
|
310
|
+
super(attrs)
|
311
|
+
@choices = choices.collect
|
312
|
+
end
|
161
313
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
314
|
+
def render(name, value, attrs=nil, choices=[])
|
315
|
+
value = '' if value.nil?
|
316
|
+
final_attrs = build_attrs(attrs, name: name)
|
317
|
+
output = ["<select#{flatatt(final_attrs)}>"]
|
318
|
+
options = render_options(choices, [value])
|
319
|
+
output << options if options && !options.empty?
|
320
|
+
output << '</select>'
|
321
|
+
mark_safe(output.join("\n"))
|
166
322
|
end
|
167
|
-
end
|
168
323
|
|
169
|
-
|
170
|
-
|
324
|
+
def render_options(choices, selected_choices)
|
325
|
+
selected_choices = selected_choices.map(&:to_s).uniq
|
326
|
+
output = []
|
327
|
+
(@choices.to_a + choices.to_a).each do |option_value, option_label|
|
328
|
+
option_label ||= option_value
|
329
|
+
if option_label.is_a?(Array)
|
330
|
+
output << '<optgroup label="%s">' % escape(option_value.to_s)
|
331
|
+
option_label.each do |option|
|
332
|
+
val, label = option
|
333
|
+
output << render_option(val, label, selected_choices)
|
334
|
+
end
|
335
|
+
output << '</optgroup>'
|
336
|
+
else
|
337
|
+
output << render_option(option_value, option_label,
|
338
|
+
selected_choices)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
output.join("\n")
|
342
|
+
end
|
171
343
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
:name => name.to_s)
|
177
|
-
final_attrs[:value] = value.to_s unless value == ''
|
178
|
-
mark_safe("<input#{flatatt(final_attrs)} />")
|
179
|
-
end
|
180
|
-
end
|
344
|
+
def render_option(option_attributes, option_label, selected_choices)
|
345
|
+
unless option_attributes.is_a?(Hash)
|
346
|
+
option_attributes = { value: option_attributes.to_s }
|
347
|
+
end
|
181
348
|
|
182
|
-
|
183
|
-
|
184
|
-
|
349
|
+
if selected_choices.include?(option_attributes[:value])
|
350
|
+
option_attributes[:selected] = "selected"
|
351
|
+
end
|
185
352
|
|
186
|
-
|
187
|
-
self.input_type = 'password'
|
353
|
+
attributes = []
|
188
354
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
end
|
355
|
+
option_attributes.each_pair do |attr_name, attr_value|
|
356
|
+
attributes << %Q[#{attr_name.to_s}="#{escape(attr_value.to_s)}"]
|
357
|
+
end
|
193
358
|
|
194
|
-
|
195
|
-
|
196
|
-
super(name, value, attrs)
|
359
|
+
"<option #{attributes.join(' ')}>#{conditional_escape(option_label.to_s)}</option>"
|
360
|
+
end
|
197
361
|
end
|
198
|
-
end
|
199
362
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
363
|
+
class NullBooleanSelect < Select
|
364
|
+
def initialize(attrs=nil)
|
365
|
+
choices = [['1', 'Unknown'], ['2', 'Yes'], ['3', 'No']]
|
366
|
+
super(attrs, choices)
|
367
|
+
end
|
204
368
|
|
205
|
-
|
206
|
-
|
207
|
-
|
369
|
+
def render(name, value, attrs=nil, choices=[])
|
370
|
+
value = case value
|
371
|
+
when true, '2' then '2'
|
372
|
+
when false, '3' then '3'
|
373
|
+
else '1'
|
374
|
+
end
|
375
|
+
super(name, value, attrs, choices)
|
376
|
+
end
|
208
377
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
378
|
+
def value_from_formdata(data, name)
|
379
|
+
case data[name]
|
380
|
+
when '2', true, 'true' then true
|
381
|
+
when '3', false, 'false' then false
|
382
|
+
else nil
|
383
|
+
end
|
384
|
+
end
|
214
385
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
end
|
386
|
+
def has_changed?(initial, data)
|
387
|
+
unless initial.nil?
|
388
|
+
initial = make_bool(initial)
|
389
|
+
end
|
390
|
+
|
391
|
+
unless data.nil?
|
392
|
+
data = make_bool(data)
|
393
|
+
end
|
224
394
|
|
225
|
-
|
226
|
-
|
227
|
-
# data.getlist(name)
|
228
|
-
#else
|
229
|
-
# data[name]
|
230
|
-
#end
|
231
|
-
data[name]
|
395
|
+
initial != data
|
396
|
+
end
|
232
397
|
end
|
233
|
-
end
|
234
398
|
|
235
|
-
|
236
|
-
|
237
|
-
|
399
|
+
class SelectMultiple < Select
|
400
|
+
def render(name, value, attrs=nil, choices=[])
|
401
|
+
value = [] if value.nil?
|
402
|
+
final_attrs = build_attrs(attrs, name: "#{name}[]")
|
403
|
+
output = ["<select multiple=\"multiple\"#{flatatt(final_attrs)}>"]
|
404
|
+
options = render_options(choices, value)
|
405
|
+
output << options if options && !options.empty?
|
406
|
+
output << '</select>'
|
407
|
+
mark_safe(output.join("\n"))
|
408
|
+
end
|
238
409
|
|
239
|
-
|
240
|
-
|
241
|
-
|
410
|
+
def has_changed?(initial, data)
|
411
|
+
initial = [] if initial.nil?
|
412
|
+
data = [] if data.nil?
|
242
413
|
|
243
|
-
|
244
|
-
|
245
|
-
|
414
|
+
if initial.length != data.length
|
415
|
+
return true
|
416
|
+
end
|
246
417
|
|
247
|
-
|
248
|
-
|
418
|
+
Set.new(initial.map(&:to_s)) != Set.new(data.map(&:to_s))
|
419
|
+
end
|
249
420
|
end
|
250
|
-
end
|
251
421
|
|
252
|
-
|
253
|
-
|
254
|
-
# The 'rows' and 'cols' attributes are required for HTML correctness.
|
255
|
-
@attrs = {:cols => '40', :rows => '10'}
|
256
|
-
@attrs.merge!(attrs) if attrs
|
257
|
-
end
|
422
|
+
class RadioInput
|
423
|
+
include Utils
|
258
424
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
425
|
+
def initialize(name, value, attrs, choice, index)
|
426
|
+
@name = name
|
427
|
+
@value = value
|
428
|
+
@attrs = attrs
|
429
|
+
@choice_value = choice[0].to_s
|
430
|
+
@choice_label = choice[1].to_s
|
431
|
+
@index = index
|
432
|
+
end
|
265
433
|
|
266
|
-
|
267
|
-
|
268
|
-
|
434
|
+
def to_s
|
435
|
+
label_for = @attrs.include?(:id) ? " for=\"#{@attrs[:id]}_#{@index}\"" : ''
|
436
|
+
choice_label = conditional_escape(@choice_label.to_s)
|
437
|
+
mark_safe("<label#{label_for}>#{tag} #{choice_label}</label>")
|
438
|
+
end
|
269
439
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
@check_test = check_test || lambda {|v| make_bool(v)}
|
274
|
-
end
|
440
|
+
def checked?
|
441
|
+
@value == @choice_value
|
442
|
+
end
|
275
443
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
444
|
+
def tag
|
445
|
+
@attrs[:id] = "#{@attrs[:id]}_#{@index}" if @attrs.include?(:id)
|
446
|
+
final_attrs = @attrs.merge(type: 'radio', name: @name,
|
447
|
+
value: @choice_value)
|
448
|
+
final_attrs[:checked] = 'checked' if checked?
|
449
|
+
mark_safe("<input#{flatatt(final_attrs)} />")
|
450
|
+
end
|
283
451
|
end
|
284
452
|
|
285
|
-
|
286
|
-
|
287
|
-
end
|
453
|
+
class RadioFieldRenderer
|
454
|
+
include Utils
|
288
455
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
456
|
+
def initialize(name, value, attrs, choices)
|
457
|
+
@name = name
|
458
|
+
@value = value
|
459
|
+
@attrs = attrs
|
460
|
+
@choices = choices
|
461
|
+
end
|
293
462
|
|
294
|
-
|
295
|
-
|
463
|
+
def each
|
464
|
+
@choices.each_with_index do |choice, i|
|
465
|
+
yield RadioInput.new(@name, @value, @attrs.dup, choice, i)
|
466
|
+
end
|
467
|
+
end
|
296
468
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
469
|
+
def [](idx)
|
470
|
+
choice = @choices[idx]
|
471
|
+
RadioInput.new(@name, @value, @attrs.dup, choice, idx)
|
472
|
+
end
|
301
473
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
output = ["<select#{flatatt(final_attrs)}>"]
|
306
|
-
options = render_options(choices, [value])
|
307
|
-
output << options if options && !options.empty?
|
308
|
-
output << '</select>'
|
309
|
-
mark_safe(output.join("\n"))
|
310
|
-
end
|
474
|
+
def to_s
|
475
|
+
render
|
476
|
+
end
|
311
477
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
output << '<optgroup label="%s">' % escape(option_value.to_s)
|
318
|
-
option_label.each do |option|
|
319
|
-
val, label = option
|
320
|
-
output << render_option(val, label, selected_choices)
|
321
|
-
end
|
322
|
-
output << '</optgroup>'
|
323
|
-
else
|
324
|
-
output << render_option(option_value, option_label,
|
325
|
-
selected_choices)
|
326
|
-
end
|
327
|
-
end
|
328
|
-
output.join("\n")
|
478
|
+
def render
|
479
|
+
list = []
|
480
|
+
each {|radio| list << "<li>#{radio}</li>"}
|
481
|
+
mark_safe("<ul>\n#{list.join("\n")}\n</ul>")
|
482
|
+
end
|
329
483
|
end
|
330
484
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
end
|
336
|
-
end
|
485
|
+
class RadioSelect < Select
|
486
|
+
def self.id_for_label(id_)
|
487
|
+
id_.empty? ? id_ : id_ + '_0'
|
488
|
+
end
|
337
489
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
super(attrs, choices)
|
342
|
-
end
|
490
|
+
def renderer
|
491
|
+
RadioFieldRenderer
|
492
|
+
end
|
343
493
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
end
|
350
|
-
super(name, value, attrs, choices)
|
351
|
-
end
|
494
|
+
def initialize(*args)
|
495
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
496
|
+
@renderer = options.fetch(:renderer, renderer)
|
497
|
+
super
|
498
|
+
end
|
352
499
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
500
|
+
def get_renderer(name, value, attrs=nil, choices=[])
|
501
|
+
value ||= ''
|
502
|
+
str_value = value.to_s
|
503
|
+
final_attrs = build_attrs(attrs)
|
504
|
+
choices = @choices.to_a + choices.to_a
|
505
|
+
@renderer.new(name, str_value, final_attrs, choices)
|
359
506
|
end
|
360
|
-
end
|
361
507
|
|
362
|
-
|
363
|
-
|
508
|
+
def render(name, value, attrs=nil, choices=[])
|
509
|
+
get_renderer(name, value, attrs, choices).render
|
510
|
+
end
|
364
511
|
end
|
365
|
-
end
|
366
512
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
output = ["<select multiple=\"multiple\"#{flatatt(final_attrs)}>"]
|
372
|
-
options = render_options(choices, value)
|
373
|
-
output << options if options && !options.empty?
|
374
|
-
output << '</select>'
|
375
|
-
mark_safe(output.join("\n"))
|
376
|
-
end
|
513
|
+
class CheckboxSelectMultiple < SelectMultiple
|
514
|
+
def self.id_for_label(id_)
|
515
|
+
id_.empty? ? id_ : id_ + '_0'
|
516
|
+
end
|
377
517
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
518
|
+
def render(name, values, attrs=nil, choices=[])
|
519
|
+
values ||= []
|
520
|
+
multi_name = "#{name}[]"
|
521
|
+
has_id = attrs && attrs.include?(:id)
|
522
|
+
final_attrs = build_attrs(attrs, name: multi_name)
|
523
|
+
output = ['<ul>']
|
524
|
+
str_values = {}
|
525
|
+
values.each {|val| str_values[(val.to_s)] = true}
|
526
|
+
|
527
|
+
(@choices.to_a + choices.to_a).each_with_index do |opt_pair, i|
|
528
|
+
opt_val, opt_label = opt_pair
|
529
|
+
if has_id
|
530
|
+
final_attrs[:id] = "#{attrs[:id]}_#{i}"
|
531
|
+
label_for = " for=\"#{final_attrs[:id]}\""
|
532
|
+
else
|
533
|
+
label_for = ''
|
534
|
+
end
|
386
535
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
536
|
+
check_test = lambda{|value| str_values[value]}
|
537
|
+
cb = CheckboxInput.new(final_attrs, check_test)
|
538
|
+
opt_val = opt_val.to_s
|
539
|
+
rendered_cb = cb.render(multi_name, opt_val)
|
540
|
+
opt_label = conditional_escape(opt_label.to_s)
|
541
|
+
output << "<li><label#{label_for}>#{rendered_cb} #{opt_label}</label></li>"
|
542
|
+
end
|
543
|
+
output << '</ul>'
|
544
|
+
mark_safe(output.join("\n"))
|
545
|
+
end
|
395
546
|
end
|
547
|
+
|
548
|
+
# TODO: MultiWidget < Widget
|
549
|
+
# TODO: SplitDateTimeWidget < MultiWidget
|
550
|
+
# TODO: SplitHiddenDateTimeWidget < SplitDateTimeWidget
|
551
|
+
|
396
552
|
end
|
397
|
-
end
|
553
|
+
end
|