active_scaffold 4.3.1 → 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 +26 -1
- data/README.md +1 -1
- data/app/assets/javascripts/active_scaffold/active_scaffold.js +107 -16
- 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/add_tab.js.erb +2 -3
- 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 +4 -6
- 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
|
@@ -41,6 +41,9 @@ module ActiveScaffold::DataStructures
|
|
|
41
41
|
# a textual description of the column and its contents. this will be displayed with any associated form input widget, so you may want to consider adding a content example.
|
|
42
42
|
attr_writer :description
|
|
43
43
|
|
|
44
|
+
# enable it if local sorting shouldn't be used for this column at all
|
|
45
|
+
attr_writer :skip_local_sorting
|
|
46
|
+
|
|
44
47
|
attr_reader :update_columns
|
|
45
48
|
|
|
46
49
|
# send all the form instead of only new value when this column changes
|
|
@@ -107,6 +110,9 @@ module ActiveScaffold::DataStructures
|
|
|
107
110
|
|
|
108
111
|
# define the fields to use with logical search
|
|
109
112
|
attr_accessor :logical_search
|
|
113
|
+
|
|
114
|
+
# SQL operator used for text searches. Defaults according to the adapter when nil.
|
|
115
|
+
attr_accessor :like_operator
|
|
110
116
|
end
|
|
111
117
|
|
|
112
118
|
def inplace_edit=(value)
|
|
@@ -125,6 +131,11 @@ module ActiveScaffold::DataStructures
|
|
|
125
131
|
!(calculate == false || calculate.nil?)
|
|
126
132
|
end
|
|
127
133
|
|
|
134
|
+
# if local sorting shouldn't be used for this column at all
|
|
135
|
+
def skip_local_sorting?
|
|
136
|
+
@skip_local_sorting == true
|
|
137
|
+
end
|
|
138
|
+
|
|
128
139
|
def required?(action = nil)
|
|
129
140
|
if action && @required
|
|
130
141
|
@required == true || @required.include?(action)
|
|
@@ -345,7 +356,14 @@ module ActiveScaffold::DataStructures
|
|
|
345
356
|
|
|
346
357
|
def search_sql
|
|
347
358
|
initialize_search_sql if @search_sql == true
|
|
348
|
-
@search_sql
|
|
359
|
+
return @search_sql unless @search_sql.is_a?(Array) && active_record?
|
|
360
|
+
|
|
361
|
+
connection = active_record_class.connection
|
|
362
|
+
@search_sql.map do |sql|
|
|
363
|
+
next sql unless sql.is_a?(Symbol)
|
|
364
|
+
|
|
365
|
+
"#{connection.quote_table_name(active_record_class.table_name)}.#{connection.quote_column_name(sql)}"
|
|
366
|
+
end
|
|
349
367
|
end
|
|
350
368
|
|
|
351
369
|
def searchable?
|
|
@@ -388,7 +406,7 @@ module ActiveScaffold::DataStructures
|
|
|
388
406
|
def initialize_sort
|
|
389
407
|
self.sort =
|
|
390
408
|
if column && !tableless?
|
|
391
|
-
{sql:
|
|
409
|
+
{sql: name}
|
|
392
410
|
else
|
|
393
411
|
false
|
|
394
412
|
end
|
|
@@ -398,7 +416,7 @@ module ActiveScaffold::DataStructures
|
|
|
398
416
|
self.search_sql =
|
|
399
417
|
unless virtual?
|
|
400
418
|
if association.nil?
|
|
401
|
-
|
|
419
|
+
name unless tableless?
|
|
402
420
|
elsif association.allow_join?
|
|
403
421
|
[association.quoted_table_name, association.quoted_primary_key].join('.') unless association.klass < ActiveScaffold::Tableless
|
|
404
422
|
end
|
|
@@ -517,14 +535,14 @@ module ActiveScaffold::DataStructures
|
|
|
517
535
|
def initialize(name, active_record_class, delegated_association = nil) # :nodoc:
|
|
518
536
|
@name = name.to_sym
|
|
519
537
|
@active_record_class = active_record_class
|
|
520
|
-
@column = _columns_hash[name.to_s]
|
|
538
|
+
@column = db_column = _columns_hash[name.to_s]
|
|
521
539
|
if @column.nil? && active_record? && active_record_class._default_attributes.key?(name.to_s)
|
|
522
540
|
@column = active_record_class._default_attributes[name.to_s]
|
|
523
541
|
end
|
|
524
542
|
@disable_on_update_column = true
|
|
525
543
|
@db_default_value = ActiveScaffold::OrmChecks.default_value active_record_class, name if @column
|
|
526
544
|
@delegated_association = delegated_association
|
|
527
|
-
@cache_key = [@active_record_class.name, name].compact.
|
|
545
|
+
@cache_key = [@active_record_class.name, name].compact.join('#')
|
|
528
546
|
setup_association_info
|
|
529
547
|
|
|
530
548
|
@link = nil
|
|
@@ -538,6 +556,9 @@ module ActiveScaffold::DataStructures
|
|
|
538
556
|
@select_columns = default_select_columns
|
|
539
557
|
|
|
540
558
|
@text = @column.nil? || [:string, :text, :citext, String].include?(column_type)
|
|
559
|
+
if active_record? && db_column&.collation.present? && active_record_class.connection.adapter_name.in?(%w[PostgreSQL PostGIS])
|
|
560
|
+
@like_operator = 'LIKE'
|
|
561
|
+
end
|
|
541
562
|
@number = false
|
|
542
563
|
setup_defaults_for_column if @column
|
|
543
564
|
@allow_add_existing = true
|
|
@@ -545,7 +566,7 @@ module ActiveScaffold::DataStructures
|
|
|
545
566
|
|
|
546
567
|
self.includes = [association.name] if association&.allow_join?
|
|
547
568
|
if delegated_association
|
|
548
|
-
self.includes = includes ? [delegated_association.name => includes] : [delegated_association.name]
|
|
569
|
+
self.includes = includes ? [{delegated_association.name => includes}] : [delegated_association.name]
|
|
549
570
|
end
|
|
550
571
|
self.subform_includes = true if association
|
|
551
572
|
|
|
@@ -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
|