active_scaffold 4.3.2 → 4.4.1

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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rdoc +26 -0
  3. data/README.md +4 -6
  4. data/app/assets/javascripts/active_scaffold/active_scaffold.js +104 -12
  5. data/app/assets/javascripts/active_scaffold/draggable_lists.js +4 -3
  6. data/app/assets/javascripts/active_scaffold/load.js +20 -5
  7. data/app/assets/stylesheets/active_scaffold/_images.scss +53 -1
  8. data/app/assets/stylesheets/active_scaffold/_layout.scss +1 -66
  9. data/app/views/active_scaffold_overrides/_add_existing_form.html.erb +17 -11
  10. data/app/views/active_scaffold_overrides/_base_form.html.erb +29 -19
  11. data/app/views/active_scaffold_overrides/_field_search.html.erb +9 -9
  12. data/app/views/active_scaffold_overrides/_field_search_columns.html.erb +29 -33
  13. data/app/views/active_scaffold_overrides/_form.html.erb +43 -34
  14. data/app/views/active_scaffold_overrides/_form_association.html.erb +3 -3
  15. data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +20 -16
  16. data/app/views/active_scaffold_overrides/_form_association_record.html.erb +3 -4
  17. data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +2 -1
  18. data/app/views/active_scaffold_overrides/_list.html.erb +6 -4
  19. data/app/views/active_scaffold_overrides/_list_calculations.html.erb +1 -1
  20. data/app/views/active_scaffold_overrides/_list_header.html.erb +3 -3
  21. data/app/views/active_scaffold_overrides/_list_messages.html.erb +19 -19
  22. data/app/views/active_scaffold_overrides/_list_messages_content.html.erb +5 -5
  23. data/app/views/active_scaffold_overrides/_list_pagination.html.erb +7 -5
  24. data/app/views/active_scaffold_overrides/_list_pagination_links.html.erb +5 -3
  25. data/app/views/active_scaffold_overrides/_list_record.html.erb +13 -8
  26. data/app/views/active_scaffold_overrides/_list_with_header.html.erb +8 -8
  27. data/app/views/active_scaffold_overrides/_messages.html.erb +3 -3
  28. data/app/views/active_scaffold_overrides/_render_field.js.erb +8 -11
  29. data/app/views/active_scaffold_overrides/_search.html.erb +7 -6
  30. data/app/views/active_scaffold_overrides/_show.html.erb +4 -4
  31. data/app/views/active_scaffold_overrides/_show_actions.html.erb +2 -2
  32. data/app/views/active_scaffold_overrides/_show_columns.html.erb +28 -30
  33. data/app/views/active_scaffold_overrides/add_existing.js.erb +7 -4
  34. data/app/views/active_scaffold_overrides/render_field.js.erb +1 -1
  35. data/config/locales/de.yml +2 -1
  36. data/config/locales/en.yml +33 -32
  37. data/config/locales/es.yml +15 -14
  38. data/config/locales/fr.yml +3 -2
  39. data/config/locales/hu.yml +3 -2
  40. data/config/locales/ja.yml +2 -1
  41. data/config/locales/ro.yml +169 -0
  42. data/config/locales/ru.yml +8 -7
  43. data/config/locales/ta.yml +7 -6
  44. data/config/locales/zh_Hans.yml +14 -13
  45. data/lib/active_scaffold/actions/common_search.rb +1 -14
  46. data/lib/active_scaffold/actions/core.rb +55 -16
  47. data/lib/active_scaffold/actions/field_search.rb +2 -2
  48. data/lib/active_scaffold/actions/list.rb +35 -13
  49. data/lib/active_scaffold/actions/nested.rb +31 -7
  50. data/lib/active_scaffold/actions/show.rb +1 -1
  51. data/lib/active_scaffold/active_record_permissions.rb +1 -1
  52. data/lib/active_scaffold/attribute_params.rb +2 -1
  53. data/lib/active_scaffold/bridges/date_picker/helper.rb +14 -0
  54. data/lib/active_scaffold/bridges/date_picker.rb +10 -0
  55. data/lib/active_scaffold/config/list.rb +10 -1
  56. data/lib/active_scaffold/core.rb +9 -6
  57. data/lib/active_scaffold/data_structures/action_columns.rb +2 -0
  58. data/lib/active_scaffold/data_structures/column.rb +27 -6
  59. data/lib/active_scaffold/data_structures/filter.rb +4 -0
  60. data/lib/active_scaffold/data_structures/nested_info.rb +8 -1
  61. data/lib/active_scaffold/data_structures/sorting.rb +1 -1
  62. data/lib/active_scaffold/engine.rb +6 -0
  63. data/lib/active_scaffold/extensions/action_view_rendering.rb +1 -1
  64. data/lib/active_scaffold/extensions/active_record_session_store.rb +49 -0
  65. data/lib/active_scaffold/finder.rb +73 -21
  66. data/lib/active_scaffold/helpers/action_link_helpers.rb +43 -18
  67. data/lib/active_scaffold/helpers/assets_helpers.rb +4 -4
  68. data/lib/active_scaffold/helpers/filter_helpers.rb +3 -3
  69. data/lib/active_scaffold/helpers/form_column_helpers.rb +34 -22
  70. data/lib/active_scaffold/helpers/form_ui_helpers.rb +15 -10
  71. data/lib/active_scaffold/helpers/framework_ui_helpers.rb +22 -0
  72. data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -0
  73. data/lib/active_scaffold/helpers/list_column_helpers.rb +19 -0
  74. data/lib/active_scaffold/helpers/pagination_helpers.rb +28 -7
  75. data/lib/active_scaffold/helpers/search_column_helpers.rb +32 -5
  76. data/lib/active_scaffold/helpers/tabs_helpers.rb +7 -7
  77. data/lib/active_scaffold/helpers/view_helpers.rb +12 -0
  78. data/lib/active_scaffold/tableless.rb +11 -8
  79. data/lib/active_scaffold/version.rb +2 -2
  80. data/lib/active_scaffold.rb +169 -0
  81. data/lib/generators/active_scaffold/install/install_generator.rb +216 -111
  82. data/lib/tasks/bundle.rake +2 -1
  83. data/lib/tasks/mock_app.rake +43 -0
  84. metadata +21 -7
  85. data/shoulda_macros/macros.rb +0 -145
