katalyst-tables 2.0.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|