katalyst-tables 3.0.0.beta1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -2
  3. data/README.md +56 -190
  4. data/app/assets/builds/katalyst/tables.esm.js +17 -47
  5. data/app/assets/builds/katalyst/tables.js +17 -47
  6. data/app/assets/builds/katalyst/tables.min.js +1 -1
  7. data/app/assets/builds/katalyst/tables.min.js.map +1 -1
  8. data/app/components/concerns/katalyst/tables/has_table_content.rb +17 -8
  9. data/app/components/concerns/katalyst/tables/identifiable.rb +51 -0
  10. data/app/components/concerns/katalyst/tables/orderable.rb +35 -105
  11. data/app/components/concerns/katalyst/tables/selectable.rb +18 -75
  12. data/app/components/concerns/katalyst/tables/sortable.rb +51 -17
  13. data/app/components/katalyst/table_component.html.erb +4 -4
  14. data/app/components/katalyst/table_component.rb +271 -53
  15. data/app/components/katalyst/tables/body_row_component.html.erb +5 -0
  16. data/app/components/katalyst/tables/body_row_component.rb +4 -31
  17. data/app/components/katalyst/tables/cell_component.rb +85 -0
  18. data/app/components/katalyst/tables/{body → cells}/boolean_component.rb +8 -2
  19. data/app/components/katalyst/tables/{body → cells}/currency_component.rb +7 -7
  20. data/app/components/katalyst/tables/{body → cells}/date_component.rb +12 -9
  21. data/app/components/katalyst/tables/{body → cells}/date_time_component.rb +13 -10
  22. data/app/components/katalyst/tables/{body → cells}/number_component.rb +5 -5
  23. data/app/components/katalyst/tables/cells/ordinal_component.rb +44 -0
  24. data/app/components/katalyst/tables/{body → cells}/rich_text_component.rb +8 -5
  25. data/app/components/katalyst/tables/cells/select_component.rb +39 -0
  26. data/app/components/katalyst/tables/data.rb +30 -0
  27. data/app/components/katalyst/tables/header_row_component.html.erb +5 -0
  28. data/app/components/katalyst/tables/header_row_component.rb +4 -25
  29. data/app/components/katalyst/tables/label.rb +37 -0
  30. data/app/components/katalyst/tables/orderable/form_component.rb +38 -0
  31. data/app/components/katalyst/tables/selectable/form_component.html.erb +3 -3
  32. data/app/components/katalyst/tables/selectable/form_component.rb +8 -11
  33. data/app/controllers/concerns/katalyst/tables/backend.rb +2 -28
  34. data/app/helpers/katalyst/tables/frontend.rb +48 -2
  35. data/app/javascript/tables/application.js +0 -5
  36. data/app/javascript/tables/orderable/form_controller.js +8 -6
  37. data/app/javascript/tables/orderable/item_controller.js +9 -0
  38. data/app/models/concerns/katalyst/tables/collection/core.rb +6 -1
  39. data/app/models/concerns/katalyst/tables/collection/sorting.rb +85 -17
  40. data/app/models/katalyst/tables/collection/array.rb +38 -0
  41. data/app/models/katalyst/tables/collection/base.rb +4 -0
  42. data/config/locales/tables.en.yml +0 -6
  43. data/lib/katalyst/tables/config.rb +23 -0
  44. data/lib/katalyst/tables.rb +9 -0
  45. metadata +22 -29
  46. data/app/components/concerns/katalyst/tables/body/typed_columns.rb +0 -132
  47. data/app/components/concerns/katalyst/tables/configurable_component.rb +0 -52
  48. data/app/components/concerns/katalyst/tables/header/typed_columns.rb +0 -179
  49. data/app/components/katalyst/tables/body/attachment_component.rb +0 -58
  50. data/app/components/katalyst/tables/body/link_component.rb +0 -40
  51. data/app/components/katalyst/tables/body_cell_component.rb +0 -55
  52. data/app/components/katalyst/tables/header/attachment_component.rb +0 -15
  53. data/app/components/katalyst/tables/header/boolean_component.rb +0 -15
  54. data/app/components/katalyst/tables/header/currency_component.rb +0 -15
  55. data/app/components/katalyst/tables/header/date_component.rb +0 -15
  56. data/app/components/katalyst/tables/header/date_time_component.rb +0 -15
  57. data/app/components/katalyst/tables/header/link_component.rb +0 -15
  58. data/app/components/katalyst/tables/header/number_component.rb +0 -15
  59. data/app/components/katalyst/tables/header/rich_text_component.rb +0 -15
  60. data/app/components/katalyst/tables/header_cell_component.rb +0 -97
  61. data/app/helpers/katalyst/tables/frontend/helper.rb +0 -31
  62. data/app/javascript/tables/turbo/collection_controller.js +0 -38
  63. data/app/models/katalyst/tables/collection/sort_form.rb +0 -120
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- using Katalyst::HtmlAttributes::HasHtmlAttributes
4
-
5
3
  module Katalyst
