active_scaffold 4.3.2 → 4.4.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 +4 -4
- data/CHANGELOG.rdoc +19 -0
- data/README.md +1 -1
- data/app/assets/javascripts/active_scaffold/active_scaffold.js +98 -11
- data/app/assets/javascripts/active_scaffold/draggable_lists.js +4 -3
- data/app/assets/javascripts/active_scaffold/load.js +20 -5
- data/app/assets/stylesheets/active_scaffold/_images.scss +53 -1
- data/app/assets/stylesheets/active_scaffold/_layout.scss +1 -66
- data/app/views/active_scaffold_overrides/_add_existing_form.html.erb +17 -11
- data/app/views/active_scaffold_overrides/_base_form.html.erb +29 -19
- data/app/views/active_scaffold_overrides/_field_search.html.erb +9 -9
- data/app/views/active_scaffold_overrides/_field_search_columns.html.erb +29 -33
- data/app/views/active_scaffold_overrides/_form.html.erb +43 -34
- data/app/views/active_scaffold_overrides/_form_association.html.erb +3 -3
- data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +20 -16
- data/app/views/active_scaffold_overrides/_form_association_record.html.erb +3 -4
- data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +2 -1
- data/app/views/active_scaffold_overrides/_list.html.erb +6 -4
- data/app/views/active_scaffold_overrides/_list_calculations.html.erb +1 -1
- data/app/views/active_scaffold_overrides/_list_header.html.erb +3 -3
- data/app/views/active_scaffold_overrides/_list_messages.html.erb +19 -19
- data/app/views/active_scaffold_overrides/_list_messages_content.html.erb +5 -5
- data/app/views/active_scaffold_overrides/_list_pagination.html.erb +7 -5
- data/app/views/active_scaffold_overrides/_list_pagination_links.html.erb +5 -3
- data/app/views/active_scaffold_overrides/_list_record.html.erb +13 -8
- data/app/views/active_scaffold_overrides/_list_with_header.html.erb +8 -8
- data/app/views/active_scaffold_overrides/_messages.html.erb +3 -3
- data/app/views/active_scaffold_overrides/_render_field.js.erb +8 -11
- data/app/views/active_scaffold_overrides/_search.html.erb +7 -6
- data/app/views/active_scaffold_overrides/_show.html.erb +4 -4
- data/app/views/active_scaffold_overrides/_show_actions.html.erb +2 -2
- data/app/views/active_scaffold_overrides/_show_columns.html.erb +28 -30
- data/app/views/active_scaffold_overrides/add_existing.js.erb +7 -4
- data/app/views/active_scaffold_overrides/render_field.js.erb +1 -1
- data/config/locales/de.yml +2 -1
- data/config/locales/en.yml +33 -32
- data/config/locales/es.yml +15 -14
- data/config/locales/fr.yml +3 -2
- data/config/locales/hu.yml +3 -2
- data/config/locales/ja.yml +2 -1
- data/config/locales/ro.yml +169 -0
- data/config/locales/ru.yml +8 -7
- data/config/locales/ta.yml +7 -6
- data/config/locales/zh_Hans.yml +14 -13
- data/lib/active_scaffold/actions/common_search.rb +1 -14
- data/lib/active_scaffold/actions/core.rb +55 -16
- data/lib/active_scaffold/actions/field_search.rb +2 -2
- data/lib/active_scaffold/actions/list.rb +35 -13
- data/lib/active_scaffold/actions/nested.rb +31 -7
- data/lib/active_scaffold/actions/show.rb +1 -1
- data/lib/active_scaffold/active_record_permissions.rb +1 -1
- data/lib/active_scaffold/attribute_params.rb +2 -1
- data/lib/active_scaffold/bridges/date_picker/helper.rb +14 -0
- data/lib/active_scaffold/bridges/date_picker.rb +10 -0
- data/lib/active_scaffold/config/list.rb +10 -1
- data/lib/active_scaffold/core.rb +9 -6
- data/lib/active_scaffold/data_structures/action_columns.rb +2 -0
- data/lib/active_scaffold/data_structures/column.rb +27 -6
- data/lib/active_scaffold/data_structures/filter.rb +4 -0
- data/lib/active_scaffold/data_structures/nested_info.rb +8 -1
- data/lib/active_scaffold/data_structures/sorting.rb +1 -1
- data/lib/active_scaffold/extensions/action_view_rendering.rb +1 -1
- data/lib/active_scaffold/finder.rb +66 -21
- data/lib/active_scaffold/helpers/action_link_helpers.rb +43 -18
- data/lib/active_scaffold/helpers/assets_helpers.rb +4 -4
- data/lib/active_scaffold/helpers/filter_helpers.rb +3 -3
- data/lib/active_scaffold/helpers/form_column_helpers.rb +34 -22
- data/lib/active_scaffold/helpers/form_ui_helpers.rb +15 -10
- data/lib/active_scaffold/helpers/framework_ui_helpers.rb +22 -0
- data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -0
- data/lib/active_scaffold/helpers/list_column_helpers.rb +19 -0
- data/lib/active_scaffold/helpers/pagination_helpers.rb +28 -7
- data/lib/active_scaffold/helpers/search_column_helpers.rb +32 -5
- data/lib/active_scaffold/helpers/tabs_helpers.rb +3 -5
- data/lib/active_scaffold/helpers/view_helpers.rb +12 -0
- data/lib/active_scaffold/tableless.rb +11 -8
- data/lib/active_scaffold/version.rb +2 -2
- data/lib/active_scaffold.rb +169 -0
- data/lib/tasks/bundle.rake +2 -1
- data/lib/tasks/mock_app.rake +43 -0
- metadata +20 -7
- data/shoulda_macros/macros.rb +0 -145
|
@@ -17,6 +17,8 @@ module ActiveScaffold::DataStructures
|
|
|
17
17
|
def initialize(model, params)
|
|
18
18
|
@parent_scaffold = "#{params[:parent_scaffold].to_s.camelize}Controller".constantize
|
|
19
19
|
@parent_model = @parent_scaffold.active_scaffold_config.model
|
|
20
|
+
rescue NameError => e
|
|
21
|
+
raise ActionController::BadRequest, e.message
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def to_params
|
|
@@ -82,9 +84,14 @@ module ActiveScaffold::DataStructures
|
|
|
82
84
|
def initialize(model, params)
|
|
83
85
|
super
|
|
84
86
|
column = parent_scaffold.active_scaffold_config.columns[params[:association].to_sym]
|
|
87
|
+
unless column&.association
|
|
88
|
+
raise ActionController::BadRequest,
|
|
89
|
+
"Invalid nested association #{params[:association].inspect} for #{parent_scaffold.active_scaffold_config.model.name}"
|
|
90
|
+
end
|
|
91
|
+
|
|
85
92
|
@param_name = column.model.name.foreign_key.to_sym
|
|
86
93
|
@parent_id = params[@param_name]
|
|
87
|
-
@association = column
|
|
94
|
+
@association = column.association
|
|
88
95
|
@child_association = association.reverse_association(model) if association
|
|
89
96
|
setup_constrained_fields
|
|
90
97
|
end
|
|
@@ -136,7 +136,7 @@ module ActiveScaffold::DataStructures
|
|
|
136
136
|
sql = sql.to_sql if sql.respond_to?(:to_sql)
|
|
137
137
|
|
|
138
138
|
parts = Array(sql).map do |column|
|
|
139
|
-
mongoid? ? [column, sort_direction] :
|
|
139
|
+
mongoid? ? [column, sort_direction] : {column => sort_direction}
|
|
140
140
|
end
|
|
141
141
|
order << parts
|
|
142
142
|
end
|
|
@@ -162,7 +162,7 @@ module ActiveScaffold # :nodoc:
|
|
|
162
162
|
content_tag(:div, class: 'active-scaffold-header') do
|
|
163
163
|
content_tag(:h2) do
|
|
164
164
|
label = options[:label] || remote_controller_config(remote_controller).list.label
|
|
165
|
-
link_to(label, url, remote: true, class: 'load-embedded', data: {error_msg: as_(:error_500)}) <<
|
|
165
|
+
link_to(label, url, remote: true, class: 'load-embedded', data: {error_msg: server_error_msg(as_(:error_500))}) <<
|
|
166
166
|
loading_indicator_tag(url_options)
|
|
167
167
|
end
|
|
168
168
|
end
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveScaffold
|
|
4
4
|
module Finder
|
|
5
|
-
def self.like_operator
|
|
6
|
-
|
|
5
|
+
def self.like_operator(column = nil)
|
|
6
|
+
return column.like_operator if column&.like_operator
|
|
7
|
+
|
|
8
|
+
connection = column&.active_record_class&.connection || ::ActiveRecord::Base.connection
|
|
9
|
+
connection.adapter_name.in?(%w[PostgreSQL PostGIS]) ? 'ILIKE' : 'LIKE'
|
|
7
10
|
end
|
|
8
11
|
|
|
9
12
|
def self.logical_comparators
|
|
@@ -35,13 +38,14 @@ module ActiveScaffold
|
|
|
35
38
|
|
|
36
39
|
def type_casted_tokens(tokens, columns, like_pattern)
|
|
37
40
|
tokens.map do |value|
|
|
38
|
-
columns.
|
|
39
|
-
|
|
41
|
+
columns.to_h do |column|
|
|
42
|
+
casted_value =
|
|
40
43
|
if column.text?
|
|
41
44
|
like_pattern.sub('?', column.active_record? ? column.active_record_class.sanitize_sql_like(value) : value)
|
|
42
45
|
else
|
|
43
|
-
ActiveScaffold::Core.column_type_cast(value, column.column)
|
|
46
|
+
ActiveScaffold::Core.column_type_cast(value, column.column, column.model)
|
|
44
47
|
end
|
|
48
|
+
[column.name, casted_value]
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
end
|
|
@@ -51,7 +55,7 @@ module ActiveScaffold
|
|
|
51
55
|
where_clauses = []
|
|
52
56
|
columns.each do |column|
|
|
53
57
|
column.search_sql.each do |search_sql|
|
|
54
|
-
where_clauses << "#{search_sql} #{column.text? ? ActiveScaffold::Finder.like_operator : '='} ?"
|
|
58
|
+
where_clauses << "#{search_sql} #{column.text? ? ActiveScaffold::Finder.like_operator(column) : '='} ?"
|
|
55
59
|
end
|
|
56
60
|
end
|
|
57
61
|
phrase = where_clauses.join(' OR ')
|
|
@@ -170,7 +174,7 @@ module ActiveScaffold
|
|
|
170
174
|
if value == 'null'
|
|
171
175
|
condition_for_null_type(column, value)
|
|
172
176
|
else
|
|
173
|
-
['%<search_sql>s = ?', column.column ? ActiveScaffold::Core.column_type_cast(value, column.column) : value]
|
|
177
|
+
['%<search_sql>s = ?', column.column ? ActiveScaffold::Core.column_type_cast(value, column.column, column.model) : value]
|
|
174
178
|
end
|
|
175
179
|
when :integer, :decimal, :float
|
|
176
180
|
condition_for_numeric(column, value)
|
|
@@ -180,7 +184,7 @@ module ActiveScaffold
|
|
|
180
184
|
else
|
|
181
185
|
condition_for_single_value(column, value, like_pattern)
|
|
182
186
|
end
|
|
183
|
-
when :date, :time, :datetime, :timestamp
|
|
187
|
+
when :date, :time, :datetime, :timestamp, :timestamptz
|
|
184
188
|
condition_for_datetime(column, value)
|
|
185
189
|
when :select, :select_multiple, :draggable, :multi_select, :country, :usa_state, :chosen, :multi_chosen
|
|
186
190
|
if value.is_a?(Hash)
|
|
@@ -211,9 +215,9 @@ module ActiveScaffold
|
|
|
211
215
|
def condition_for_single_value(column, value, like_pattern = nil)
|
|
212
216
|
if column.text?
|
|
213
217
|
value = column.active_record_class.sanitize_sql_like(value) if column.active_record?
|
|
214
|
-
["%<search_sql>s #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)]
|
|
218
|
+
["%<search_sql>s #{ActiveScaffold::Finder.like_operator(column)} ?", like_pattern.sub('?', value)]
|
|
215
219
|
else
|
|
216
|
-
['%<search_sql>s = ?', ActiveScaffold::Core.column_type_cast(value, column.column)]
|
|
220
|
+
['%<search_sql>s = ?', ActiveScaffold::Core.column_type_cast(value, column.column, column.model)]
|
|
217
221
|
end
|
|
218
222
|
end
|
|
219
223
|
|
|
@@ -228,7 +232,7 @@ module ActiveScaffold
|
|
|
228
232
|
elsif ActiveScaffold::Finder::STRING_COMPARATORS.value?(value[:opt])
|
|
229
233
|
text = column.active_record? ? column.active_record_class.sanitize_sql_like(value[:from]) : value[:from]
|
|
230
234
|
[
|
|
231
|
-
"%<search_sql>s #{'NOT ' if value[:opt].start_with?('not_')}#{ActiveScaffold::Finder.like_operator} ?",
|
|
235
|
+
"%<search_sql>s #{'NOT ' if value[:opt].start_with?('not_')}#{ActiveScaffold::Finder.like_operator(column)} ?",
|
|
232
236
|
value[:opt].sub('not_', '').sub('?', text)
|
|
233
237
|
]
|
|
234
238
|
elsif value[:opt] == 'BETWEEN'
|
|
@@ -367,7 +371,7 @@ module ActiveScaffold
|
|
|
367
371
|
end
|
|
368
372
|
elsif conversion == :to_date
|
|
369
373
|
parse_date_with_format(*format_for_date(column, value, column.send(ui_method), ui_options))
|
|
370
|
-
elsif value.
|
|
374
|
+
elsif value.match?(/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/) # iso8601 format
|
|
371
375
|
Time.zone.parse(value)
|
|
372
376
|
else # datetime
|
|
373
377
|
time = parse_time_with_format(value, *format_for_datetime(column, value, column.send(ui_method), ui_options))
|
|
@@ -623,7 +627,6 @@ module ActiveScaffold
|
|
|
623
627
|
search_conditions = all_conditions
|
|
624
628
|
|
|
625
629
|
sorting = options[:sorting]&.clause
|
|
626
|
-
sorting = sorting.map { |part| Arel.sql(part) } if sorting && active_scaffold_config.active_record?
|
|
627
630
|
# create a general-use options array that's compatible with Rails finders
|
|
628
631
|
finder_options = {
|
|
629
632
|
reorder: sorting,
|
|
@@ -647,6 +650,22 @@ module ActiveScaffold
|
|
|
647
650
|
finder_options
|
|
648
651
|
end
|
|
649
652
|
|
|
653
|
+
def add_order_expressions_to_select(query, sorting)
|
|
654
|
+
return query unless query.respond_to?(:distinct_value) && query.distinct_value &&
|
|
655
|
+
active_scaffold_config.model.connection.needs_order_expressions_in_select?
|
|
656
|
+
|
|
657
|
+
order_expressions = order_select_expressions(sorting)
|
|
658
|
+
return query if order_expressions.empty?
|
|
659
|
+
|
|
660
|
+
select_columns = query.select_values
|
|
661
|
+
select_columns = ["#{active_scaffold_config.model.quoted_table_name}.*"] if select_columns.empty?
|
|
662
|
+
query.reselect(*(select_columns + order_expressions).uniq)
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
def order_select_expressions(sorting)
|
|
666
|
+
Array(sorting&.clause).flat_map { |order| order.is_a?(Hash) ? order.keys : [] }
|
|
667
|
+
end
|
|
668
|
+
|
|
650
669
|
def count_items(query, find_options = {}, count_includes = nil)
|
|
651
670
|
count_includes ||= find_options[:includes] if find_options[:conditions].present?
|
|
652
671
|
options = find_options.except(:select, :reorder, :order)
|
|
@@ -671,31 +690,54 @@ module ActiveScaffold
|
|
|
671
690
|
find_options = finder_options(options)
|
|
672
691
|
query = filtered_query
|
|
673
692
|
query = query.where(nil) if active_scaffold_config.active_record? # where(nil) is needed because we need a relation
|
|
693
|
+
count_query = query
|
|
674
694
|
|
|
675
695
|
# NOTE: we must use :include in the count query, because some conditions may reference other tables
|
|
676
|
-
|
|
696
|
+
count_after_first_page = count_after_loading_first_page?(options)
|
|
697
|
+
if options[:pagination] && options[:pagination] != :infinite && !count_after_first_page
|
|
677
698
|
count = count_items(query, find_options, options[:count_includes])
|
|
678
699
|
end
|
|
679
700
|
|
|
680
701
|
query = append_to_query(query, find_options)
|
|
702
|
+
query = add_order_expressions_to_select(query, options[:sorting])
|
|
703
|
+
first_page_items = load_page_for_delayed_count(query, options[:per_page]) if count_after_first_page
|
|
704
|
+
if first_page_items
|
|
705
|
+
count = first_page_items.size
|
|
706
|
+
count = count_items(count_query, find_options, options[:count_includes]) if count == options[:per_page]
|
|
707
|
+
end
|
|
681
708
|
# we build the paginator differently for method- and sql-based sorting
|
|
682
709
|
pager = if options[:sorting]&.sorts_by_method?
|
|
683
710
|
::Paginator.new(count, options[:per_page]) do |offset, per_page|
|
|
684
711
|
calculate_last_modified(query)
|
|
685
712
|
sorted_collection = sort_collection_by_column(query.to_a, *options[:sorting].first)
|
|
686
|
-
sorted_collection = sorted_collection.slice(offset, per_page) if options[:pagination]
|
|
713
|
+
sorted_collection = sorted_collection.slice(offset, per_page) || [] if options[:pagination]
|
|
687
714
|
sorted_collection
|
|
688
715
|
end
|
|
689
716
|
else
|
|
690
717
|
::Paginator.new(count, options[:per_page]) do |offset, per_page|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
718
|
+
if first_page_items && offset.zero?
|
|
719
|
+
first_page_items
|
|
720
|
+
else
|
|
721
|
+
page_query = options[:pagination] ? append_to_query(query, offset: offset, limit: per_page) : query
|
|
722
|
+
calculate_last_modified(page_query)
|
|
723
|
+
page_query
|
|
724
|
+
end
|
|
694
725
|
end
|
|
695
726
|
end
|
|
696
727
|
pager.page(options[:page])
|
|
697
728
|
end
|
|
698
729
|
|
|
730
|
+
def count_after_loading_first_page?(options)
|
|
731
|
+
active_scaffold_config.active_record? && options[:pagination] && options[:pagination] != :infinite && options[:page].to_i == 1 &&
|
|
732
|
+
!options[:sorting]&.sorts_by_method?
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def load_page_for_delayed_count(query, per_page)
|
|
736
|
+
page_query = append_to_query(query, offset: 0, limit: per_page)
|
|
737
|
+
calculate_last_modified(page_query)
|
|
738
|
+
page_query.to_a
|
|
739
|
+
end
|
|
740
|
+
|
|
699
741
|
def calculate_last_modified(query)
|
|
700
742
|
return unless conditional_get_support? && ActiveScaffold::OrmChecks.columns_hash(query.klass)['updated_at']
|
|
701
743
|
|
|
@@ -730,8 +772,11 @@ module ActiveScaffold
|
|
|
730
772
|
rel.send(k, v)
|
|
731
773
|
end
|
|
732
774
|
end
|
|
733
|
-
relation.
|
|
734
|
-
|
|
775
|
+
distinct_query?(options) ? relation.distinct : relation
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
def distinct_query?(options)
|
|
779
|
+
options[:left_outer_joins].present? || options[:left_joins].present?
|
|
735
780
|
end
|
|
736
781
|
|
|
737
782
|
def joins_for_finder
|
|
@@ -759,7 +804,7 @@ module ActiveScaffold
|
|
|
759
804
|
def sort_collection_by_column(collection, column, order)
|
|
760
805
|
sorter = column.sort[:method]
|
|
761
806
|
collection = collection.sort_by do |record|
|
|
762
|
-
value = sorter.is_a?(Proc) ? record.instance_exec(record, &sorter) : record.
|
|
807
|
+
value = sorter.is_a?(Proc) ? record.instance_exec(record, &sorter) : record.send(sorter)
|
|
763
808
|
value = '' if value.nil?
|
|
764
809
|
value
|
|
765
810
|
end
|
|
@@ -26,8 +26,8 @@ module ActiveScaffold
|
|
|
26
26
|
record = record_or_ul_options unless record_or_ul_options.is_a?(Hash)
|
|
27
27
|
ul_options ||= {}
|
|
28
28
|
ul_options[:class] = [ul_options[:class], 'dynamic-menu'].compact.join(' ')
|
|
29
|
-
html =
|
|
30
|
-
safe_join(links.map { |link|
|
|
29
|
+
html = as_element :dynamic_action_group, **ul_options do
|
|
30
|
+
safe_join(links.map { |link| as_element :dynamic_action_group_element, link })
|
|
31
31
|
end
|
|
32
32
|
raw "ActiveScaffold.display_dynamic_action_group('#{get_action_link_id action_link, record}', '#{escape_javascript html}');" # rubocop:disable Rails/OutputSafety
|
|
33
33
|
end
|
|
@@ -77,8 +77,7 @@ module ActiveScaffold
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def display_action_link_separator(options)
|
|
80
|
-
|
|
81
|
-
content_tag(tag || :li, ' '.html_safe, class: 'separator')
|
|
80
|
+
as_element(:action_link_separator, ' '.html_safe, proc_options: options)
|
|
82
81
|
end
|
|
83
82
|
|
|
84
83
|
def display_click_menu_link(link, record, options)
|
|
@@ -99,25 +98,36 @@ module ActiveScaffold
|
|
|
99
98
|
|
|
100
99
|
def display_action_link(link, content, record, options)
|
|
101
100
|
if content
|
|
102
|
-
|
|
103
|
-
if options[:level].zero?
|
|
104
|
-
html_classes += 'action_group'
|
|
105
|
-
group_tag = :div
|
|
106
|
-
else
|
|
107
|
-
html_classes += 'top' if options[:first_action]
|
|
108
|
-
group_tag = :li
|
|
109
|
-
end
|
|
110
|
-
content = content_tag(group_tag, class: html_classes.presence, onclick: ('' if hover_via_click?)) do
|
|
111
|
-
content_tag(:div, link.label(record), class: link.css_class, title: options[:title]) << content_tag(:ul, content)
|
|
112
|
-
end
|
|
101
|
+
content = render_action_link_group(link, content, record, options)
|
|
113
102
|
else
|
|
114
103
|
content = render_action_link(link, record, options)
|
|
115
|
-
|
|
104
|
+
unless options[:level].zero?
|
|
105
|
+
context = {link: link, record: record, options: options}
|
|
106
|
+
content = as_element(
|
|
107
|
+
:action_link_item,
|
|
108
|
+
content,
|
|
109
|
+
proc_options: context,
|
|
110
|
+
class: ('top' if options[:first_action])
|
|
111
|
+
)
|
|
112
|
+
end
|
|
116
113
|
end
|
|
117
114
|
content = content_tag(options[:level_0_tag], content, options[:options_level_0_tag]) if options[:level].zero? && options[:level_0_tag]
|
|
118
115
|
content
|
|
119
116
|
end
|
|
120
117
|
|
|
118
|
+
def render_action_link_group(link, content, record, options, group_label: nil)
|
|
119
|
+
context = options.merge(link: link, record: record)
|
|
120
|
+
as_element(:action_link_group, proc_options: context) do
|
|
121
|
+
as_element(
|
|
122
|
+
:action_link_group_title,
|
|
123
|
+
group_label || link.label(record),
|
|
124
|
+
proc_options: context,
|
|
125
|
+
class: link.css_class,
|
|
126
|
+
title: options[:title]
|
|
127
|
+
) << as_element(:action_link_group_content, content, proc_options: context)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
121
131
|
def render_action_link(link, record = nil, options = {})
|
|
122
132
|
if link.action.nil? || link.column&.association&.polymorphic?
|
|
123
133
|
link = action_link_to_inline_form(link, record) if link.column&.association
|
|
@@ -167,6 +177,7 @@ module ActiveScaffold
|
|
|
167
177
|
class: html_class,
|
|
168
178
|
title: cache ? '--REASON--' : options[:not_authorized_reason]
|
|
169
179
|
}
|
|
180
|
+
html_options = action_link_element_attributes(link, record, options, html_options, authorized: false)
|
|
170
181
|
html = action_link_html(link, nil, html_options, record)
|
|
171
182
|
cache ? html.to_str : html
|
|
172
183
|
end
|
|
@@ -175,10 +186,16 @@ module ActiveScaffold
|
|
|
175
186
|
url = cache ? '--URL--' : action_link_url(link, record)
|
|
176
187
|
options[:link] = '--LABEL--' if cache
|
|
177
188
|
html_options = action_link_html_options(link, record, options, cache: cache)
|
|
189
|
+
html_options = action_link_element_attributes(link, record, options, html_options, authorized: true)
|
|
178
190
|
html = action_link_html(link, url, html_options, record)
|
|
179
191
|
cache ? html.to_str : html
|
|
180
192
|
end
|
|
181
193
|
|
|
194
|
+
def action_link_element_attributes(link, record, options, attributes, authorized:)
|
|
195
|
+
context = {link: link, record: record, options: options, authorized: authorized}
|
|
196
|
+
as_element_attributes(:action_link, proc_options: context, **attributes)
|
|
197
|
+
end
|
|
198
|
+
|
|
182
199
|
# setup the action link to inline form
|
|
183
200
|
def action_link_to_inline_form(link, record)
|
|
184
201
|
link = link.dup
|
|
@@ -302,12 +319,20 @@ module ActiveScaffold
|
|
|
302
319
|
|
|
303
320
|
def column_in_params_conditions?(key)
|
|
304
321
|
if key.match?(/!$/)
|
|
305
|
-
conditions_from_params[1..].any? { |node| node
|
|
322
|
+
conditions_from_params[1..].any? { |node| condition_node_for_column?(node, key[0..-2]) }
|
|
306
323
|
else
|
|
307
324
|
conditions_from_params[0].include?(key)
|
|
308
325
|
end
|
|
309
326
|
end
|
|
310
327
|
|
|
328
|
+
def condition_node_for_column?(node, column_name)
|
|
329
|
+
return node.name.to_s == column_name if node.is_a?(Arel::Attributes::Attribute)
|
|
330
|
+
|
|
331
|
+
%i[expr left right].any? do |child|
|
|
332
|
+
node.respond_to?(child) && condition_node_for_column?(node.public_send(child), column_name)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
311
336
|
def ignore_param_for_nested?(key)
|
|
312
337
|
NESTED_PARAMS.include?(key) || column_in_params_conditions?(key) || (nested? && nested.param_name == key)
|
|
313
338
|
end
|
|
@@ -335,7 +360,7 @@ module ActiveScaffold
|
|
|
335
360
|
end
|
|
336
361
|
end
|
|
337
362
|
if nested_singular_association? && action_name == 'index'
|
|
338
|
-
# pass current path as return_to, for nested listing on singular association, so forms
|
|
363
|
+
# pass the current path as return_to, for nested listing on singular association, so forms don't return to parent listing
|
|
339
364
|
@query_string_params << :return_to
|
|
340
365
|
non_nested_query_string_options[:return_to] = request.fullpath
|
|
341
366
|
end
|
|
@@ -8,7 +8,7 @@ module ActiveScaffold
|
|
|
8
8
|
js_tags << active_scaffold_javascript_config
|
|
9
9
|
unless defined?(Importmap)
|
|
10
10
|
# Without importmap, active_scaffold/load must be added to the layout too
|
|
11
|
-
js_tags << javascript_include_tag('active_scaffold/load')
|
|
11
|
+
js_tags << javascript_include_tag('active_scaffold/load', nonce: true)
|
|
12
12
|
end
|
|
13
13
|
safe_join js_tags
|
|
14
14
|
end
|
|
@@ -22,16 +22,16 @@ module ActiveScaffold
|
|
|
22
22
|
plugins: ActiveScaffold.javascripts.map { |asset| asset_path(asset, extname: '.js') }
|
|
23
23
|
)
|
|
24
24
|
if ActiveScaffold.jquery_ui_included?
|
|
25
|
-
config[:datepickerLocalization] = ActiveScaffold::
|
|
25
|
+
config[:datepickerLocalization] = ActiveScaffold::Bridges[:date_picker].localization_data
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
if Object.const_defined?(:Jquery) && Jquery.const_defined?(:Ui)
|
|
29
29
|
config[:jqueryUi] = ActiveScaffold::Assets::JqueryUiManifest.all_dependencies.map { |asset| asset_path(asset, extname: '.js') }
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
javascript_tag
|
|
32
|
+
javascript_tag(<<~JS, nonce: true)
|
|
33
33
|
window.ActiveScaffold = window.ActiveScaffold || {};
|
|
34
|
-
window.ActiveScaffold.config = #{config.to_json};
|
|
34
|
+
window.ActiveScaffold.config = #{json_escape config.to_json};
|
|
35
35
|
JS
|
|
36
36
|
end
|
|
37
37
|
end
|
|
@@ -5,8 +5,8 @@ module ActiveScaffold
|
|
|
5
5
|
# Helpers rendering filters
|
|
6
6
|
module FilterHelpers
|
|
7
7
|
def clear_filters_params
|
|
8
|
-
active_scaffold_config.list.filters.
|
|
9
|
-
|
|
8
|
+
active_scaffold_config.list.filters.to_h do |filter|
|
|
9
|
+
[filter.name, nil]
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ module ActiveScaffold
|
|
|
14
14
|
return unless filters_enabled?
|
|
15
15
|
|
|
16
16
|
content = render_filters filters
|
|
17
|
-
|
|
17
|
+
as_element :filters, content if content.present?
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def render_filters(filters)
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
88
|
+
attributes = as_element_attributes(:subform_record_remove, class: 'destroy')
|
|
89
|
+
link_to(as_(:remove), '#', attributes)
|
|
88
90
|
else
|
|
89
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
255
|
-
field << hidden_field(:record, method, column_options)
|
|
262
|
+
field << hidden_field(:record, column.association&.foreign_key || column.name, column_options)
|
|
256
263
|
end
|
|
257
264
|
else
|
|
258
|
-
field
|
|
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 << 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
|
-
|
|
269
|
-
|
|
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 ||= h('')) << description 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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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,
|
|
241
|
-
content_tag(:li,
|
|
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
|