@@ -79,14 +79,16 @@ module ActiveScaffold
79
79
  end
80
80
 
81
81
  def active_scaffold_subform_record_actions(association_column, record, locked, scope)
82
- return unless association_column.association.collection? && !locked
82
+ # association_column nil when called for batch_create for example
83
+ return if locked || association_column&.association&.singular?
83
84
 
84
- auth = %i[destroy delete_all delete].exclude?(association_column.association.dependent)
85
+ auth = %i[destroy delete_all delete].exclude?(association_column&.association&.dependent)
85
86
  auth, reason = record.authorized_for?(crud_type: :delete, reason: true) unless auth
86
87
  if auth
87
- link_to(as_(:remove), '#', class: 'destroy')
88
+ attributes = as_element_attributes(:subform_record_remove, class: 'destroy')
89
+ link_to(as_(:remove), '#', attributes)
88
90
  else
89
- content_tag :span, reason, class: 'destroy reason'
91
+ as_element(:subform_record_remove_reason, reason)
90
92
  end
91
93
  end
92
94
 
@@ -121,7 +123,9 @@ module ActiveScaffold
121
123
 
122
124
  collapsible_id = "container_#{id_control}" if (column.form_ui_options || column.options)[:collapsible]
123
125
 
124
- {name: name, class: classes, id: id_control, collapsible_id: collapsible_id}.merge(options)
126
+ attributes = {name: name, class: classes, id: id_control, collapsible_id: collapsible_id}.merge(options)
127
+ context = {column: column, form_ui: column.form_ui}
128
+ as_element_attributes(:form_control, proc_options: context, **attributes)
125
129
  end
126
130
 
127
131
  def current_form_columns(record, scope, subform_controller = nil)
@@ -174,10 +178,10 @@ module ActiveScaffold
174
178
  {}
175
179
  end
176
180
 
177
- def render_subsection(column, record, scope, form_action, partial: 'form')
178
- subsection_id = sub_section_id(sub_section: column.label)
181
+ def render_subsection(column, record, scope, form_action, partial: 'form', subsection_id: nil, &)
182
+ subsection_id ||= sub_section_id(sub_section: column.label)
179
183
  locals = {columns: column, form_action: form_action, scope: scope}