6
4
  module Tables
7
- module Body
5
+ module Cells
8
6
  # Formats the value as a number
9
7
  #
10
8
  # Adds a class to the cell to allow for custom styling
11
- class NumberComponent < BodyCellComponent
9
+ class NumberComponent < CellComponent
12
10
  def rendered_value
13
11
  value.present? ? number_to_human(value) : ""
14
12
  end
15
13
 
14
+ private
15
+
16
16
  def default_html_attributes
17
- super.merge_html(class: "type-number")
17
+ { class: "type-number" }
18
18
  end
19
19
  end
20
20
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Cells
6
+ class OrdinalComponent < CellComponent
7
+ def initialize(primary_key:, **)
8
+ super(**)
9
+
10
+ @primary_key = primary_key
11
+ end
12
+
13
+ def rendered_value
14
+ t("katalyst.tables.orderable.value")
15
+ end
16
+
17
+ private
18
+
19
+ def default_html_attributes
20
+ if @row.header?
21
+ { class: "ordinal" }
22
+ else
23
+ {
24
+ class: "ordinal",
25
+ data: {
26
+ controller: Orderable::ITEM_CONTROLLER,
27
+ "#{Orderable::ITEM_CONTROLLER}-params-value": params.to_json,
28
+ },
29
+ }
30
+ end
31
+ end
32
+
33
+ def params
34
+ {
35
+ id_name: @primary_key,
36
+ id_value: record.public_send(@primary_key),
37
+ index_name: column,
38
+ index_value: record.public_send(column),
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,16 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- using Katalyst::HtmlAttributes::HasHtmlAttributes
4
-
5
3
  module Katalyst
6
4
  module Tables
7
- module Body
5
+ module Cells
8
6
  # Displays the plain text for rich text content
9
7
  #
10
8
  # Adds a title attribute to allow for hover over display of the full content
11
- class RichTextComponent < BodyCellComponent
9
+ class RichTextComponent < CellComponent
10
+ private
11
+
12
12
  def default_html_attributes
13
- { title: value.to_plain_text }
13
+ {
14
+ class: "type-rich-text",
15
+ title: (value.to_plain_text unless row.header?),
16
+ }
14
17
  end
15
18
  end
16
19
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Cells
6
+ class SelectComponent < CellComponent
7
+ def initialize(params:, form_id:, **)
8
+ super(**)
9
+
10
+ @params = params
11
+ @form_id = form_id
12
+ end
13
+
14
+ def rendered_value
15
+ tag.input(type: :checkbox)
16
+ end
17
+
18
+ private
19
+
20
+ def default_html_attributes
21
+ if @row.header?
22
+ { class: "selection" }
23
+ else
24
+ {
25
+ class: "selection",
26
+ data: {
27
+ controller: Selectable::ITEM_CONTROLLER,
28
+ "#{Selectable::ITEM_CONTROLLER}-params-value" => @params.to_json,
29
+ "#{Selectable::ITEM_CONTROLLER}-#{Selectable::FORM_CONTROLLER}-outlet" => "##{@form_id}",
30
+ action: "change->#{Selectable::ITEM_CONTROLLER}#change",
31
+ turbo_permanent: "",
32
+ },
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ class Data
6
+ def initialize(record:, column:)
7
+ @record = record
8
+ @column = column
9
+ end
10
+
11
+ def value
12
+ return @value if defined?(@value)
13
+
14
+ @value = @record&.public_send(@column)
15
+ end
16
+
17
+ def call
18
+ ActionView::OutputBuffer.new.tap do |output|
19
+ output << value.to_s
20
+ end.to_s
21
+ end
22
+
23
+ alias to_s call
24
+
25
+ def inspect
26
+ "#<#{self.class.name} column: #{@column.inspect}, value: #{value.inspect}>"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ <%= tag.tr(**html_attributes) do %>
2
+ <% cells.each do |cell| %>
3
+ <%= cell %>
4
+ <% end %>
5
+ <% end %>
@@ -4,29 +4,11 @@ module Katalyst
4
4
  module Tables
5
5
  class HeaderRowComponent < ViewComponent::Base # :nodoc:
6
6
  include Katalyst::HtmlAttributes
7
- include Header::TypedColumns
8
7
 
9
- renders_many :columns, ->(component) { component }
8
+ renders_many :cells, ->(cell) { cell }
10
9
 
11
- def initialize(table, link: {})
12
- super()
13
-
14
- @table = table
15
- @link_attributes = link
16
- end
17
-
18
- def call
19
- content # generate content before rendering
20
-
21
- tag.tr(**html_attributes) do
22
- columns.each do |column|
23
- concat(column.to_s)
24
- end
25
- end
26
- end
27
-
28
- def cell(attribute, **, &)
29
- with_column(@table.header_cell_component.new(@table, attribute, link: @link_attributes, **), &)
10
+ def before_render
11
+ content # ensure content is rendered so html_attributes can be set
30
12
  end
31
13
 
32
14
  def header?
@@ -38,11 +20,8 @@ module Katalyst
38
20
  end
39
21
 
40
22
  def inspect
41
- "#<#{self.class.name} link_attributes: #{@link_attributes.inspect}>"
23
+ "#<#{self.class.name}>"
42
24
  end
43
-
44
- # Backwards compatibility with tables 1.0
45
- alias_method :options, :html_attributes=
46
25
  end
47
26
  end
48
27
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ class Label
6
+ def initialize(collection:, column:, label: nil)
7
+ @collection = collection
8
+ @column = column
9
+ @label = label
10
+ end
11
+
12
+ def value
13
+ return @value if defined?(@value)
14
+
15
+ @value = if !@label.nil?
16
+ @label
17
+ elsif @collection.model.present?
18
+ @collection.model.human_attribute_name(@column)
19
+ else
20
+ @column.to_s.humanize.capitalize
21
+ end
22
+ end
23
+
24
+ def call
25
+ ActionView::OutputBuffer.new.tap do |output|
26
+ output << value.to_s
27
+ end.to_s
28
+ end
29
+
30
+ alias to_s call
31
+
32
+ def inspect
33
+ "#<#{self.class.name} column: #{@column.inspect} value: #{value.inspect}>"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Tables
5
+ module Orderable
6
+ class FormComponent < ViewComponent::Base # :nodoc:
7
+ include Katalyst::Tables::Identifiable::Defaults
8
+
9
+ attr_reader :id, :url
10
+
11
+ # @param collection [Katalyst::Tables::Collection::Core] the collection to render
12
+ # @param url [String] the url to submit the form to (e.g. <resources>_order_path)
13
+ # @param id [String] the id of the form element (defaults to <resources>_order_form)
14
+ # @param scope [String] the base scope to use for form inputs (defaults to order[<resources>])
15
+ def initialize(collection:, url:, id: nil, scope: nil)
16
+ super
17
+
18
+ @id = id || Orderable.default_form_id(collection)
19
+ @url = url
20
+ @scope = scope || Orderable.default_scope(collection)
21
+ end
22
+
23
+ def call
24
+ form_with(id:, url:, method: :patch, data: {
25
+ controller: FORM_CONTROLLER,
26
+ "#{FORM_CONTROLLER}-scope-value": @scope,
27
+ }) do |form|
28
+ form.button(hidden: "")
29
+ end
30
+ end
31
+
32
+ def inspect
33
+ "#<#{self.class.name} id: #{id.inspect}, url: #{url.inspect}>"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -4,11 +4,11 @@
4
4
  data: { controller: form_controller,
5
5
  turbo_action: "replace",
6
6
  turbo_permanent: "" },
7
- html: { hidden: "" }) do |form| %>
7
+ html: { action: false, hidden: "" }) do |form| %>
8
8
  <p class="tables--selection--summary">
