forked from opf/openproject
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtabular_form_builder.rb
More file actions
267 lines (214 loc) · 8.64 KB
/
Copy pathtabular_form_builder.rb
File metadata and controls
267 lines (214 loc) · 8.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'action_view/helpers/form_helper'
require 'securerandom'
class TabularFormBuilder < ActionView::Helpers::FormBuilder
include Redmine::I18n
include ActionView::Helpers::AssetTagHelper
include ERB::Util
(field_helpers - %i(radio_button hidden_field fields_for label) + %i(date_select)).each do |selector|
define_method selector do |field, options = {}, *args|
options[:class] = Array(options[:class]) + [field_css_class(selector)]
merge_required_attributes(options[:required], options)
input_options, label_options = extract_from options
label = label_for_field(field, label_options)
input = super(field, input_options, *args)
(label + container_wrap_field(input, selector, options))
end
end
def label(method, text = nil, options = {}, &block)
options[:class] = Array(options[:class]) + %w(form--label)
options[:title] = options[:title] || title_from_context(method)
super
end
def radio_button(field, value, options = {}, *args)
options[:class] = Array(options[:class]) + %w(form--radio-button)
input_options, label_options = extract_from options
label_options[:for] = "#{sanitized_object_name}_#{field}_#{value.downcase}"
label = label_for_field(field, label_options)
input = super(field, value, input_options, *args)
(label + container_wrap_field(input, 'radio-button', options))
end
def select(field, choices, options = {}, html_options = {})
html_options[:class] = Array(html_options[:class]) + %w(form--select)
merge_required_attributes(options[:required], html_options)
label_for_field(field, options) + container_wrap_field(super, 'select', options)
end
def collection_select(field, collection, value_method, text_method, options = {}, html_options = {})
html_options[:class] = Array(html_options[:class]) + %w(form--select)
label_for_field(field, options) + container_wrap_field(super, 'select', options)
end
def collection_check_box(field,
checked_value,
checked,
text = field.to_s + "_#{checked_value}",
options = {})
label_for = "#{sanitized_object_name}_#{field}_#{checked_value}".to_sym
unchecked_value = options.delete(:unchecked_value) { '' }
input_options = options.reverse_merge(multiple: true,
checked: checked,
for: label_for,
label: text)
check_box(field, input_options, checked_value, unchecked_value)
end
def fields_for_custom_fields(record_name, record_object = nil, options = {}, &block)
options_with_defaults = options.merge(builder: CustomFieldFormBuilder)
fields_for(record_name, record_object, options_with_defaults, &block)
end
private
attr_reader :template
TEXT_LIKE_FIELDS = %i(
number_field password_field url_field telephone_field email_field
).freeze
def container_wrap_field(field_html, selector, options = {})
ret = content_tag(:span, field_html, class: field_container_css_class(selector, options))
prefix, suffix = options.values_at(:prefix, :suffix)
if prefix
ret.prepend content_tag(:span,
prefix.html_safe,
class: 'form--field-affix',
id: options[:prefix_id],
:'aria-hidden' => true)
end
if suffix
ret.concat content_tag(:span,
suffix.html_safe,
class: 'form--field-affix',
id: options[:suffix_id],
:'aria-hidden' => true)
end
field_container_wrap_field(ret, options)
end
def merge_required_attributes(required, options = nil)
if required
options.merge!(required: true, :'aria-required' => 'true')
end
end
def field_container_wrap_field(field_html, options = {})
if options[:no_label]
field_html
else
content_tag(:span, field_html, class: options[:no_class] ? '' : 'form--field-container')
end
end
def field_container_css_class(selector, options)
classes = if TEXT_LIKE_FIELDS.include?(selector)
'form--text-field-container'
else
"form--#{selector.to_s.tr('_', '-')}-container"
end
classes << ' ' + options.fetch(:container_class, '')
classes.strip
end
def field_css_class(selector)
if TEXT_LIKE_FIELDS.include?(selector)
"form--text-field -#{selector.to_s.gsub(/_field$/, '')}"
else
"form--#{selector.to_s.tr('_', '-')}"
end
end
# Returns a label tag for the given field
def label_for_field(field, options = {})
return ''.html_safe if options[:no_label]
text = get_localized_field(field, options[:label])
label_options = { class: 'form--label', title: text }
if options[:class].is_a?(Array)
label_options[:class] << " #{options[:class].join(' ')}"
elsif options[:class].is_a?(String)
label_options[:class] << " #{options[:class]}"
end
content = h(text)
label_for_field_errors(content, label_options, field)
label_for_field_required(content, label_options, options[:required])
label_for_field_for(options, label_options, field)
label_for_field_prefix(content, options)
label_options[:lang] = options[:lang]
label_options.reject! do |_k, v| v.nil? end
@template.label(@object_name, field, content, label_options)
end
def label_for_field_errors(content, options, field)
if @object.try(:errors) && @object.errors.include?(field)
options[:class] << ' -error'
error_label = I18n.t('errors.field_erroneous_label',
full_errors: @object.errors.full_messages_for(field).join(' '))
content << content_tag('p', error_label, class: 'hidden-for-sighted')
end
end
def label_for_field_required(content, options, is_required)
if is_required
options[:class] << ' -required'
content << content_tag('span',
'*',
class: 'form--label-required',
:'aria-hidden' => true)
end
end
def label_for_field_for(options, label_options, field)
label_options[:for] = options[:for]
end
def label_for_field_prefix(content, options)
if options[:prefix]
content << content_tag(:span, options[:prefix].html_safe, class: 'hidden-for-sighted')
end
end
def get_localized_field(field, label)
if label.is_a?(Symbol)
l(label)
elsif label
label
elsif @object.is_a?(ActiveRecord::Base)
@object.class.human_attribute_name(field)
else
l(field)
end
end
def extract_from(options)
label_options = options.dup.except(:class)
input_options = options.dup.except(:for, :label, :no_label, :prefix, :suffix)
label_options.merge!(options.delete(:label_options) || {})
if options[:suffix]
options[:suffix_id] ||= SecureRandom.uuid
input_options[:'aria-describedby'] ||= options[:suffix_id]
end
if options[:prefix]
options[:prefix_id] ||= SecureRandom.uuid
end
[input_options, label_options]
end
def sanitized_object_name
object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, '_').sub(/_$/, '')
end
def title_from_context(method)
if object.class.respond_to? :human_attribute_name
object.class.human_attribute_name method
else
method.to_s.camelize
end
end
end