180
- header = content_tag(:h5) do
184
+ header = as_element(:form_subsection_header) do
181
185
  h(column.label) <<
182
186
  link_to_visibility_toggle(subsection_id, default_visible: !column.collapsed)
183
187
  end
@@ -188,6 +192,8 @@ module ActiveScaffold
188
192
  render partial, locals.merge(subsection_id: "#{subsection_id}-#{tab_id}", tab_id: tab_id, tab_value: tab_value)
189
193
  end
190
194
  end
195
+ elsif block_given?
196
+ header << capture(subsection_id, &)
191
197
  else
192
198
  header << render(partial, locals.merge(subsection_id: subsection_id))
193
199
  end
@@ -217,6 +223,8 @@ module ActiveScaffold
217
223
  end
218
224
  end
219
225
 
226
+ def form_ui_class(form_ui) = form_ui
227
+
220
228
  def form_column_is_hidden?(column, record, scope = nil) # rubocop:disable Naming/PredicateMethod
221
229
  if column.clear_form_column_if.nil?
222
230
  condition_to_hide = column.hide_form_column_if
@@ -238,35 +246,38 @@ module ActiveScaffold
238
246
 
239
247
  def column_description(column, record, scope = nil)
240
248
  desc = column.description(record, scope)
241
- content_tag(:span, h(desc) + content_tag(:span, nil, class: 'close'), class: 'description') if desc.present?
249
+ as_element(:form_field_description, h(desc) + as_element(:form_field_description_close)) if desc.present?
242
250
  end
243
251
 
244
- def form_attribute(column, record, scope = nil, only_value = false, col_class = nil, form_columns: nil)
252
+ def form_attribute(column, record, scope = nil, only_value = false, col_class = nil, form_columns: nil, field: nil)
245
253
  column_options = active_scaffold_input_options(column, scope, object: record)
246
254
  collapsible_id = column_options.delete :collapsible_id
247
255
  attributes = field_attributes(column, record)
248
256
  attributes[:class] = "#{attributes[:class]} #{col_class}" if col_class.present?
249
257
  if only_value
250
- field = content_tag(:span, show_column_value(record, column), column_options.except(:name, :object))
258
+ field ||= content_tag(:span, show_column_value(record, column), column_options.except(:name, :object))
251
259
  if column.association.nil? || column.association.belongs_to?
252
260
  # hidden field probably not needed, but leaving it just in case
253
261
  # but it isn't working for assocations which are not belongs_to
254
- method = column.association ? column.association.foreign_key : column.name
255
- field << hidden_field(:record, method, column_options)
262
+ field = safe_join([field, hidden_field(:record, column.association&.foreign_key || column.name, column_options)])
256
263
  end
257
264
  else
258
- field = active_scaffold_input_for column, scope, column_options, form_columns: form_columns
259
- end
260
- if field
261
- field << loading_indicator_tag(action: :render_field, id: params[:id]) if column.update_columns
262
- desc = column_description(column, record, scope)
263
- field << desc if desc.present?
265
+ field ||= active_scaffold_input_for column, scope, column_options, form_columns: form_columns
264
266
  end
267
+ field = safe_join([field, loading_indicator_tag(action: :render_field, id: params[:id])]) if field && column.update_columns
268
+ description = column_description(column, record, scope)
265
269
 
266
270
  label = label_tag(label_for(column, column_options), form_column_label(column, record, scope))
267
271
  label << h(' ') << link_to_visibility_toggle(collapsible_id) if collapsible_id
268
- content_tag :dl, attributes do
269
- content_tag(:dt, label) << content_tag(:dd, field, id: collapsible_id)
272
+ form_attribute_html(column, label, field, description, attributes, collapsible_id: collapsible_id)
273
+ end
274
+
275
+ def form_attribute_html(column, label, field, description, attributes, collapsible_id: nil)
276
+ field = safe_join([field, description].compact) if description.present?
277
+ context = {column: column}
278
+ as_element :form_attribute, proc_options: context, **attributes do
279
+ as_element(:form_attribute_label, label, proc_options: context) <<
280
+ as_element(:form_attribute_field, field, proc_options: context, id: collapsible_id)
270
281
  end
