-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Expand file tree
/
Copy pathform_builder.rb
More file actions
183 lines (154 loc) · 5.96 KB
/
form_builder.rb
File metadata and controls
183 lines (154 loc) · 5.96 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
# frozen_string_literal: true
# Provides an intuitive way to build has_many associated records in the same form.
module Formtastic
module Inputs
module Base
def input_wrapping(&block)
html = super
template.concat(html) if template.output_buffer && template.assigns[:has_many_block]
html
end
end
end
end
module ActiveAdmin
class FormBuilder < ::Formtastic::FormBuilder
self.input_namespaces = [::Object, ::ActiveAdmin::Inputs, ::Formtastic::Inputs]
def cancel_link(url = { action: "index" }, html_options = {}, li_attrs = {})
li_attrs[:class] ||= "action cancel"
html_options[:class] ||= "cancel-link"
li_content = template.link_to I18n.t("active_admin.cancel"), url, html_options
template.content_tag(:li, li_content, li_attrs)
end
attr_accessor :already_in_an_inputs_block
def has_many(assoc, options = {}, &block)
HasManyBuilder.new(self, assoc, options).render(&block)
end
end
# Decorates a FormBuilder with the additional attributes and methods
# to build a has_many block. Nested has_many blocks are handled by
# nested decorators.
class HasManyBuilder < SimpleDelegator
attr_reader :assoc
attr_reader :options
attr_reader :heading, :sortable_column, :sortable_start
attr_reader :new_record, :destroy_option, :remove_record
def initialize(has_many_form, assoc, options)
super has_many_form
@assoc = assoc
@options = extract_custom_settings!(options.dup)
@options.reverse_merge!(for: assoc)
@options[:class] = [options[:class], "inputs has-many-fields"].compact.join(" ")
if sortable_column
@options[:for] = [assoc, sorted_children(sortable_column)]
end
end
def render(&block)
html = "".html_safe
html << template.content_tag(:h3, class: "has-many-fields-title") { heading } if heading.present?
html << template.capture { content_has_many(&block) }
html = wrap_div_or_li(html)
template.concat(html) if template.output_buffer
html
end
protected
# remove options that should not render as attributes
def extract_custom_settings!(options)
@heading = options.key?(:heading) ? options.delete(:heading) : default_heading
@sortable_column = options.delete(:sortable)
@sortable_start = options.delete(:sortable_start) || 0
@new_record = options.key?(:new_record) ? options.delete(:new_record) : true
@destroy_option = options.delete(:allow_destroy)
@remove_record = options.delete(:remove_record)
options
end
def default_heading
assoc_klass.model_name.human(count: 2.1)
end
def assoc_klass
@assoc_klass ||= __getobj__.object.class.reflect_on_association(assoc).klass
end
def content_has_many(&block)
form_block = proc do |form_builder|
render_has_many_form(form_builder, options[:parent], &block)
end
template.assigns[:has_many_block] = true
contents = without_wrapper { inputs(options, &form_block) }
contents ||= "".html_safe
js = new_record ? js_for_has_many(&form_block) : ""
contents << js
end
# Renders the Formtastic inputs then appends ActiveAdmin delete and sort actions.
def render_has_many_form(form_builder, parent, &block)
index = parent && form_builder.send(:parent_child_index, parent)
template.concat template.capture { yield(form_builder, index) }
template.concat has_many_actions(form_builder, "".html_safe)
end
def has_many_actions(form_builder, contents)
if form_builder.object.new_record?
contents << template.content_tag(:li, class: "input") do
remove_text = remove_record.is_a?(String) ? remove_record : I18n.t("active_admin.has_many_remove")
template.link_to remove_text, "#", class: "has-many-remove"
end
elsif allow_destroy?(form_builder.object)
form_builder.input(
:_destroy, as: :boolean,
wrapper_html: { class: "has-many-delete" },
label: I18n.t("active_admin.has_many_delete"))
end
if sortable_column
form_builder.input sortable_column, as: :hidden
# contents << template.content_tag(:li, class: "handle") do
# I18n.t("active_admin.move")
# end
end
contents
end
def allow_destroy?(form_object)
!! case destroy_option
when Symbol, String
form_object.public_send destroy_option
when Proc
destroy_option.call form_object
else
destroy_option
end
end
def sorted_children(column)
__getobj__.object.public_send(assoc).sort_by do |o|
attribute = o.public_send column
[attribute.nil? ? Float::INFINITY : attribute, o.id || Float::INFINITY]
end
end
def without_wrapper
is_being_wrapped = already_in_an_inputs_block
self.already_in_an_inputs_block = false
html = yield
self.already_in_an_inputs_block = is_being_wrapped
html
end
# Capture the ADD JS
def js_for_has_many(&form_block)
assoc_name = assoc_klass.model_name
placeholder = "NEW_#{assoc_name.to_s.underscore.upcase.tr('/', '_')}_RECORD"
opts = options.merge(
for: [assoc, assoc_klass.new],
for_options: { child_index: placeholder }
)
html = template.capture { __getobj__.send(:inputs_for_nested_attributes, opts, &form_block) }
text = new_record.is_a?(String) ? new_record : I18n.t("active_admin.has_many_new", model: assoc_name.human)
template.link_to text, "#", class: "has-many-add", data: {
html: CGI.escapeHTML(html).html_safe, placeholder: placeholder
}
end
def wrap_div_or_li(html)
template.content_tag(
already_in_an_inputs_block ? :li : :div,
html,
class: "has-many-container",
"data-has-many-association" => assoc,
"data-sortable" => sortable_column,
"data-sortable-start" => sortable_start)
end
end
end