katalyst-tables 2.0.0 → 2.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +151 -103
- data/app/assets/config/katalyst-tables.js +1 -0
- data/app/assets/javascripts/controllers/tables/turbo_collection_controller.js +22 -0
- data/app/components/concerns/katalyst/tables/configurable_component.rb +31 -0
- data/app/components/concerns/katalyst/tables/has_html_attributes.rb +45 -0
- data/app/components/concerns/katalyst/tables/has_table_content.rb +33 -0
- data/app/components/concerns/katalyst/tables/sortable.rb +32 -0
- data/app/components/concerns/katalyst/tables/turbo_replaceable.rb +62 -0
- data/app/components/katalyst/table_component.rb +51 -40
- data/app/components/katalyst/tables/body_cell_component.rb +4 -4
- data/app/components/katalyst/tables/body_row_component.rb +3 -3
- data/app/components/katalyst/tables/empty_caption_component.html.erb +6 -0
- data/app/components/katalyst/tables/empty_caption_component.rb +38 -0
- data/app/components/katalyst/tables/header_cell_component.rb +20 -16
- data/app/components/katalyst/tables/header_row_component.rb +6 -5
- data/app/components/katalyst/tables/pagy_nav_component.rb +26 -0
- data/app/components/katalyst/turbo/pagy_nav_component.rb +23 -0
- data/app/components/katalyst/turbo/table_component.rb +48 -0
- data/app/models/concerns/katalyst/tables/collection/core.rb +67 -0
- data/app/models/concerns/katalyst/tables/collection/pagination.rb +66 -0
- data/app/models/concerns/katalyst/tables/collection/reducers.rb +67 -0
- data/app/models/concerns/katalyst/tables/collection/sorting.rb +63 -0
- data/app/models/katalyst/tables/collection.rb +32 -0
- data/config/importmap.rb +7 -0
- data/lib/katalyst/tables/backend/sort_form.rb +16 -2
- data/lib/katalyst/tables/engine.rb +13 -0
- data/lib/katalyst/tables/frontend/helper.rb +4 -14
- data/lib/katalyst/tables/version.rb +1 -1
- metadata +34 -2
@@ -11,38 +11,61 @@ module Katalyst
|
|
11
11
|
# <% end %>
|
12
12
|
# ```
|
13
13
|
class TableComponent < ViewComponent::Base
|
14
|
-
include
|
15
|
-
include Tables::
|
14
|
+
include Tables::ConfigurableComponent
|
15
|
+
include Tables::HasHtmlAttributes
|
16
|
+
include Tables::HasTableContent
|
17
|
+
|
18
|
+
attr_reader :collection, :object_name
|
19
|
+
|
20
|
+
config_component :header_row, default: "Katalyst::Tables::HeaderRowComponent"
|
21
|
+
config_component :header_cell, default: "Katalyst::Tables::HeaderCellComponent"
|
22
|
+
config_component :body_row, default: "Katalyst::Tables::BodyRowComponent"
|
23
|
+
config_component :body_cell, default: "Katalyst::Tables::BodyCellComponent"
|
24
|
+
config_component :caption, default: "Katalyst::Tables::EmptyCaptionComponent"
|
25
|
+
|
26
|
+
# Construct a new table component. This entry point supports a large number
|
27
|
+
# of options for customizing the table. The most common options are:
|
28
|
+
# - `collection`: the collection to render
|
29
|
+
# - `sorting`: the sorting to apply to the collection (defaults to collection.storing if available)
|
30
|
+
# - `header`: whether to render the header row (defaults to true, supports options)
|
31
|
+
# - `caption`: whether to render the caption (defaults to true, supports options)
|
32
|
+
# - `object_name`: the name of the object to use for partial rendering (defaults to collection.model_name.i18n_key)
|
33
|
+
# - `partial`: the name of the partial to use for rendering each row (defaults to collection.model_name.param_key)
|
34
|
+
# - `as`: the name of the local variable to use for rendering each row (defaults to collection.model_name.param_key)
|
35
|
+
# In addition to these options, standard HTML attributes can be passed which will be added to the table tag.
|
36
|
+
def initialize(collection:,
|
37
|
+
sorting: nil,
|
38
|
+
header: true,
|
39
|
+
caption: false,
|
40
|
+
**html_attributes)
|
41
|
+
@collection = collection
|
16
42
|
|
17
|
-
|
43
|
+
# sorting: instance of Katalyst::Tables::Backend::SortForm. If not provided will be inferred from the collection.
|
44
|
+
@sorting = sorting || html_attributes.delete(:sort) # backwards compatibility with old `sort` option
|
18
45
|
|
19
|
-
|
20
|
-
|
46
|
+
# header: true means render the header row, header: false means no header row, if a hash, passes as options
|
47
|
+
@header = header
|
48
|
+
@header_options = (header if header.is_a?(Hash)) || {}
|
21
49
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
config_accessor :body_cell
|
50
|
+
# caption: true means render the caption, caption: false means no caption, if a hash, passes as options
|
51
|
+
@caption = caption
|
52
|
+
@caption_options = (caption if caption.is_a?(Hash)) || {}
|
26
53
|
|
27
|
-
|
28
|
-
sort: nil,
|
29
|
-
header: true,
|
30
|
-
object_name: collection.try(:model_name)&.i18n_key,
|
31
|
-
**html_options)
|
32
|
-
super
|
33
|
-
|
34
|
-
@collection = collection
|
35
|
-
@sort = sort
|
36
|
-
@header = header
|
37
|
-
@object_name = object_name
|
54
|
+
super(**html_attributes)
|
38
55
|
end
|
39
56
|
|
40
57
|
def call
|
41
|
-
tag.table(
|
42
|
-
|
58
|
+
tag.table(**html_attributes) do
|
59
|
+
concat(caption)
|
60
|
+
concat(thead)
|
61
|
+
concat(tbody)
|
43
62
|
end
|
44
63
|
end
|
45
64
|
|
65
|
+
def caption
|
66
|
+
caption_component&.new(self)&.render_in(view_context) if @caption
|
67
|
+
end
|
68
|
+
|
46
69
|
def thead
|
47
70
|
return "".html_safe unless @header
|
48
71
|
|
@@ -61,31 +84,19 @@ module Katalyst
|
|
61
84
|
|
62
85
|
def render_header
|
63
86
|
# extract the column's block from the slot and pass it to the cell for rendering
|
64
|
-
|
87
|
+
header_row_component.new(self, **@header_options).render_in(view_context, &row_proc)
|
65
88
|
end
|
66
89
|
|
67
90
|
def render_row(record)
|
68
|
-
|
69
|
-
|
70
|
-
self.class.body_row_component.new(self, record).render_in(view_context) do |row|
|
71
|
-
block.call(row, record)
|
91
|
+
body_row_component.new(self, record).render_in(view_context) do |row|
|
92
|
+
row_proc.call(row, record)
|
72
93
|
end
|
73
94
|
end
|
74
95
|
|
75
|
-
def
|
76
|
-
@
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.header_cell_component
|
80
|
-
@header_cell_component ||= const_get(config.header_cell || "Katalyst::Tables::HeaderCellComponent")
|
81
|
-
end
|
82
|
-
|
83
|
-
def self.body_row_component
|
84
|
-
@body_row_component ||= const_get(config.body_row || "Katalyst::Tables::BodyRowComponent")
|
85
|
-
end
|
96
|
+
def sorting
|
97
|
+
return @sorting if @sorting.present?
|
86
98
|
|
87
|
-
|
88
|
-
@body_cell_component ||= const_get(config.body_cell || "Katalyst::Tables::BodyCellComponent")
|
99
|
+
collection.sorting if collection.respond_to?(:sorting)
|
89
100
|
end
|
90
101
|
end
|
91
102
|
end
|
@@ -3,12 +3,12 @@
|
|
3
3
|
module Katalyst
|
4
4
|
module Tables
|
5
5
|
class BodyCellComponent < ViewComponent::Base # :nodoc:
|
6
|
-
include
|
6
|
+
include HasHtmlAttributes
|
7
7
|
|
8
8
|
attr_reader :record
|
9
9
|
|
10
|
-
def initialize(table, record, attribute, heading: false, **
|
11
|
-
super(**
|
10
|
+
def initialize(table, record, attribute, heading: false, **html_attributes)
|
11
|
+
super(**html_attributes)
|
12
12
|
|
13
13
|
@table = table
|
14
14
|
@record = record
|
@@ -24,7 +24,7 @@ module Katalyst
|
|
24
24
|
def call
|
25
25
|
content # ensure content is set before rendering options
|
26
26
|
|
27
|
-
content_tag(@type, content,
|
27
|
+
content_tag(@type, content, **html_attributes)
|
28
28
|
end
|
29
29
|
|
30
30
|
# @return the object for this row.
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Katalyst
|
4
4
|
module Tables
|
5
5
|
class BodyRowComponent < ViewComponent::Base # :nodoc:
|
6
|
-
include
|
6
|
+
include HasHtmlAttributes
|
7
7
|
|
8
8
|
renders_many :columns, ->(component) { component }
|
9
9
|
|
@@ -17,7 +17,7 @@ module Katalyst
|
|
17
17
|
def call
|
18
18
|
content # generate content before rendering
|
19
19
|
|
20
|
-
tag.tr(
|
20
|
+
tag.tr(**html_attributes) do
|
21
21
|
columns.each do |column|
|
22
22
|
concat(column.to_s)
|
23
23
|
end
|
@@ -25,7 +25,7 @@ module Katalyst
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def cell(attribute, **options, &block)
|
28
|
-
with_column(@table.
|
28
|
+
with_column(@table.body_cell_component.new(@table, @record, attribute, **options), &block)
|
29
29
|
end
|
30
30
|
|
31
31
|
def header?
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
class EmptyCaptionComponent < ViewComponent::Base # :nodoc:
|
6
|
+
include HasHtmlAttributes
|
7
|
+
|
8
|
+
def initialize(table, **html_attributes)
|
9
|
+
super(**html_attributes)
|
10
|
+
|
11
|
+
@table = table
|
12
|
+
end
|
13
|
+
|
14
|
+
def render?
|
15
|
+
@table.collection.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def filtered?
|
19
|
+
@table.collection.respond_to?(:filtered?) && @table.collection.filtered?
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear_filters_path
|
23
|
+
url_for
|
24
|
+
end
|
25
|
+
|
26
|
+
def plural_human_model_name
|
27
|
+
human = @table.model_name&.human || @table.object_name.to_s.humanize
|
28
|
+
human.pluralize.downcase
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def default_attributes
|
34
|
+
{ align: "bottom" }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -4,25 +4,28 @@ module Katalyst
|
|
4
4
|
module Tables
|
5
5
|
class HeaderCellComponent < ViewComponent::Base # :nodoc:
|
6
6
|
include Frontend::Helper
|
7
|
+
include HasHtmlAttributes
|
8
|
+
include Sortable
|
7
9
|
|
8
|
-
delegate :object_name, :
|
10
|
+
delegate :object_name, :collection, :sorting, to: :@table
|
9
11
|
|
10
|
-
def initialize(table, attribute, label: nil, **
|
11
|
-
super(**
|
12
|
+
def initialize(table, attribute, label: nil, link: {}, **html_attributes)
|
13
|
+
super(**html_attributes)
|
12
14
|
|
13
|
-
@table
|
14
|
-
@attribute
|
15
|
-
@value
|
15
|
+
@table = table
|
16
|
+
@attribute = attribute
|
17
|
+
@value = label
|
18
|
+
@link_attributes = link
|
16
19
|
end
|
17
20
|
|
18
21
|
def call
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
tag.th(**html_attributes) do
|
23
|
+
if sortable?(@attribute)
|
24
|
+
link_to(value, sort_url(@attribute), **@link_attributes)
|
25
|
+
else
|
26
|
+
value
|
27
|
+
end
|
28
|
+
end
|
26
29
|
end
|
27
30
|
|
28
31
|
def value
|
@@ -45,9 +48,10 @@ module Katalyst
|
|
45
48
|
|
46
49
|
private
|
47
50
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
+
def default_attributes
|
52
|
+
return {} unless sorting&.supports?(collection, @attribute)
|
53
|
+
|
54
|
+
{ data: { sort: sorting.status(@attribute) } }
|
51
55
|
end
|
52
56
|
end
|
53
57
|
end
|
@@ -3,20 +3,21 @@
|
|
3
3
|
module Katalyst
|
4
4
|
module Tables
|
5
5
|
class HeaderRowComponent < ViewComponent::Base # :nodoc:
|
6
|
-
include
|
6
|
+
include HasHtmlAttributes
|
7
7
|
|
8
8
|
renders_many :columns, ->(component) { component }
|
9
9
|
|
10
|
-
def initialize(table)
|
10
|
+
def initialize(table, link: {})
|
11
11
|
super()
|
12
12
|
|
13
|
-
@table
|
13
|
+
@table = table
|
14
|
+
@link_attributes = link
|
14
15
|
end
|
15
16
|
|
16
17
|
def call
|
17
18
|
content # generate content before rendering
|
18
19
|
|
19
|
-
tag.tr(
|
20
|
+
tag.tr(**html_attributes) do
|
20
21
|
columns.each do |column|
|
21
22
|
concat(column.to_s)
|
22
23
|
end
|
@@ -24,7 +25,7 @@ module Katalyst
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def cell(attribute, **options, &block)
|
27
|
-
with_column(@table.
|
28
|
+
with_column(@table.header_cell_component.new(@table, attribute, link: @link_attributes, **options), &block)
|
28
29
|
end
|
29
30
|
|
30
31
|
def header?
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
class PagyNavComponent < ViewComponent::Base # :nodoc:
|
6
|
+
include Pagy::Frontend
|
7
|
+
|
8
|
+
attr_reader :pagy_options
|
9
|
+
|
10
|
+
def initialize(collection: nil, pagy: nil, **pagy_options)
|
11
|
+
super()
|
12
|
+
|
13
|
+
pagy ||= collection&.pagination if collection.respond_to?(:pagination)
|
14
|
+
|
15
|
+
raise ArgumentError, "pagy is required" if pagy.blank?
|
16
|
+
|
17
|
+
@pagy = pagy
|
18
|
+
@pagy_options = pagy_options
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
pagy_nav(@pagy, **pagy_options).html_safe # rubocop:disable Rails/OutputSafety
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Turbo
|
5
|
+
class PagyNavComponent < Tables::PagyNavComponent # :nodoc:
|
6
|
+
include Tables::TurboReplaceable
|
7
|
+
|
8
|
+
def initialize(id:, **options)
|
9
|
+
super(pagy_id: id, **options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def id
|
13
|
+
pagy_options[:pagy_id]
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def pagy_options
|
19
|
+
super.merge(link_extra: "data-turbo-stream")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Turbo
|
5
|
+
# Renders a table that uses turbo stream replacement when sorting or
|
6
|
+
# paginating.
|
7
|
+
class TableComponent < ::Katalyst::TableComponent
|
8
|
+
include Tables::TurboReplaceable
|
9
|
+
|
10
|
+
def initialize(collection:, id:, header: true, **options)
|
11
|
+
header = if header.is_a?(Hash)
|
12
|
+
default_header_options.merge(header)
|
13
|
+
elsif header
|
14
|
+
default_header_options
|
15
|
+
end
|
16
|
+
|
17
|
+
super(collection: collection, header: header, id: id, **options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def id
|
21
|
+
html_attributes[:id]
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def default_attributes
|
27
|
+
{
|
28
|
+
data: {
|
29
|
+
controller: "tables--turbo-collection",
|
30
|
+
tables__turbo_collection_url_value: current_path,
|
31
|
+
tables__turbo_collection_sort_value: collection.sort
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_path
|
37
|
+
params = collection.to_params
|
38
|
+
query_string = params.empty? ? "" : "?#{Rack::Utils.build_nested_query(params)}"
|
39
|
+
|
40
|
+
"#{request.path}#{query_string}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_header_options
|
44
|
+
{ link: { data: { turbo_stream: "" } } }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
module Core # :nodoc:
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
include ActiveModel::Model
|
10
|
+
include ActiveModel::Attributes
|
11
|
+
include ActiveModel::Dirty
|
12
|
+
include ActiveSupport::Configurable
|
13
|
+
|
14
|
+
include Reducers
|
15
|
+
|
16
|
+
included do
|
17
|
+
attr_accessor :items
|
18
|
+
|
19
|
+
delegate :model, :to_model, :each, :count, :empty?, to: :items, allow_nil: true
|
20
|
+
delegate :model_name, to: :model, allow_nil: true
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(**options)
|
24
|
+
super
|
25
|
+
|
26
|
+
clear_changes_information
|
27
|
+
end
|
28
|
+
|
29
|
+
def filter
|
30
|
+
# no-op by default
|
31
|
+
end
|
32
|
+
|
33
|
+
def filtered?
|
34
|
+
!self.class.new.filters.eql?(filters)
|
35
|
+
end
|
36
|
+
|
37
|
+
def filters
|
38
|
+
attributes.except("page", "sort")
|
39
|
+
end
|
40
|
+
|
41
|
+
def apply(items)
|
42
|
+
@items = items
|
43
|
+
reducers.build do |_|
|
44
|
+
filter
|
45
|
+
self
|
46
|
+
end.call(self)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def with_params(params)
|
51
|
+
self.attributes = params.permit(self.class.attribute_types.keys)
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a hash of the current attributes that have changed from defaults.
|
57
|
+
def to_params
|
58
|
+
attributes.slice(*changed)
|
59
|
+
end
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
"#<#{self.class.name} @attributes=#{attributes.inspect} @model=\"#{model}\" @count=#{items&.count}>"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pagy/backend"
|
4
|
+
|
5
|
+
module Katalyst
|
6
|
+
module Tables
|
7
|
+
module Collection
|
8
|
+
# Adds pagination support for a collection.
|
9
|
+
#
|
10
|
+
# Pagination will be applied if the collection is configured to paginate
|
11
|
+
# by either specifying `config.paginate = true` or passing
|
12
|
+
# `paginate: true` to the initializer.
|
13
|
+
#
|
14
|
+
# If the value given to `paginate` is a hash, it will be passed to the
|
15
|
+
# `pagy` gem as options.
|
16
|
+
#
|
17
|
+
# If `page` is present in params it will be passed to pagy.
|
18
|
+
module Pagination
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
included do
|
22
|
+
attr_accessor :pagination
|
23
|
+
|
24
|
+
attribute :page, :integer, default: 1
|
25
|
+
|
26
|
+
config_accessor :paginate
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(paginate: config.paginate, **options)
|
30
|
+
super(**options)
|
31
|
+
|
32
|
+
@paginate = paginate
|
33
|
+
end
|
34
|
+
|
35
|
+
def paginate?
|
36
|
+
!!@paginate
|
37
|
+
end
|
38
|
+
|
39
|
+
def paginate_options
|
40
|
+
@paginate.is_a?(Hash) ? @paginate : {}
|
41
|
+
end
|
42
|
+
|
43
|
+
class Paginate # :nodoc:
|
44
|
+
include Pagy::Backend
|
45
|
+
|
46
|
+
def initialize(app)
|
47
|
+
@app = app
|
48
|
+
end
|
49
|
+
|
50
|
+
def call(collection)
|
51
|
+
@collection = @app.call(collection)
|
52
|
+
if collection.paginate?
|
53
|
+
@collection.pagination, @collection.items = pagy(@collection.items, collection.paginate_options)
|
54
|
+
end
|
55
|
+
@collection
|
56
|
+
end
|
57
|
+
|
58
|
+
# pagy shim
|
59
|
+
def params
|
60
|
+
@collection.attributes.with_indifferent_access
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Tables
|
5
|
+
module Collection
|
6
|
+
# Adds stackable reducers to a collection.
|
7
|
+
# Inspired by ActiveDispatch::MiddlewareStack which unfortunately can't
|
8
|
+
# be used due to monkey patches from gems such as NewRelic which assume
|
9
|
+
# it is only used for Rack middleware.
|
10
|
+
module Reducers
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
included do
|
14
|
+
class_attribute :reducers, default: Stack.new
|
15
|
+
end
|
16
|
+
|
17
|
+
class_methods do
|
18
|
+
delegate :use, :insert, to: :reducers
|
19
|
+
end
|
20
|
+
|
21
|
+
class Stack # :nodoc:
|
22
|
+
def initialize
|
23
|
+
@stack = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def use(klass)
|
27
|
+
@stack << Reducer.new(klass) unless index(klass)
|
28
|
+
end
|
29
|
+
|
30
|
+
def insert(other, klass)
|
31
|
+
@stack.insert(index(other), Reducer.new(klass))
|
32
|
+
end
|
33
|
+
|
34
|
+
def index(klass)
|
35
|
+
@stack.index(Reducer.new(klass))
|
36
|
+
end
|
37
|
+
|
38
|
+
def build(&block)
|
39
|
+
@stack.freeze.reduce(block) do |app, reducer|
|
40
|
+
reducer.build(app)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Reducer # :nodoc:
|
46
|
+
attr_reader :klass
|
47
|
+
|
48
|
+
def initialize(klass)
|
49
|
+
@klass = klass
|
50
|
+
end
|
51
|
+
|
52
|
+
def build(app)
|
53
|
+
klass.new(app)
|
54
|
+
end
|
55
|
+
|
56
|
+
def ==(other)
|
57
|
+
klass.name == other.klass.name
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
"#<#{self.class.name} #{klass.name}>"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pagy/backend"
|
4
|
+
|
5
|
+
module Katalyst
|
6
|
+
module Tables
|
7
|
+
module Collection
|
8
|
+
# Adds sorting support to a collection.
|
9
|
+
#
|
10
|
+
# Sorting will be applied if the collection is configured with a default
|
11
|
+
# sorting configuration by either specifying
|
12
|
+
# `config.sorting = "column direction"` or passing
|
13
|
+
# `sorting: "column direction"` to the initializer.
|
14
|
+
#
|
15
|
+
# If `sort` is present in params it will override the default sorting.
|
16
|
+
module Sorting
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
|
19
|
+
included do
|
20
|
+
config_accessor :sorting
|
21
|
+
attr_accessor :sorting
|
22
|
+
|
23
|
+
attribute :sort, :string
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(sorting: config.sorting, **options)
|
27
|
+
@sorting = Backend::SortForm.parse(sorting) if sorting
|
28
|
+
|
29
|
+
super(sort: sorting, **options) # set default sort based on config
|
30
|
+
end
|
31
|
+
|
32
|
+
def sort=(value)
|
33
|
+
return unless @sorting
|
34
|
+
|
35
|
+
# update internal proxy
|
36
|
+
@sorting = Backend::SortForm.parse(value, default: attribute_was(:sort))
|
37
|
+
|
38
|
+
# update attribute based on normalized value
|
39
|
+
super(@sorting.to_param)
|
40
|
+
end
|
41
|
+
|
42
|
+
class Sort # :nodoc:
|
43
|
+
include Backend
|
44
|
+
|
45
|
+
def initialize(app)
|
46
|
+
@app = app
|
47
|
+
end
|
48
|
+
|
49
|
+
def call(collection)
|
50
|
+
@collection = @app.call(collection)
|
51
|
+
@collection.sorting, @collection.items = @collection.sorting.apply(@collection.items) if @collection.sorting
|
52
|
+
@collection
|
53
|
+
end
|
54
|
+
|
55
|
+
# pagy shim
|
56
|
+
def params
|
57
|
+
@collection.attributes
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|