271
282
  end
272
283
 
@@ -322,7 +333,8 @@ module ActiveScaffold
322
333
 
323
334
  # Should this column be displayed in the subform?
324
335
  def in_subform?(column, parent_record, parent_column)
325
- return true unless column.association
336
+ # parent_column nil when called for batch_create for example
337
+ return true if parent_column.nil? || column.association.nil?
326
338
 
327
339
  if column.association.reverse.nil?
328
340
  # Polymorphic associations can't appear because they *might* be the reverse association
@@ -27,18 +27,23 @@ module ActiveScaffold
27
27
 
28
28
  def active_scaffold_input_singular_association(column, html_options, options = {}, ui_options: column.options)
29
29
  record = html_options.delete(:object)
30
- associated = html_options.include?(:associated) ? html_options.delete(:associated) : record.send(column.association.name)
31
-
32
30
  helper_method = association_helper_method(column.association, :sorted_association_options_find)
33
31
  select_options = send(helper_method, column.association, nil, record)
34
- select_options.unshift(associated) if associated&.persisted? && select_options.exclude?(associated)
32
+
33
+ if html_options.include?(:associated)
34
+ options[:selected] ||= html_options.delete(:associated)
35
+ else
36
+ associated = record.send(column.association.name)
37
+ select_options.unshift(associated) if associated&.persisted? && select_options.exclude?(associated)
38
+ options[:selected] ||= associated&.id
39
+ end
35
40
 
36
41
  method = column.name
37
- options.merge! selected: associated&.id, include_blank: as_(:_select_), object: record
42
+ options.reverse_merge! include_blank: as_(:_select_), object: record
38
43
 
39
44
  html_options.merge!(ui_options[:html_options] || {})
40
45
  options.merge!(ui_options)
41
- html_options.delete(:multiple) # no point using multiple in a form for singular assoc, but may be set for field search
46
+ active_scaffold_select_name_with_multiple html_options # doesn't make sense most of time, but AS_batch use it
42
47
  active_scaffold_translate_select_options(options)
43
48
 
44
49
  html =
@@ -101,7 +106,7 @@ module ActiveScaffold
101
106
  url_options[:parent_model] ||= record.class.name
102
107
  url_options[:parent_column] ||= column.name
103
108
  url_options.reverse_merge! options[:url_options] if options[:url_options]
104
- link_to(link_text, url_options, remote: true, data: {position: :popup}, class: 'as_action')
109
+ link_to(link_text, url_options, as_element_attributes(:field_add_new_popup, remote: true, data: {position: :popup}, class: 'as_action'))
105
110
  end
106
111
 
107
112
  def active_scaffold_new_record_subform(column, record, html_options, options: {}, new_record_attributes: nil, locals: {}, skip_link: false)
@@ -141,7 +146,7 @@ module ActiveScaffold
141
146
  create_new = active_scaffold_add_new_text(options, :add_new_text, :create_new)
142
147
  data = {select_id: select_id, subform_id: subform_id, subform_text: add_existing, select_text: create_new}
143
148
  label = data[record.send(column.name)&.new_record? ? :subform_text : :select_text]
144
- link_to(label, '#', data: data, class: 'show-new-subform')
149
+ link_to(label, '#', as_element_attributes(:field_add_new_subform, data: data, class: 'show-new-subform'))
145
150
  end
146
151
 
147
152
  def active_scaffold_file_with_content(column, options, content, remove_file_prefix, controls_class, ui_options: column.options)
@@ -237,8 +242,8 @@ module ActiveScaffold
237
242
  end
238
243
  end
239
244
 
240
- def active_scaffold_checkbox_option(option, label_method, associated_ids, checkbox_options, li_options = {})
241
- content_tag(:li, li_options) do
245
+ def active_scaffold_checkbox_option(option, label_method, associated_ids, checkbox_options, item_options = {})
246
+ content_tag(:li, item_options) do
242
247
  option_id = option.is_a?(Array) ? option[1] : option.id
243
248
  label = option.is_a?(Array) ? option[0] : option.send(label_method)
244
249
  check_box_tag(checkbox_options[:name], option_id, associated_ids.include?(option_id), checkbox_options) <<
