custom_table 0.1.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.
- checksums.yaml +7 -0
- data/README.md +210 -0
- data/Rakefile +8 -0
- data/app/assets/config/custom_table_manifest.js +1 -0
- data/app/assets/stylesheets/custom_table/application.scss +1 -0
- data/app/assets/stylesheets/custom_table/table.scss +89 -0
- data/app/controllers/concerns/custom_table_concern.rb +118 -0
- data/app/controllers/custom_table/application_controller.rb +4 -0
- data/app/controllers/custom_table/settings_controller.rb +86 -0
- data/app/helpers/custom_table/application_helper.rb +483 -0
- data/app/helpers/custom_table/fieldset_helper.rb +90 -0
- data/app/helpers/custom_table/icons_helper.rb +42 -0
- data/app/inputs/date_picker_input.rb +52 -0
- data/app/javascript/controllers/batch_actions_controller.js +53 -0
- data/app/javascript/controllers/table_controller.js +109 -0
- data/app/jobs/custom_table/application_job.rb +4 -0
- data/app/mailers/custom_table/application_mailer.rb +6 -0
- data/app/models/concerns/custom_table_settings.rb +42 -0
- data/app/models/custom_table/application_record.rb +5 -0
- data/app/views/custom_table/_download.haml +19 -0
- data/app/views/custom_table/_field.haml +8 -0
- data/app/views/custom_table/_field_plain.haml +1 -0
- data/app/views/custom_table/_fieldset.haml +2 -0
- data/app/views/custom_table/_filter.html.haml +104 -0
- data/app/views/custom_table/_settings.html.haml +57 -0
- data/app/views/custom_table/_table.html.haml +261 -0
- data/app/views/custom_table/_table.xlsx.axlsx +76 -0
- data/app/views/custom_table/_table_fe.xlsx.fast_excel +141 -0
- data/app/views/custom_table/_table_row.html.haml +72 -0
- data/app/views/custom_table/_table_row_data.html.haml +26 -0
- data/app/views/custom_table/settings/destroy.html.haml +4 -0
- data/app/views/custom_table/settings/edit.html.haml +2 -0
- data/app/views/custom_table/settings/update.html.haml +4 -0
- data/app/views/layouts/custom_table/application.html.erb +15 -0
- data/config/initializers/simple_form_bootstrap.rb +468 -0
- data/config/locales/en.yml +18 -0
- data/config/locales/ru.yml +18 -0
- data/config/routes.rb +5 -0
- data/lib/custom_table/configuration.rb +10 -0
- data/lib/custom_table/engine.rb +12 -0
- data/lib/custom_table/version.rb +3 -0
- data/lib/custom_table.rb +14 -0
- data/lib/generators/custom_table/USAGE +8 -0
- data/lib/generators/custom_table/custom_table_generator.rb +16 -0
- data/lib/generators/custom_table/templates/initializer.rb +3 -0
- data/lib/generators/custom_table/templates/migration.rb +5 -0
- data/lib/tasks/custom_table_tasks.rake +4 -0
- metadata +300 -0
@@ -0,0 +1,483 @@
|
|
1
|
+
module CustomTable
|
2
|
+
module ApplicationHelper
|
3
|
+
|
4
|
+
# MOVE TO GENERATED HELPER
|
5
|
+
# def boolean_icon(value, true_value = nil, false_value = nil)
|
6
|
+
# capture do
|
7
|
+
# concat content_tag(:i, "", class: (value ? "bi bi-check-lg text-success" : "bi bi-x-lg text-danger"), data: {raw: value})
|
8
|
+
# concat content_tag(:span, value ? true_value : false_value, class: "ms-1") unless true_value.nil?
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
|
12
|
+
def custom_table_form_for(record, options = {}, &block)
|
13
|
+
options[:url] = request.path if options[:url].nil?
|
14
|
+
options[:method] = :get
|
15
|
+
|
16
|
+
options[:html] ||= {}
|
17
|
+
options[:html][:class] = "row row-cols-md-auto g-3 align-items-center custom-table-filter"
|
18
|
+
options[:wrapper] = options[:wrapper] || :inline_form
|
19
|
+
|
20
|
+
options[:wrapper_mappings] = {
|
21
|
+
boolean: :inline_boolean,
|
22
|
+
check_boxes: :vertical_collection,
|
23
|
+
radio_buttons: :vertical_collection,
|
24
|
+
date: :inline_element,
|
25
|
+
select: :inline_select
|
26
|
+
}
|
27
|
+
|
28
|
+
simple_form_for(record, options, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def fields_table_for(collection, options = {}, &block); end
|
32
|
+
|
33
|
+
# Tries to fetch attribute value by all possible ways (by priority):
|
34
|
+
# Helper {singular_model_name}_{field}_field
|
35
|
+
# Helper {singular_model_name}_{field}
|
36
|
+
# Helper {singular_model_name}_{field}_raw
|
37
|
+
# Attribute of model
|
38
|
+
def field_value_for item, field, definitions: nil, variant: nil
|
39
|
+
|
40
|
+
defs = definitions
|
41
|
+
|
42
|
+
model_name = item.model_name.singular
|
43
|
+
global_model_name = item.class.model_name.singular # non-model
|
44
|
+
|
45
|
+
helpers = []
|
46
|
+
helpers += [defs[:helper]] if !defs.nil?
|
47
|
+
|
48
|
+
helpers += [
|
49
|
+
"#{model_name}_#{variant}_#{field}_field",
|
50
|
+
"#{model_name}_#{variant}_#{field}",
|
51
|
+
] if !variant.nil?
|
52
|
+
|
53
|
+
helpers += [
|
54
|
+
"#{model_name}_#{field}_field",
|
55
|
+
"#{model_name}_#{field}",
|
56
|
+
"#{model_name}_#{field}_raw",
|
57
|
+
"#{global_model_name}_#{field}"
|
58
|
+
]
|
59
|
+
|
60
|
+
if item.class.superclass.to_s != "ApplicationRecord"
|
61
|
+
super_model_name = item.class.superclass.model_name.singular
|
62
|
+
helpers += [
|
63
|
+
"#{super_model_name}_#{field}_field",
|
64
|
+
"#{super_model_name}_#{field}",
|
65
|
+
"#{super_model_name}_#{field}_raw",
|
66
|
+
]
|
67
|
+
end
|
68
|
+
|
69
|
+
helpers = helpers.flatten.compact
|
70
|
+
|
71
|
+
helpers.each do |helper|
|
72
|
+
if self.class.method_defined?(helper)
|
73
|
+
if self.method(helper).arity == 1 || self.method(helper).arity == -2
|
74
|
+
return self.send(helper, item) || not_set
|
75
|
+
end
|
76
|
+
if self.method(helper).arity == 2
|
77
|
+
return self.send(helper, item, field) || not_set
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if !defs.nil? && defs[:amount]
|
83
|
+
if !item.class.columns_hash[field.to_s].nil? && item.class.columns_hash[field.to_s].type == :integer
|
84
|
+
return amount_value(item.send(field), 0) rescue ""
|
85
|
+
else
|
86
|
+
return amount(item.send(field)) rescue ""
|
87
|
+
end
|
88
|
+
else
|
89
|
+
if item.class.reflect_on_association(field)
|
90
|
+
return not_set if (item.send(field) rescue nil).nil?
|
91
|
+
return render(item.send(field)) rescue item.send(field).to_s rescue ""
|
92
|
+
elsif item.class.columns_hash[field.to_s] && item.class.columns_hash[field.to_s].type == :boolean
|
93
|
+
return boolean_icon(item.send(field)) rescue ""
|
94
|
+
elsif item.class.defined_enums.has_key?(field.to_s)
|
95
|
+
return (item.send("human_#{field}") rescue (item.send(field).presence || not_set)).to_s rescue ""
|
96
|
+
elsif item.class.columns_hash[field.to_s] && [:date, :datetime].include?(item.class.columns_hash[field.to_s].type)
|
97
|
+
return (item.send(field).blank? ? not_set : l(item.send(field), format: :short)) rescue ""
|
98
|
+
elsif item.class.columns_hash[field.to_s] && [:integer, :float, :decimal].include?(item.class.columns_hash[field.to_s].type)
|
99
|
+
return not_set if (item.send(field) rescue nil).nil?
|
100
|
+
return amount(item.send(field)) rescue ""
|
101
|
+
else
|
102
|
+
return (item.send(field).presence || not_set).to_s rescue ""
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
def custom_table_totals collection, fields, totals = {}, fields_totals = {}
|
109
|
+
|
110
|
+
out = {}
|
111
|
+
totals = {} if totals.nil?
|
112
|
+
fields_totals = {} if fields_totals.nil?
|
113
|
+
|
114
|
+
fields.keys.each do |field|
|
115
|
+
|
116
|
+
# We only work if field total is enabled explicitly with definition or totals
|
117
|
+
next if !fields[field][:total] && !totals.has_key?(field)
|
118
|
+
|
119
|
+
# Taking pre-defined value if available
|
120
|
+
if !totals[field].nil?
|
121
|
+
out[field] = totals[field]
|
122
|
+
else
|
123
|
+
model_class = collection.model
|
124
|
+
# Trying to find helper first
|
125
|
+
model_name = model_class.model_name.singular
|
126
|
+
helper = "#{model_name}_#{field}_total"
|
127
|
+
|
128
|
+
if self.class.method_defined?(helper)
|
129
|
+
# Better use helper!
|
130
|
+
out[field] = self.send(helper, collection.except(:limit, :offset, :order, :group))
|
131
|
+
else
|
132
|
+
if collection.respond_to?(:total_pages) && (!model_class.columns_hash[field.to_s].nil? || !fields[field][:total_scope].nil?)
|
133
|
+
# We can try to sum value from database
|
134
|
+
|
135
|
+
if fields[field][:total_scope].nil?
|
136
|
+
out[field] = collection.except(:limit, :offset, :order, :group).sum(field)
|
137
|
+
else
|
138
|
+
out[field] = collection.except(:limit, :offset, :order, :group).send(fields[field][:total_scope])
|
139
|
+
end
|
140
|
+
else
|
141
|
+
# Taking simple summed value because all data is shown
|
142
|
+
out[field] = fields_totals[field]
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
out
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
# Same as above but for Export only
|
155
|
+
def raw_field_value_for item, field, definitions: nil, variant: nil
|
156
|
+
|
157
|
+
defs = definitions
|
158
|
+
|
159
|
+
model_name = item.model_name.singular
|
160
|
+
global_model_name = item.class.model_name.singular
|
161
|
+
|
162
|
+
helpers = []
|
163
|
+
|
164
|
+
helpers += [
|
165
|
+
"#{model_name}_#{variant}_#{field}_field_raw",
|
166
|
+
"#{model_name}_#{variant}_#{field}_raw",
|
167
|
+
] if !variant.nil?
|
168
|
+
|
169
|
+
helpers += [
|
170
|
+
"#{model_name}_#{field}_field_raw",
|
171
|
+
"#{model_name}_#{field}_raw",
|
172
|
+
"#{global_model_name}_#{field}_raw"
|
173
|
+
]
|
174
|
+
|
175
|
+
if item.class.superclass.to_s != "ApplicationRecord"
|
176
|
+
super_model_name = item.class.superclass.model_name.singular
|
177
|
+
helpers += [
|
178
|
+
"#{super_model_name}_#{field}_field_raw",
|
179
|
+
"#{super_model_name}_#{field}_raw",
|
180
|
+
]
|
181
|
+
end
|
182
|
+
|
183
|
+
helpers = helpers.flatten.compact
|
184
|
+
|
185
|
+
helpers.each do |helper|
|
186
|
+
return self.send(helper, item) if self.class.method_defined?(helper)
|
187
|
+
end
|
188
|
+
|
189
|
+
if !defs.nil? && defs[:amount]
|
190
|
+
return item.send(field) rescue nil
|
191
|
+
else
|
192
|
+
if item.class.reflect_on_association(field)
|
193
|
+
return item.send(field).to_s rescue nil
|
194
|
+
elsif item.class.columns_hash[field.to_s] && item.class.columns_hash[field.to_s].type == :boolean
|
195
|
+
return item.send(field) rescue nil
|
196
|
+
elsif item.class.defined_enums.has_key?(field.to_s)
|
197
|
+
return (item.send(field).presence) rescue nil
|
198
|
+
elsif item.class.columns_hash[field.to_s] && [:date, :datetime].include?(item.class.columns_hash[field.to_s].type)
|
199
|
+
return (item.send(field).presence) rescue nil
|
200
|
+
elsif item.class.columns_hash[field.to_s] && [:float, :decimal].include?(item.class.columns_hash[field.to_s].type)
|
201
|
+
return item.send(field).round(2) rescue nil
|
202
|
+
elsif item.class.columns_hash[field.to_s] && [:integer].include?(item.class.columns_hash[field.to_s].type)
|
203
|
+
return item.send(field) rescue nil
|
204
|
+
else
|
205
|
+
return (item.send(field).presence) rescue nil
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
# Returns list of fields to show in table according user settings
|
213
|
+
def custom_table_fields_for(model, variant: nil, current_search: {}, predefined_fields: nil, use_all_fields: false)
|
214
|
+
|
215
|
+
fields = []
|
216
|
+
current_search = {} if current_search.nil?
|
217
|
+
|
218
|
+
model_fields = custom_table_fields_definition_for(model, variant)
|
219
|
+
|
220
|
+
if (!predefined_fields.nil?)
|
221
|
+
out = {}
|
222
|
+
predefined_fields.each do |f|
|
223
|
+
out[f] = model_fields[f]
|
224
|
+
end
|
225
|
+
return out.compact
|
226
|
+
# return model_fields.select {|k,v| predefined_fields.include?(k) }
|
227
|
+
end
|
228
|
+
|
229
|
+
fields_key = model.model_name.to_s
|
230
|
+
fields_key += "-#{variant}" unless variant.nil?
|
231
|
+
|
232
|
+
s = custom_table_user_customized_fields_for(model, variant)
|
233
|
+
|
234
|
+
use_all_fields = true if params[:custom_table_use_all_fields]
|
235
|
+
|
236
|
+
if use_all_fields
|
237
|
+
selected_fields = model_fields.keys
|
238
|
+
else
|
239
|
+
if !s.nil?
|
240
|
+
# Use saved user settings for the model
|
241
|
+
always_selected_fields = model_fields.select { |x, y| [:always].include?(y[:appear]) || current_search.keys.include?(x.to_s) }.keys
|
242
|
+
always_selected_fields.each {|f| s = {"#{f.to_s}": true}.merge(s) if s[f].nil? }
|
243
|
+
selected_fields = s.select{|f,v| v }.keys
|
244
|
+
else
|
245
|
+
# Use default model settings
|
246
|
+
selected_fields = model_fields.select { |x, y| [:always, :default].include?(y[:appear]) || current_search.keys.include?(x.to_s) }.keys
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Filter selection again with model settings for sure
|
251
|
+
return selected_fields.collect{|f| [f, model_fields[f]]}.to_h
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns list of fields for customization form
|
256
|
+
def custom_table_fields_settings_for(model, variant: nil)
|
257
|
+
|
258
|
+
model_fields = custom_table_fields_definition_for(model, variant)
|
259
|
+
model_fields = model_fields.reject {|k,v| [:export, :never].include?(v[:appear]) }
|
260
|
+
|
261
|
+
fields_key = model.model_name.to_s
|
262
|
+
fields_key += "-#{variant}" unless variant.nil?
|
263
|
+
|
264
|
+
user_customization = custom_table_user_customized_fields_for(model, variant)
|
265
|
+
|
266
|
+
if !user_customization.nil?
|
267
|
+
# Add new fields at the top to user customization if not present
|
268
|
+
model_fields.each do |f, v|
|
269
|
+
next if !user_customization[f].nil?
|
270
|
+
selected = [:always, :default].include?(v[:appear])
|
271
|
+
new_field = {"#{f.to_s}": selected}
|
272
|
+
if selected
|
273
|
+
user_customization = new_field.merge(user_customization)
|
274
|
+
else
|
275
|
+
user_customization = user_customization.merge(new_field)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
return user_customization.reject{|f,v| model_fields[f].nil?}.collect{|f,v| [f, {selected: v}.merge(model_fields[f])]}.to_h
|
279
|
+
else
|
280
|
+
# Use default model settings
|
281
|
+
# abort model_fields.transform_values!{|f| f[:selected] = true}.inspect
|
282
|
+
return model_fields.each{|k,f| f[:selected] = [:always, :default].include?(f[:appear])}
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
# Prepares object of user fields customization
|
289
|
+
def custom_table_user_customized_fields_for(model, variant = nil)
|
290
|
+
fields_key = custom_table_fields_key(model, variant)
|
291
|
+
defs = custom_table_fields_definition_for(model, variant)
|
292
|
+
return nil if current_user.nil? || current_user.custom_table.nil? || current_user.custom_table.dig(fields_key, :fields).nil?
|
293
|
+
return current_user.custom_table.dig(fields_key, :fields).symbolize_keys.reject{|k,v| defs[k.to_sym].nil?}
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
# Prepares object of user search customization
|
298
|
+
def custom_table_user_customization_for(model, variant = nil)
|
299
|
+
fields_key = custom_table_fields_key(model, variant)
|
300
|
+
return nil if current_user.nil? || current_user.custom_table.nil?
|
301
|
+
return current_user.custom_table[fields_key]&.symbolize_keys
|
302
|
+
end
|
303
|
+
|
304
|
+
def custom_table_fields_key(model, variant = nil)
|
305
|
+
fields_key = model.model_name.to_s
|
306
|
+
fields_key += "-#{variant}" unless variant.nil?
|
307
|
+
return fields_key
|
308
|
+
end
|
309
|
+
|
310
|
+
def custom_table_customizable_fields_for(model, variant = nil)
|
311
|
+
model_fields = custom_table_fields_definition_for(model, variant)
|
312
|
+
model_fields.reject {|k,v| [:always, :export, :never].include?(v[:appear]) }
|
313
|
+
end
|
314
|
+
|
315
|
+
# Base definition for model
|
316
|
+
def custom_table_fields_definition_for(model, variant = nil)
|
317
|
+
helper_name = "#{model.model_name.singular}_custom_table_fields"
|
318
|
+
if (! self.class.method_defined?(helper_name))
|
319
|
+
raise "#{helper_name} helper is not defined so we do not know how to render custom_table for #{model}"
|
320
|
+
end
|
321
|
+
|
322
|
+
if variant.nil? || method(helper_name).parameters.empty?
|
323
|
+
defs = self.send("#{helper_name}")
|
324
|
+
else
|
325
|
+
defs = self.send("#{helper_name}", variant)
|
326
|
+
end
|
327
|
+
|
328
|
+
return defs.each{|x,y| y[:label] = model.human_attribute_name(x) if y[:label].nil? }
|
329
|
+
end
|
330
|
+
|
331
|
+
# Base definition for model
|
332
|
+
def custom_table_fields_definition_for_field(model, field, variant = nil)
|
333
|
+
helper_name = "#{model.model_name.singular}_custom_table_fields"
|
334
|
+
if (! self.class.method_defined?(helper_name))
|
335
|
+
raise "#{helper_name} helper is not defined so we do not know how to render custom_table for #{model}"
|
336
|
+
end
|
337
|
+
if variant.nil? || method(helper_name).parameters.empty?
|
338
|
+
defs = self.send("#{helper_name}")
|
339
|
+
else
|
340
|
+
defs = self.send("#{helper_name}", variant)
|
341
|
+
end
|
342
|
+
return nil if defs[field].nil?
|
343
|
+
defs[:label] = model.human_attribute_name(field) if defs[:label].nil?
|
344
|
+
return defs
|
345
|
+
end
|
346
|
+
|
347
|
+
|
348
|
+
# Returns true if model can be customized by current user at least by one field
|
349
|
+
def current_user_has_customizable_fields_for?(model, variant=nil)
|
350
|
+
return false if current_user.nil?
|
351
|
+
custom_table_customizable_fields_for(model, variant).count.positive?
|
352
|
+
end
|
353
|
+
|
354
|
+
def custom_table_data collection, variant=nil, **params
|
355
|
+
|
356
|
+
params[:collection] = collection
|
357
|
+
params[:variant] = variant
|
358
|
+
params[:paginate] = true if params[:paginate]!=false
|
359
|
+
params[:last_page] = true if params[:last_page]!=false
|
360
|
+
params[:namespace] = (controller.class.module_parent == Object) ? nil : controller.class.module_parent.to_s.underscore.to_sym
|
361
|
+
params[:force_edit_button] = false if params[:force_edit_button].nil?
|
362
|
+
params[:modal_edit] = true if params[:modal_edit].nil?
|
363
|
+
|
364
|
+
render "custom_table/table", params do
|
365
|
+
yield
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# Data for updating values via turbo according object id and field name
|
370
|
+
def custom_table_row_data item, variant = nil, **params
|
371
|
+
|
372
|
+
params[:item] = item
|
373
|
+
params[:variant] = variant
|
374
|
+
params[:namespace] = (controller.class.module_parent == Object) ? nil : controller.class.module_parent.to_s.underscore.to_sym
|
375
|
+
|
376
|
+
render "custom_table/table_row_data", params do
|
377
|
+
yield
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def custom_table_filter search_model, variant=nil, **params, &block
|
382
|
+
|
383
|
+
params[:search_model] = search_model
|
384
|
+
params[:variant] = variant
|
385
|
+
render "custom_table/filter", params do |f|
|
386
|
+
yield if !block.nil?
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# Rounded
|
391
|
+
def amount_round(c)
|
392
|
+
content_tag(:span, amount_value(c, 0), "data-raw": (c.blank? ? nil : c.round))
|
393
|
+
end
|
394
|
+
|
395
|
+
# Base
|
396
|
+
def amount(c)
|
397
|
+
content_tag(:span, amount_value(c), "data-raw": (c.blank? ? nil : c.round(2)))
|
398
|
+
end
|
399
|
+
|
400
|
+
# Abstract
|
401
|
+
def amount_value(c, p = 2)
|
402
|
+
number_to_currency(c, precision: p, locale: :en, unit: "")
|
403
|
+
end
|
404
|
+
|
405
|
+
# Colored
|
406
|
+
def amount_color(c)
|
407
|
+
content_tag(:span, amount(c), class: ["amount", (c.to_f >= 0 ? "positive" : "negative")])
|
408
|
+
end
|
409
|
+
|
410
|
+
# Colored rounded
|
411
|
+
def amount_round_color(c)
|
412
|
+
content_tag(:span, amount_round(c), class: ["amount", (c.to_f >= 0 ? "positive" : "negative")])
|
413
|
+
end
|
414
|
+
|
415
|
+
def custom_table_download_button collection, **p
|
416
|
+
p[:collection] = collection
|
417
|
+
p[:downloads] ||= {}
|
418
|
+
p[:downloads].unshift({title: t("custom_table.download_as_csv"), href: params.permit!.merge({:format => :csv})}) if p[:csv]
|
419
|
+
render "custom_table/download", p
|
420
|
+
end
|
421
|
+
|
422
|
+
def custom_table_settings search_model, variant=nil, **params
|
423
|
+
|
424
|
+
params[:search_model] = search_model
|
425
|
+
params[:variant] = variant
|
426
|
+
|
427
|
+
render "custom_table/settings", params
|
428
|
+
|
429
|
+
end
|
430
|
+
|
431
|
+
def custom_table_settings_button search_model, variant=nil, size: "sm"
|
432
|
+
link_to custom_table.edit_setting_path(search_model.model_name, variant: variant), :class => "btn btn-outline-primary btn-#{size}", data: {"turbo-frame": "remote-modal"} do
|
433
|
+
custom_table_settings_icon
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def custom_table_variants_for model
|
438
|
+
helper_name = "#{model.model_name.singular}_custom_table_variants"
|
439
|
+
return self.send(helper_name) if self.class.method_defined?(helper_name)
|
440
|
+
return []
|
441
|
+
end
|
442
|
+
|
443
|
+
def tree_opener item_id, has_children=false, expanded=false
|
444
|
+
content_tag :span, class: "tree-opener #{expanded ? 'opened' : ''}", data: {action: (has_children ? "click->table#toggle" : ""), "table-css-param": ".child-of-#{item_id}"} do
|
445
|
+
concat content_tag(:span, (has_children ? "▶" : "▷"), class: "closed")
|
446
|
+
concat content_tag(:span, "▼", class: "opened")
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def custom_table_fields_totals fields:, totals:, item:, fields_totals:, variant:
|
451
|
+
fields.each do |field, defs|
|
452
|
+
if !totals.nil? && totals.has_key?(field) && totals[field].nil? # Auto-counting
|
453
|
+
fields_totals[field] = 0 if fields_totals[field].nil?
|
454
|
+
fields_totals[field] += raw_field_value_for(item, field, definitions: defs, variant: variant).to_f rescue 0
|
455
|
+
end
|
456
|
+
end
|
457
|
+
return fields_totals
|
458
|
+
end
|
459
|
+
|
460
|
+
def custom_table_batch_selector_check_box item, **params
|
461
|
+
|
462
|
+
# abort params.inspect
|
463
|
+
|
464
|
+
params[:param] = "#{item.model_name.plural}[]" if params[:param].nil?
|
465
|
+
params[:data] = {"toggle-target": "checkbox", "batch-actions-target": "checkbox", "action": "toggle#recalculateToggler batch-actions#refresh"}
|
466
|
+
|
467
|
+
check_box_tag params[:param], item.id, (!params[item.model_name.plural.to_sym].blank?) && (params[item.model_name.plural.to_sym].include?(item.id.to_s)), params
|
468
|
+
end
|
469
|
+
|
470
|
+
# Gets array of fields and results in object of available fields and definitions from fields from list
|
471
|
+
def custom_table_fields_list_to_definitions model, fields
|
472
|
+
d = custom_table_fields_definition_for(model)
|
473
|
+
out = {}
|
474
|
+
fields.each do |f|
|
475
|
+
found = d.find {|k, v| k == f }
|
476
|
+
out[f] = found[1] if !found.nil?
|
477
|
+
end
|
478
|
+
return out
|
479
|
+
end
|
480
|
+
|
481
|
+
end
|
482
|
+
|
483
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module CustomTable
|
2
|
+
|
3
|
+
module FieldsetHelper
|
4
|
+
|
5
|
+
class FieldsetBuilder
|
6
|
+
|
7
|
+
def initialize(object, template, **params)
|
8
|
+
@object = object
|
9
|
+
@template = template
|
10
|
+
@params = params
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_editable?
|
14
|
+
@template.respond_to?(:editable)
|
15
|
+
end
|
16
|
+
|
17
|
+
def field column, **params, &block
|
18
|
+
|
19
|
+
defs = custom_table_fields_definition_for_field(@object.class, column) rescue nil
|
20
|
+
|
21
|
+
params = {} if params.nil?
|
22
|
+
params = params.deep_merge(@params)
|
23
|
+
params[:editable] = true if params[:editable].nil?
|
24
|
+
params[:adaptive] = false if params[:adaptive].nil?
|
25
|
+
|
26
|
+
if params[:label].nil?
|
27
|
+
if !defs.nil? && !defs[:label].blank?
|
28
|
+
params[:label] = defs[:label].nil?
|
29
|
+
else
|
30
|
+
params[:label] = @object.class.human_attribute_name(column)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
params[:template] = "field" if params[:template].nil?
|
35
|
+
params[:column] = column
|
36
|
+
params[:object] = @object
|
37
|
+
|
38
|
+
hint_key = "simple_form.hints.#{@object.model_name.singular}.#{column}"
|
39
|
+
if I18n.exists?(hint_key)
|
40
|
+
params[:hint] = I18n.t(hint_key)
|
41
|
+
end
|
42
|
+
|
43
|
+
@template.render "custom_table/#{params[:template]}", **params do
|
44
|
+
if params[:editable] && has_editable?
|
45
|
+
params[:editable_params] = {} if params[:editable_params].nil?
|
46
|
+
editable_field = params[:editable_params][:field] || column
|
47
|
+
@template.editable @object, editable_field, **params[:editable_params] do
|
48
|
+
if block_given?
|
49
|
+
yield
|
50
|
+
else
|
51
|
+
@template.field_value_for(@object, column)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
else
|
55
|
+
if block_given?
|
56
|
+
yield
|
57
|
+
else
|
58
|
+
@template.field_value_for(@object, column)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
def fieldset object=nil, **params, &block
|
68
|
+
|
69
|
+
builder = FieldsetBuilder.new(object, self, **params)
|
70
|
+
output = capture(builder, &block)
|
71
|
+
|
72
|
+
render "custom_table/fieldset" do
|
73
|
+
output
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def field object, column, **params, &block
|
79
|
+
|
80
|
+
params[:template] = "field_plain"
|
81
|
+
|
82
|
+
builder = FieldsetBuilder.new(object, self, **params)
|
83
|
+
|
84
|
+
builder.field(column, **params, &block)
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CustomTable
|
2
|
+
|
3
|
+
module IconsHelper
|
4
|
+
|
5
|
+
def custom_table_download_icon
|
6
|
+
return custom_table_icon("fa fa-download") if CustomTable.configuration.icons_framework == :fa
|
7
|
+
return custom_table_icon("bi bi-download") if CustomTable.configuration.icons_framework == :bi
|
8
|
+
end
|
9
|
+
|
10
|
+
def custom_table_settings_icon
|
11
|
+
return custom_table_icon("fa fa-cog") if CustomTable.configuration.icons_framework == :fa
|
12
|
+
return custom_table_icon("bi bi-gear") if CustomTable.configuration.icons_framework == :bi
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def custom_table_move_icon_class
|
17
|
+
return "fa fa-sort" if CustomTable.configuration.icons_framework == :fa
|
18
|
+
return "bi bi-arrow-down-up" if CustomTable.configuration.icons_framework == :bi
|
19
|
+
end
|
20
|
+
|
21
|
+
def custom_table_tree_child_icon_class
|
22
|
+
return "fa fa-arrow-right" if CustomTable.configuration.icons_framework == :fa
|
23
|
+
return "bi bi-arrow-return-right" if CustomTable.configuration.icons_framework == :bi
|
24
|
+
end
|
25
|
+
|
26
|
+
def custom_table_cancel_icon
|
27
|
+
return custom_table_icon("fa fa-ban") if CustomTable.configuration.icons_framework == :fa
|
28
|
+
return custom_table_icon("bi bi-slash-circle") if CustomTable.configuration.icons_framework == :bi
|
29
|
+
end
|
30
|
+
|
31
|
+
def custom_table_search_icon
|
32
|
+
return custom_table_icon("fa fa-search") if CustomTable.configuration.icons_framework == :fa
|
33
|
+
return custom_table_icon("bi bi-search") if CustomTable.configuration.icons_framework == :bi
|
34
|
+
end
|
35
|
+
|
36
|
+
def custom_table_icon c
|
37
|
+
return content_tag(:i, "", class: c)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class DatePickerInput < SimpleForm::Inputs::StringInput
|
2
|
+
def input(wrapper_options)
|
3
|
+
set_html_options
|
4
|
+
set_value_html_option
|
5
|
+
|
6
|
+
classes = ["input-group"]
|
7
|
+
|
8
|
+
input_html_options[:data] ||= {}
|
9
|
+
input_html_options[:data]["controller"] = "flatpickr"
|
10
|
+
|
11
|
+
picker_options = (options[:picker] || {}).merge!(flatpickr_options)
|
12
|
+
|
13
|
+
picker_options.each do |k, v|
|
14
|
+
input_html_options[:data]["flatpickr-#{k}-value"] = v
|
15
|
+
end
|
16
|
+
|
17
|
+
input_html_options[:data]["flatpickr-locale-value"] = I18n.locale
|
18
|
+
|
19
|
+
classes.push("input-group-sm") if input_html_options[:class].include? "input-sm"
|
20
|
+
|
21
|
+
# template.content_tag :div, class: classes do
|
22
|
+
input = super(wrapper_options) # leave StringInput do the real rendering
|
23
|
+
# end
|
24
|
+
end
|
25
|
+
|
26
|
+
def input_html_classes
|
27
|
+
super.push '' # 'form-control'
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def set_html_options
|
33
|
+
input_html_options[:type] = 'text'
|
34
|
+
input_html_options[:data] ||= {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_value_html_option
|
38
|
+
# return unless value.present?
|
39
|
+
# input_html_options[:value] ||= I18n.localize(value, format: display_pattern)
|
40
|
+
end
|
41
|
+
|
42
|
+
def value
|
43
|
+
v = object.send(attribute_name) if object.respond_to?(attribute_name) || object.is_a?(Ransack::Search)
|
44
|
+
v = v.to_date if v.is_a?(String)
|
45
|
+
v
|
46
|
+
end
|
47
|
+
|
48
|
+
def flatpickr_options
|
49
|
+
{}
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|