9
9
  <span data-<%= form_target("count") %>>0</span>
10
- <span data-<%= form_target("singular") %> hidden><%= @table.collection.model_name.singular %></span>
11
- <span data-<%= form_target("plural") %>><%= @table.collection.model_name.plural %></span>
10
+ <span data-<%= form_target("singular") %> hidden><%= @collection.model_name.singular %></span>
11
+ <span data-<%= form_target("plural") %>><%= @collection.model_name.plural %></span>
12
12
  selected
13
13
  </p>
14
14
  <%= content %>
@@ -4,24 +4,21 @@ module Katalyst
4
4
  module Tables
5
5
  module Selectable
6
6
  class FormComponent < ViewComponent::Base # :nodoc:
7
+ include Katalyst::Tables::Identifiable::Defaults
8
+
7
9
  attr_reader :id, :primary_key
8
10
 
9
- def initialize(table:,
11
+ # @param collection [Katalyst::Tables::Collection::Core] the collection to render
12
+ # @param id [String] the id of the form element (defaults to <resources>_selection_form)
13
+ # @param primary_key [String] the primary key of the record in the collection (defaults to :id)
14
+ def initialize(collection:,
10
15
  id: nil,
11
16
  primary_key: :id)
12
17
  super
13
18
 
14
- @table = table
15
- @id = id
19
+ @collection = collection
20
+ @id = id || Selectable.default_form_id(collection)
16
21
  @primary_key = primary_key
17
-
18
- if @id.nil?
19
- table_id = table.try(:id)
20
-
21
- raise ArgumentError, "Table selection requires an id" if table_id.nil?
22
-
23
- @id = "#{table_id}_selection"
24
- end
25
22
  end
26
23
 
27
24
  def inspect
@@ -2,35 +2,10 @@
2
2
 
3
3
  module Katalyst
4
4
  module Tables
5
- # Utilities for controllers that are generating collections for visualisation
6
- # in a table view using Katalyst::Tables::Frontend.
7
- #
8
- # Provides `table_sort` for sorting based on column interactions (sort param).
5
+ # Configuration for controllers to specify which TableComponent should be used in associated views.
9
6
  module Backend
10
7
  extend ActiveSupport::Concern
11
8
 
12
- # @deprecated backwards compatibility
13
- class SortForm < Katalyst::Tables::Collection::SortForm
14
- end
15
-
16
- # Sort the given collection by params[:sort], which is set when a user
17
- # interacts with a column header in a frontend table view.
18
- #
19
- # @return [[SortForm, ActiveRecord::Relation]]
20
- def table_sort(collection)
21
- column, direction = params[:sort]&.split
22
- direction = "asc" unless SortForm::DIRECTIONS.include?(direction)
23
-
24
- SortForm.new(column:,
25
- direction:)
26
- .apply(collection)
27
- end
28
-
29
- def self_referred?
30
- request.referer.present? && URI.parse(request.referer).path == request.path
31
- end
32
- alias self_refered? self_referred?
33
-
34
9
  included do
35
10
  class_attribute :_default_table_component, instance_accessor: false
36
11
  end
@@ -39,8 +14,7 @@ module Katalyst
39
14
  # Set the table component to be used as the default for all tables
40
15
  # in the views rendered by this controller and its subclasses.
41
16
  #
42
- # ==== Parameters
43
- # * <tt>component</tt> - Default table component, an instance of +Katalyst::TableComponent+
17
+ # @param component [Class] the table component class to use
44
18
  def default_table_component(component)
45
19
  self._default_table_component = component
46
20
  end
@@ -4,9 +4,55 @@ module Katalyst
4
4
  module Tables
5
5
  # View Helper for generating HTML tables. Include in your ApplicationHelper, or similar.
6
6
  module Frontend
7
- def table_with(collection:, component: nil, **, &)
7
+ # Construct a new table component. This entry point supports a large number
8
+ # of options for customizing the table. The most common options are:
9
+ # @param collection [Katalyst::Tables::Collection::Core] the collection to render
10
+ # @param header [Boolean] whether to render the header row (defaults to true, supports options)
11
+ # @param caption [Boolean Hash] whether to render the caption (defaults to true, supports options)
12
+ # @param generate_ids [Boolean] whether to generate dom ids for the table and rows
13
+ #
14
+ # Blocks will receive the table in row-rendering mode (with row and record defined):
15
+ # @yieldparam [Katalyst::TableComponent] the row component to render
16
+ # @yieldparam [Object, nil] the object to render, or nil for header rows
17
+ #
18
+ # If no block is provided when the table is rendered then the table will look for a row partial:
19
+ # @param object_name [Symbol] the name of the object to use for partial rendering
20
+ # (defaults to collection.model_name.i18n_key)
21
+ # @param partial [String] the name of the partial to use for rendering each row
22
+ # (defaults to to_partial_path on the object)
23
+ # @param as [Symbol] the name of the local variable to use for rendering each row
24
+ # (defaults to collection.model_name.param_key)
25
+ #
26
+ # In addition to these options, standard HTML attributes can be passed which will be added to the table tag.
27
+ def table_with(collection:,
28
+ component: nil,
29
+ header: true,
30
+ caption: true,
31
+ generate_ids: false,
32
+ object_name: nil,
33
+ partial: nil,
34
+ as: nil,
35
+ **,
36
+ &)
8
37
  component ||= default_table_component_class
9
- render(component.new(collection:, **), &)
38
+ component = component.new(collection:, header:, caption:, generate_ids:, object_name:, partial:, as:, **)
39
+
40
+ render(component, &)
41
+ end
42
+
43
+ # @param collection [Katalyst::Tables::Collection::Core] the collection to render
44
+ # @param url [String] the url to submit the form to (e.g. <resources>_order_path)
45
+ # @param id [String] the id of the form element (defaults to <resources>_order_form)
46
+ # @param scope [String] the base scope to use for form inputs (defaults to order[<resources>])
47
+ def table_orderable_with(collection:, url:, id: nil, scope: nil, &)
48
+ render(Orderable::FormComponent.new(collection:, url:, id:, scope:))
49
+ end
50
+
51
+ # @param collection [Katalyst::Tables::Collection::Core] the collection to render
52
+ # @param id [String] the id of the form element (defaults to <resources>_selection_form)
53
+ # @param primary_key [String] the primary key of the record in the collection (defaults to :id)
54
+ def table_selection_with(collection:, id: nil, primary_key: :id, &)
55
+ render(Selectable::FormComponent.new(collection:, id:, primary_key:), &)
10
56
  end
11
57
 
12
58
  private
@@ -1,4 +1,3 @@
1
- import TurboCollectionController from "./turbo/collection_controller";
2
1
  import OrderableItemController from "./orderable/item_controller";
3
2
  import OrderableListController from "./orderable/list_controller";
4
3
  import OrderableFormController from "./orderable/form_controller";
@@ -6,10 +5,6 @@ import SelectionFormController from "./selection/form_controller";
6
5
  import SelectionItemController from "./selection/item_controller";
7
6
 
8
7
  const Definitions = [
9
- {
10
- identifier: "tables--turbo--collection",
11
- controllerConstructor: TurboCollectionController,
12
- },
13
8
  {
14
9
  identifier: "tables--orderable--item",
15
10
  controllerConstructor: OrderableItemController,
@@ -1,13 +1,15 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
3
  export default class OrderableFormController extends Controller {
4
+ static values = { scope: String };
5
+
4
6
  add(item) {
5
- const { id_name, id_value, index_name } = item.paramsValue;
6
- this.element.insertAdjacentHTML(
7
- "beforeend",
8
- `<input type="hidden" name="${id_name}" value="${id_value}" data-generated>
9
- <input type="hidden" name="${index_name}" value="${item.index}" data-generated>`,
10
- );
7
+ item.params(this.scopeValue).forEach(({ name, value }) => {
8
+ this.element.insertAdjacentHTML(
9
+ "beforeend",
10
+ `<input type="hidden" name="${name}" value="${value}" data-generated>`,
11
+ );
12
+ });
11
13
  }
12
14
 
13
15
  submit() {
@@ -48,6 +48,15 @@ export default class OrderableRowController extends Controller {
48
48
  this.index = index;
49
49
  }
50
50
 
51
+ /** Retrieve params for use in the form */
52
+ params(scope) {
53
+ const { id_name, id_value, index_name } = this.paramsValue;
54
+ return [
55
+ { name: `${scope}[${id_value}][${id_name}]`, value: this.id },
56
+ { name: `${scope}[${id_value}][${index_name}]`, value: this.index },
57
+ ];
58
+ }
59
+
51
60
  /**
52
61
  * Restore any visual changes made during drag and remove the drag state.
53
62
  */
@@ -18,7 +18,7 @@ module Katalyst
18
18
  def permitted_params
19
19
  _default_attributes.to_h.each_with_object([]) do |(k, v), h|
20
20
  h << case v
21
- when Array
21
+ when ::Array
22
22
  { k => [] }
23
23
  else
24
24
  k
@@ -39,6 +39,11 @@ module Katalyst
39
39
  clear_changes_information
40
40
  end
41
41
 
42
+ # Collections that do not include Sorting are never sortable.
43
+ def sortable?
44
+ false
45
+ end
46
+
42
47
  def apply(items)
43
48
  @items = items
44
49
  reducers.build do |_|
@@ -14,27 +14,88 @@ module Katalyst
14
14
  module Sorting
15
15
  extend ActiveSupport::Concern
16
16
 
17
- included do
18
- config_accessor :sorting
19
- attr_accessor :sorting
17
+ DIRECTIONS = %w[asc desc].freeze
18
+
19
+ module SortParams
20
+ refine Hash do
21
+ def to_param
22
+ "#{self[:column]} #{self[:direction]}"
23
+ end
24
+ end
25
+
26
+ refine String do
27
+ def to_param
28
+ to_h.to_param
29
+ end
20
30
 
31
+ def to_h
32
+ column, direction = split(/[ +]/, 2)
33
+
34
+ direction = "asc" unless DIRECTIONS.include?(direction)
35
+ { column:, direction: }
36
+ end
37
+ end
38
+ end
39
+
40
+ using SortParams
41
+
42
+ included do
21
43
  attribute :sort, :string
44
+
45
+ attr_reader :default_sort
22
46
  end
23
47
 
24
48
  def initialize(sorting: config.sorting, **options)
25
- @sorting = SortForm.parse(sorting, default: sorting) if sorting
49
+ @default_sort = sorting.to_param if sorting.present?
50
+
51
+ super(sort: @default_sort, **options) # set default sort based on config
52
+ end
26
53
 
27
- super(sort: @sorting.to_param, **options) # set default sort based on config
54
+ def default_sort?
55
+ sort == @default_sort
28
56
  end
29
57
 
58
+ # Returns true if the collection supports sorting on the given column.
59
+ # A column supports sorting if it is a database column or if
60
+ # the collection responds to `order_by_#{column}(direction)`.
61
+ #
62
+ # @param column [String, Symbol]
63
+ # @return [true, false]
64
+ def sortable?(column = nil)
65
+ if column.nil?
66
+ @default_sort.present?
67
+ else
68
+ items.respond_to?(:"order_by_#{column}") || items.model.has_attribute?(column.to_s)
69
+ end
70
+ end
71
+
72
+ # Set the current sort behaviour of the collection.
73
+ #
74
+ # @param value [String, Hash] "column direction", or { column:, direction: }
30
75
  def sort=(value)
31
- return unless @sorting
76
+ super(value.to_param) if @default_sort
77
+ end
78
+
79
+ # Returns the current sort behaviour of the given column, for use as a
80
+ # column heading class in the table view.
81
+ #
82
+ # @param column [String, Symbol] the table column as defined in table_with
83
+ # @return [String] the current sort behaviour of the given column
84
+ def sort_status(column)
85
+ current, direction = sort.to_h.values_at(:column, :direction)
86
+ direction if column.to_s == current
87
+ end
32
88
 
33
- # update internal proxy
34
- @sorting = SortForm.parse(value, default: @sorting.default)
89
+ # Calculates the sort parameter to apply when the given column is toggled.
90
+ #
91
+ # @param column [String, Symbol]
92
+ # @return [String]
93
+ def toggle_sort(column)
94
+ current, direction = sort.to_h.values_at(:column, :direction)
35
95
 
36
- # update attribute based on normalized value
37
- super(@sorting.to_param)
96
+ return "#{column} asc" unless column.to_s == current
97
+
98
+ direction == "asc" ? "#{column} desc" : "#{column} asc"
38
99
  end
39
100
 
40
101
  class Sort # :nodoc:
@@ -44,15 +105,22 @@ module Katalyst
44
105
  @app = app
45
106
  end
46
107
 
108
+ using SortParams
109
+
47
110
  def call(collection)
48
- @collection = @app.call(collection)
49
- @collection.sorting, @collection.items = @collection.sorting.apply(@collection.items) if @collection.sorting
50
- @collection
51
- end
111
+ collection = @app.call(collection)
112
+
113
+ column, direction = collection.sort.to_h.values_at(:column, :direction)
114
+
115
+ return collection if column.nil?
116
+
117
+ if collection.items.respond_to?(:"order_by_#{column}")
118
+ collection.items = collection.items.reorder(nil).public_send(:"order_by_#{column}", direction.to_sym)
119
+ elsif collection.items.model.has_attribute?(column)
120
+ collection.items = collection.items.reorder(column => direction)
121
+ end
52
122
 
53
- # pagy shim
54
- def params
55
- @collection.attributes
123
+ collection
56
124
  end
57
125
  end
58
126
  end