@@ -261,7 +266,7 @@ module ActiveScaffold
261
266
  html << content_tag(:ul, options.merge(class: "#{options[:class]} checkbox-list#{' draggable-lists' if draggable}")) do
262
267
  content = []
263
268
  select_options.each_with_index do |option, i|
264
- content << active_scaffold_checkbox_option(option, label_method, associated_ids, name: "#{options[:name]}[]", id: "#{options[:id]}_#{i}_id")
269
+ content << active_scaffold_checkbox_option(option, label_method, associated_ids, {name: "#{options[:name]}[]", id: "#{options[:id]}_#{i}_id"}, ui_options[:item_options] || {})
265
270
  end
266
271
  safe_join content
267
272
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveScaffold
4
+ module Helpers
5
+ module FrameworkUiHelpers
6
+ def as_element(key, content = nil, proc_options: {}, **options, &)
7
+ tag, attributes = as_tag_and_attributes(key, options, proc_options)
8
+ content_tag(tag, content, attributes, &)
9
+ end
10
+
11
+ def as_element_attributes(key, proc_options: {}, **options)
12
+ as_tag_and_attributes(key, options, proc_options).last
13
+ end
14
+
15
+ def as_tag_and_attributes(key, options, proc_options = {})
16
+ tag, attributes, proc = ActiveScaffold.ui_elements[key]&.values_at(:tag, :attributes, :proc)
17
+ tag, attributes = instance_exec(proc_options, &proc) if proc
18
+ [tag, attributes&.smart_merge(options) || options&.as_html_attrs]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -72,6 +72,7 @@ module ActiveScaffold
72
72
  alias active_scaffold_human_condition_time active_scaffold_human_condition_datetime
73
73
  alias active_scaffold_human_condition_date active_scaffold_human_condition_datetime
74
74
  alias active_scaffold_human_condition_timestamp active_scaffold_human_condition_datetime
75
+ alias active_scaffold_human_condition_timestamptz active_scaffold_human_condition_datetime
75
76
 
76
77
  def active_scaffold_human_condition_datetime_range_format(range_type, range)
77
78
  case range
@@ -431,6 +431,24 @@ module ActiveScaffold
431
431
  data
432
432
  end
433
433
 
434
+ def local_sorting?
435
+ @page&.pager&.number_of_pages == 1 && active_scaffold_config.list.local_sorting
436
+ end
437
+
438
+ def column_sort_value(record, column)
439
+ value = record.send(column.name)
440
+ return if value.nil?
441
+
442
+ case column.column.type
443
+ when :time, :datetime, Time then value.to_i
444
+ when :boolean then value ? 1 : 0
445
+ when :binary then nil
446
+ when :integer then value
447
+ when :float, :decimal then (value % 1).zero? ? value.to_i : value
448
+ else value.to_s
449
+ end
450
+ end
451
+
434
452
  # MARK
435
453
 
436
454
  def all_marked?
@@ -465,6 +483,7 @@ module ActiveScaffold
465
483
  elsif column.inplace_edit
466
484
  tag_options[:data] = inplace_edit_data(column)
467
485
  end
486
+
468
487
  content_tag(:th, column_heading_value(column, sorting, sort_direction) + inplace_edit_control(column), tag_options)
469
488
  end
470
489
 
@@ -3,8 +3,13 @@
3
3
  module ActiveScaffold
4
4
  module Helpers
5
5
  module PaginationHelpers
6
- def pagination_ajax_link(page_number, url_options, options)
7
- link_to page_number, url_options.merge(page: page_number), options.merge(class: 'as_paginate')
6
+ def pagination_ajax_link(page_number, url_options, options, text: nil)
7
+ attributes = as_element_attributes(
8
+ :pagination_link,
9
+ proc_options: {page_number: page_number, text: text},
10
+ **options.smart_merge(class: 'as_paginate')
11
+ )
12
+ link_to text ? as_(text) : page_number, url_options.merge(page: page_number), attributes
8
13
  end
9
14
 
10
15
  def pagination_url_options(url_options = nil)
@@ -17,6 +22,22 @@ module ActiveScaffold
17
22
  url_options
18
23
  end
19
24
 
25
+ def pagination_active_page(page_number, url_options, options)
26
+ attributes = options.slice(:id, :class).smart_merge(class: 'as_paginate current')
27
+ as_element :pagination_active_page, page_number,
28
+ proc_options: {page_number: page_number}, **attributes
29
+ end
30
+
31
+ def pagination_gap(options)
32
+ '..'
33
+ end
34
+
35
+ # Wraps the complete pagination sequence, including previous and next links.
36
+ # UI adapters may override this method and call +super+ to add nested markup.
37
+ def pagination_html(&)
38
+ capture(&)
39
+ end
40
+
20
41
  def pagination_ajax_links(current_page, url_options, options, inner_window, outer_window)
21
42
  start_number = current_page.number - inner_window
22
43
  end_number = current_page.number + inner_window
@@ -42,16 +63,16 @@ module ActiveScaffold
42
63
  page = current_page.number - offset
43
64
  next unless page < start_number && page > last_page
44
65
 
45
- html << '..' if page > last_page + 1
66
+ html << pagination_gap(options) if page > last_page + 1
46
67
  html << pagination_ajax_link(page, url_options, options)
47
68
  last_page = page
48
69
  end
49
70
  end
50
- html << '..' if start_number > last_page + 1
71
+ html << pagination_gap(options) if start_number > last_page + 1
51
72
 
52
73
  [start_number, last_page + 1].max.upto(end_number) do |num|
53
74
  html << if current_page.number == num
54
- content_tag(:span, num.to_s, class: 'as_paginate current')
75
+ pagination_active_page(num, url_options, options)
55
76
  else
56
77
  pagination_ajax_link(num, url_options, options)
57
78
  end
@@ -59,10 +80,10 @@ module ActiveScaffold
59
80
 
60
81
  if current_page.pager.infinite?
61
82
  offsets.each do |offset|
62
- html << '..' << pagination_ajax_link(current_page.number + offset, url_options, options)
83
+ html << pagination_gap(options) << pagination_ajax_link(current_page.number + offset, url_options, options)
63
84
  end
64
85
  else
65
- html << '..' unless end_number >= current_page.pager.last.number - outer_window - 1
86
+ html << pagination_gap(options) unless end_number >= current_page.pager.last.number - outer_window - 1
66
87
  [end_number + 1, current_page.pager.last.number - outer_window].max.upto(current_page.pager.last.number) do |num|
67
88
  html << pagination_ajax_link(num, url_options, options)
68
89
  end
@@ -50,14 +50,29 @@ module ActiveScaffold
50
50
 
51
51
  # the standard active scaffold options used for class, name and scope
52
52
  def active_scaffold_search_options(column)
53
- {name: "search[#{column.name}]", class: "#{column.name}-input", id: "search_#{column.name}", value: field_search_params[column.name.to_s]}
53
+ attributes = {
54
+ name: "search[#{column.name}]",
55
+ class: "#{column.name}-input",
56
+ id: "search_#{column.name}",
57
+ value: field_search_params[column.name.to_s]
58
+ }
59
+ as_element_attributes(:search_control, proc_options: {column: column, search_ui: column.search_ui}, **attributes)
54
60
  end
55
61
 
56
62
  def search_attribute(column, record)
57
63
  column_options = active_scaffold_search_options(column).merge(object: record)
58
- content_tag :dl do
59
- content_tag(:dt, label_tag(search_label_for(column, column_options), search_column_label(column, record))) <<
60
- content_tag(:dd, active_scaffold_search_for(column, column_options))
64
+ search_attribute_html(
65
+ column,
66
+ label_tag(search_label_for(column, column_options), search_column_label(column, record)),
67
+ active_scaffold_search_for(column, column_options)
68
+ )
69
+ end
70
+
71
+ def search_attribute_html(column, label, field)
72
+ context = {column: column}
73
+ as_element :search_attribute, proc_options: context do
74
+ as_element(:search_attribute_label, label, proc_options: context) <<
75
+ as_element(:search_attribute_field, field, proc_options: context)
61
76
  end
62
77
  end
63
78
 
@@ -156,7 +171,7 @@ module ActiveScaffold
156
171
  select_options << [as_(:true), true] # rubocop:disable Lint/BooleanSymbol
157
172
  select_options << [as_(:false), false] # rubocop:disable Lint/BooleanSymbol
158
173
 
159
- select_tag(options[:name], options_for_select(select_options, ActiveScaffold::Core.column_type_cast(options[:value], column.column)), id: options[:id])
174
+ select_tag(options[:name], options_for_select(select_options, ActiveScaffold::Core.column_type_cast(options[:value], column.column, column.model)), id: options[:id])
160
175
  end
161
176
  # we can't use checkbox ui because it's not possible to decide whether search for this field or not
162
177
  alias active_scaffold_search_checkbox active_scaffold_search_boolean
@@ -296,6 +311,7 @@ module ActiveScaffold
296
311
  def active_scaffold_search_timestamp(column, options, ui_options: column.options)
297
312
  active_scaffold_search_datetime(column, options, ui_options: ui_options, field_ui: :datetime)
298
313
  end
314
+ alias active_scaffold_search_timestamptz active_scaffold_search_timestamp
299
315
 
300
316
  def active_scaffold_search_time(column, options, ui_options: column.options)
301
317
  active_scaffold_search_datetime(column, options, ui_options: ui_options, field_ui: :time)
@@ -425,6 +441,17 @@ module ActiveScaffold
425
441
  [visibles, hiddens]
426
442
  end
427
443
 
444
+ def render_field_search_column(column, record)
445
+ column_css_class = column.css_class unless column.css_class.is_a?(Proc)
446
+ if column.respond_to? :each_column
447
+ as_element :field_search_subsection, class: column_css_class do
448
+ render_subsection column, record, nil, :field_search, partial: 'field_search_columns'
449
+ end
450
+ else
451
+ as_element :field_search_element, search_attribute(column, record), class: "form-element #{column_css_class}"
452
+ end
453
+ end
454
+
428
455
  def searched_by?(column)
429
456
  value = field_search_params[column.name.to_s]
430
457
  case value
@@ -10,9 +10,11 @@ module ActiveScaffold
10
10
  tab_options = send(override_helper_per_model(:active_scaffold_tab_options, record.class), column, record)
11
11
  used_tabs = send(override_helper_per_model(:active_scaffold_current_tabs, record.class), column, record, tab_options)
12
12
  input_helper = override_helper_per_model(:active_scaffold_input_for_tabbed, record.class)
13
- send(input_helper, column, record, subsection_id, tab_options, used_tabs.map(&:first)) <<
14
- active_scaffold_refresh_link(nil, {'data-update_url' => url_for(add_tab_url)}, record, refresh_opts) <<
15
- active_scaffold_tabs_for(column, record, subsection_id, tab_options, used_tabs, &)
13
+ safe_join([
14
+ send(input_helper, column, record, subsection_id, tab_options, used_tabs.map { |v, _| v }),
15
+ active_scaffold_refresh_link(nil, {'data-update_url' => url_for(add_tab_url)}, record, refresh_opts),
16
+ active_scaffold_tabs_for(column, record, subsection_id, tab_options, used_tabs, &)
17
+ ])
16
18
  end
17
19
 
18
20
  def active_scaffold_input_for_tabbed(column, record, subsection_id, tab_options, used_tabs)
@@ -32,8 +34,6 @@ module ActiveScaffold
32
34
  tab_values = record.send(col.name).map(&tabbed_by).compact
33
35
  if tabbed_by_association(col, tabbed_by)
34
36
  tab_values.map! { |value| [value, value.id.to_s] }
35
- else
36
- tab_values.map! { |value| [tab_options.find { |_, tab_value, _| value == tab_value }&.first || value, value] }
37
37
  end
38
38
  used_choices.merge tab_values
39
39
  end
@@ -41,7 +41,7 @@ module ActiveScaffold
41
41
  end
42
42
 
43
43
  def active_scaffold_tab_options(column, record)
44
- subform_column = column.first
44
+ subform_column = column.visible_columns.first
45
45
  if subform_column
46
46
  tabbed_by = subform_column.options[:tabbed_by] || column.tabbed_by
47
47
  if tabbed_by_association(subform_column, tabbed_by)
@@ -51,7 +51,7 @@ module ActiveScaffold
51
51
  end
52
52
  if tab_column
53
53
  label_method = (tab_column.form_ui_options || tab_column.options)[:label_method] || :to_label
54
- helper_method = association_helper_method(column.association, :sorted_association_options_find)
54
+ helper_method = association_helper_method(tab_column.association, :sorted_association_options_find)
55
55
  send(helper_method, tab_column.association, nil, subform_record).map do |opt_record|
56
56
  [opt_record.send(label_method), opt_record.id, opt_record]
57
57
  end
@@ -18,6 +18,7 @@ module ActiveScaffold
18
18
  include ActiveScaffold::Helpers::SearchColumnHelpers
19
19
  include ActiveScaffold::Helpers::HumanConditionHelpers
20
20
  include ActiveScaffold::Helpers::FilterHelpers
21
+ include ActiveScaffold::Helpers::FrameworkUiHelpers
21
22
 
22
23
  ##
23
24
  ## Delegates
@@ -31,6 +32,12 @@ module ActiveScaffold
31
32
  ## Uncategorized
32
33
  ##
33
34
 
35
+ # UI adapters may override this method and call +super+ to wrap the table,
36
+ # for example in a responsive-table container.
37
+ def list_table_html(&)
38
+ capture(&)
39
+ end
40
+
34
41
  def handle_exception_on_column(exception, column, record = nil)
35
42
  raise exception if exception.instance_variable_get(:@_active_scaffold_wrapped)
36
43
 
@@ -126,6 +133,7 @@ module ActiveScaffold
126
133
  def column_heading_class(column, sorting)
127
134
  classes = "#{column.name}-column_heading "
128
135
  classes << "sorted #{sorting.direction_of(column).downcase} " if sorting.sorts_on? column
136
+ classes << 'skip-local-sorting' if column.skip_local_sorting?
129
137
  classes << column.css_class unless column.css_class.nil? || column.css_class.is_a?(Proc)
130
138
  classes
131
139
  end
@@ -140,6 +148,10 @@ module ActiveScaffold
140
148
  params[:eid] ? {eid: id_from_controller(params[:eid])} : {}
141
149
  end
142
150
 
151
+ def server_error_msg(content = nil, **attributes, &)
152
+ as_element :server_error, content, **attributes, &
153
+ end
154
+
143
155
  def column_empty?(column_value)
144
156
  @_empty_values ||= ['&nbsp;', empty_field_text].compact
145
157
  empty = column_value.nil?
@@ -24,16 +24,19 @@ class ActiveScaffold::Tableless < ActiveRecord::Base
24
24
 
25
25
  class Column < ActiveRecord::ConnectionAdapters::Column
26
26
  def initialize(name, default, type, null = true, *, **) # rubocop:disable Style/OptionalBooleanParameter,Metrics/ParameterLists
27
- if Rails.version >= '8.1'
28
- if type.is_a?(Symbol)
29
- cast_type = ActiveRecord::Type.lookup(type)
30
- else
31
- metadata = ActiveRecord::Base.connection.send :fetch_type_metadata, type
32
- cast_type = ActiveRecord::Type.lookup(metadata.type)
33
- end
27
+ connection = ActiveRecord::Base.connection
28
+ sql_type = type.is_a?(Symbol) ? connection.type_to_sql(type) : type.to_s
29
+ cast_type = connection.send(:lookup_cast_type, sql_type)
30
+ metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(
31
+ sql_type: sql_type,
32
+ type: cast_type.type,
33
+ limit: cast_type.limit,
34
+ precision: cast_type.precision,
35
+ scale: cast_type.scale
36
+ )
37
+ if ActiveRecord.version >= Gem::Version.new('8.1')
34
38
  super(name, cast_type, default, metadata, null, *, **)
35
39
  else
36
- metadata = ActiveRecord::Base.connection.send :fetch_type_metadata, type.to_s
37
40
  super(name, default, metadata, null, *, **)
38
41
  end
39
42
  end
@@ -3,8 +3,8 @@
3
3
  module ActiveScaffold
4
4
  module Version
5
5
  MAJOR = 4
6
- MINOR = 3
7
- PATCH = 2
6
+ MINOR = 4
7
+ PATCH = 1
8
8
  FIX = nil
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, FIX].compact.join('.')