phlexi-table 0.0.2 → 0.0.3
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/lib/phlexi/table/base.rb +64 -51
- data/lib/phlexi/table/components/actions_column.rb +1 -7
- data/lib/phlexi/table/components/base.rb +4 -5
- data/lib/phlexi/table/components/column.rb +0 -10
- data/lib/phlexi/table/components/concerns/displays_data.rb +3 -42
- data/lib/phlexi/table/components/concerns/displays_header.rb +1 -85
- data/lib/phlexi/table/components/data_column.rb +33 -0
- data/lib/phlexi/table/components/header_cell.rb +19 -0
- data/lib/phlexi/table/components/options/alignment.rb +2 -2
- data/lib/phlexi/table/components/selection_cell.rb +17 -0
- data/lib/phlexi/table/components/selection_column.rb +21 -0
- data/lib/phlexi/table/components/sortable_header_cell.rb +74 -0
- data/lib/phlexi/table/display_theme.rb +6 -0
- data/lib/phlexi/table/html.rb +7 -5
- data/lib/phlexi/table/{table_options → options}/captions.rb +1 -1
- data/lib/phlexi/table/{table_options → options}/descriptions.rb +1 -1
- data/lib/phlexi/table/theme.rb +19 -23
- data/lib/phlexi/table/version.rb +1 -1
- data/lib/phlexi/table/wrapped_object.rb +11 -4
- data/lib/phlexi/table.rb +3 -2
- metadata +24 -7
- data/lib/phlexi/table/components/options/associations.rb +0 -23
- data/lib/phlexi/table/components/options/attachments.rb +0 -23
- data/lib/phlexi/table/components/options/types.rb +0 -140
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea441f5dc6bff0752e14011507ba3274026a46f8900de536f7b1d253fff45c44
|
4
|
+
data.tar.gz: 05dd06af33197b2f8847d2bfab878340cf23d28d9c824b53382497343ae1de13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5db97df1ef5cf3131212ee6ce6a9b043dffd5d97f68e783ceca071b95cbedbb3a7a57086d82d0285cd100714e6aa0994001615f99768a56154cd4296df2eac75
|
7
|
+
data.tar.gz: 48935e08c5e0dfb4b9fb110ebe7a14cffc5d3a73930a0ad7779e89fbe3579c4a19ebe72062deaac07df84b8a47fe73351ee7a0e8555bdadf79c01e02ba72d9df
|
data/lib/phlexi/table/base.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "phlex"
|
4
|
-
# require "phlexi-display"
|
5
|
-
|
6
3
|
module Phlexi
|
7
4
|
module Table
|
8
5
|
# Base class for creating customizable table components
|
@@ -31,15 +28,19 @@ module Phlexi
|
|
31
28
|
# @attr_reader [Hash] columns The columns defined for the table
|
32
29
|
class Base < Phlexi::Table::HTML
|
33
30
|
include Phlex::DeferredRender
|
34
|
-
include Phlexi::Table::
|
35
|
-
include Phlexi::Table::
|
31
|
+
include Phlexi::Table::Options::Captions
|
32
|
+
include Phlexi::Table::Options::Descriptions
|
36
33
|
|
37
|
-
class
|
34
|
+
class DataColumn < Phlexi::Table::Components::DataColumn; end
|
38
35
|
|
39
|
-
class
|
36
|
+
class SelectionColumn < Phlexi::Table::Components::SelectionColumn; end
|
40
37
|
|
41
38
|
class ActionsColumn < Phlexi::Table::Components::ActionsColumn; end
|
42
39
|
|
40
|
+
class ColumnGroup < Phlexi::Table::Components::ColumnGroup; end
|
41
|
+
|
42
|
+
class Display < Phlexi::Display::Base; end
|
43
|
+
|
43
44
|
attr_reader :key, :collection, :columns, :options
|
44
45
|
|
45
46
|
# Initialize a new table component
|
@@ -50,35 +51,55 @@ module Phlexi
|
|
50
51
|
# @option options [String] :class The CSS class(es) for the table
|
51
52
|
# @raise [ArgumentError] If the collection is empty
|
52
53
|
def initialize(collection, **options)
|
53
|
-
|
54
|
+
@collection = Array(collection)
|
55
|
+
raise ArgumentError, "Collection cannot be empty" if @collection.empty?
|
54
56
|
|
55
|
-
@collection = collection
|
56
57
|
@columns = {}
|
57
58
|
@options = options
|
58
59
|
initialize_key
|
59
60
|
end
|
60
61
|
|
62
|
+
def around_template
|
63
|
+
original_template = Phlexi::Display::Theme.instance
|
64
|
+
Phlexi::Display::Theme.instance = Phlexi::Table::DisplayTheme.instance
|
65
|
+
super
|
66
|
+
ensure
|
67
|
+
Phlexi::Display::Theme.instance = original_template
|
68
|
+
end
|
69
|
+
|
70
|
+
def before_template
|
71
|
+
super
|
72
|
+
table_template
|
73
|
+
end
|
74
|
+
|
61
75
|
def view_template
|
62
76
|
render_table
|
63
77
|
end
|
64
78
|
|
65
79
|
def table_template
|
80
|
+
# implement this in subclasses to define the column template
|
66
81
|
end
|
67
82
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
83
|
+
# Add a column to the table
|
84
|
+
#
|
85
|
+
# @param column [Columns::Base] The column object to add
|
86
|
+
# @return [void]
|
87
|
+
# @raise [ArgumentError] If a column with the same key already exists
|
88
|
+
def add_column(column)
|
89
|
+
raise ArgumentError, "Column '#{column.key}' already exists" if @columns.key?(column.key)
|
90
|
+
|
91
|
+
@columns[column.key] = column
|
92
|
+
end
|
72
93
|
|
73
|
-
|
74
|
-
|
94
|
+
def column(key, **, &)
|
95
|
+
add_column(column_class.new(key, self, **, &))
|
75
96
|
end
|
76
97
|
|
77
98
|
def selection_column(key, **, &)
|
78
99
|
raise "Selection column already added" if @has_selection_column
|
79
100
|
|
80
101
|
@has_selection_column = true
|
81
|
-
add_column(
|
102
|
+
add_column(selection_column_class.new(key, self, **, &))
|
82
103
|
end
|
83
104
|
|
84
105
|
def column_group(key, **, &)
|
@@ -97,29 +118,32 @@ module Phlexi
|
|
97
118
|
collection[0]
|
98
119
|
end
|
99
120
|
|
121
|
+
def wrapped_sample
|
122
|
+
@wrapped_sample ||= WrappedObject.new(sample, index: -1, display_class: self.class::Display)
|
123
|
+
end
|
124
|
+
|
100
125
|
def dom_id
|
101
126
|
key
|
102
127
|
end
|
103
128
|
|
104
129
|
def column_class
|
105
|
-
self.class::
|
130
|
+
self.class::DataColumn
|
106
131
|
end
|
107
132
|
|
108
|
-
def
|
109
|
-
self.class::
|
133
|
+
def selection_column_class
|
134
|
+
self.class::SelectionColumn
|
110
135
|
end
|
111
136
|
|
112
137
|
def actions_column_class
|
113
138
|
self.class::ActionsColumn
|
114
139
|
end
|
115
140
|
|
116
|
-
|
117
|
-
|
118
|
-
def before_template
|
119
|
-
super
|
120
|
-
table_template
|
141
|
+
def column_group_class
|
142
|
+
self.class::ColumnGroup
|
121
143
|
end
|
122
144
|
|
145
|
+
protected
|
146
|
+
|
123
147
|
# Render the complete table structure
|
124
148
|
#
|
125
149
|
# @return [void]
|
@@ -190,28 +214,28 @@ module Phlexi
|
|
190
214
|
# @return [void]
|
191
215
|
def render_table_body
|
192
216
|
tbody(**table_body_attributes) do
|
193
|
-
collection.each_with_index do |
|
194
|
-
|
195
|
-
render_table_body_row(
|
217
|
+
collection.each_with_index do |object, index|
|
218
|
+
wrapped_object = WrappedObject.new(object, index:, display_class: self.class::Display)
|
219
|
+
render_table_body_row(wrapped_object)
|
196
220
|
end
|
197
221
|
end
|
198
222
|
end
|
199
223
|
|
200
224
|
# Render a table body row
|
201
225
|
#
|
202
|
-
# @param
|
226
|
+
# @param wrapped_object [Object] The current item from the collection
|
203
227
|
# @return [void]
|
204
|
-
def render_table_body_row(
|
205
|
-
tr(**table_body_row_attributes(
|
228
|
+
def render_table_body_row(wrapped_object)
|
229
|
+
tr(**table_body_row_attributes(wrapped_object)) do
|
206
230
|
columns.each_value do |column|
|
207
231
|
if column.is_a?(Phlexi::Table::Components::Concerns::GroupsColumns)
|
208
232
|
column.columns.each_value do |column|
|
209
|
-
td(**table_data_cell_attributes(
|
233
|
+
td(**table_data_cell_attributes(wrapped_object, column)) { render column.data_cell(wrapped_object) }
|
210
234
|
end
|
211
235
|
elsif column.is_a?(actions_column_class)
|
212
|
-
td(**table_data_cell_attributes(
|
236
|
+
td(**table_data_cell_attributes(wrapped_object, column)) { column.render_actions(wrapped_object) }
|
213
237
|
else
|
214
|
-
td(**table_data_cell_attributes(
|
238
|
+
td(**table_data_cell_attributes(wrapped_object, column)) { render column.data_cell(wrapped_object) }
|
215
239
|
end
|
216
240
|
end
|
217
241
|
end
|
@@ -325,27 +349,27 @@ module Phlexi
|
|
325
349
|
|
326
350
|
# Get the attributes for a table body row element
|
327
351
|
#
|
328
|
-
# @param
|
352
|
+
# @param wrapped_object [Object] The current item from the collection
|
329
353
|
# @return [Hash] The body row attributes
|
330
|
-
def table_body_row_attributes(
|
354
|
+
def table_body_row_attributes(wrapped_object)
|
331
355
|
{
|
332
|
-
id: "#{dom_id}_table_row_#{
|
356
|
+
id: "#{dom_id}_table_row_#{wrapped_object.identifier}",
|
333
357
|
class: tokens("phlexi_table_body_row", themed(:body_row))
|
334
358
|
}
|
335
359
|
end
|
336
360
|
|
337
361
|
# Get the attributes for a table body cell element
|
338
362
|
#
|
339
|
-
# @param
|
363
|
+
# @param wrapped_object [Object] The current item from the collection
|
340
364
|
# @param column [Columns::Base] The column object
|
341
365
|
# @return [Hash] The body cell attributes
|
342
|
-
def table_data_cell_attributes(
|
366
|
+
def table_data_cell_attributes(wrapped_object, column)
|
343
367
|
mix(
|
344
368
|
{
|
345
|
-
id: "#{dom_id}_table_row_#{
|
369
|
+
id: "#{dom_id}_table_row_#{wrapped_object.identifier}_#{column.key}",
|
346
370
|
class: tokens("phlexi_table_data_cell", themed(:body_cell))
|
347
371
|
},
|
348
|
-
column.data_cell_attributes(
|
372
|
+
column.data_cell_attributes(wrapped_object)
|
349
373
|
)
|
350
374
|
end
|
351
375
|
|
@@ -359,17 +383,6 @@ module Phlexi
|
|
359
383
|
}
|
360
384
|
end
|
361
385
|
|
362
|
-
# Add a column to the table
|
363
|
-
#
|
364
|
-
# @param column [Columns::Base] The column object to add
|
365
|
-
# @return [void]
|
366
|
-
# @raise [ArgumentError] If a column with the same key already exists
|
367
|
-
def add_column(column)
|
368
|
-
raise ArgumentError, "Column '#{column.key}' already exists" if @columns.key?(column.key)
|
369
|
-
|
370
|
-
@columns[column.key] = column
|
371
|
-
end
|
372
|
-
|
373
386
|
def initialize_key
|
374
387
|
# always pop these keys
|
375
388
|
# add support for `as` to make it more rails friendly
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "phlex"
|
4
|
-
|
5
3
|
module Phlexi
|
6
4
|
module Table
|
7
5
|
module Components
|
@@ -33,11 +31,7 @@ module Phlexi
|
|
33
31
|
end
|
34
32
|
|
35
33
|
def data_cell_attributes(object)
|
36
|
-
|
37
|
-
attributes[:class] = tokens(
|
38
|
-
themed(:actions_row_cell)
|
39
|
-
)
|
40
|
-
attributes
|
34
|
+
@attributes
|
41
35
|
end
|
42
36
|
|
43
37
|
def type = "actions"
|
@@ -1,16 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "phlex"
|
4
|
-
|
5
3
|
module Phlexi
|
6
4
|
module Table
|
7
5
|
module Components
|
8
6
|
class Base
|
9
7
|
include Phlex::Helpers
|
8
|
+
include Phlexi::Table::HTML::Behaviour
|
10
9
|
|
11
10
|
attr_reader :key, :parent, :options
|
12
11
|
|
13
|
-
delegate :sample, to: :parent
|
12
|
+
delegate :sample, :wrapped_sample, to: :parent
|
14
13
|
|
15
14
|
def initialize(key, parent, **options)
|
16
15
|
@key = key
|
@@ -34,8 +33,8 @@ module Phlexi
|
|
34
33
|
}
|
35
34
|
end
|
36
35
|
|
37
|
-
def
|
38
|
-
|
36
|
+
def type
|
37
|
+
options[:as] || wrapped_sample.field(key).inferred_field_type
|
39
38
|
end
|
40
39
|
end
|
41
40
|
end
|
@@ -1,23 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "phlex"
|
4
|
-
|
5
3
|
module Phlexi
|
6
4
|
module Table
|
7
5
|
module Components
|
8
6
|
class Column < Base
|
9
|
-
include Phlexi::Table::Components::Options::Types
|
10
7
|
include Phlexi::Table::Components::Options::Labels
|
11
8
|
include Phlexi::Table::Components::Options::Alignment
|
12
|
-
include Phlexi::Table::Components::Options::Attachments
|
13
|
-
include Phlexi::Table::Components::Options::Associations
|
14
9
|
include Phlexi::Table::Components::Concerns::DisplaysHeader
|
15
10
|
include Phlexi::Table::Components::Concerns::DisplaysData
|
16
|
-
|
17
|
-
def initialize(*, **, &block)
|
18
|
-
super(*, **)
|
19
|
-
@block = block
|
20
|
-
end
|
21
11
|
end
|
22
12
|
end
|
23
13
|
end
|
@@ -7,41 +7,12 @@ module Phlexi
|
|
7
7
|
module DisplaysData
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
@value = value
|
13
|
-
end
|
14
|
-
|
15
|
-
def view_template
|
16
|
-
plain @value.to_s
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
class SelectionCell < Phlexi::Table::HTML
|
21
|
-
def initialize(value)
|
22
|
-
@value = value
|
23
|
-
end
|
24
|
-
|
25
|
-
def view_template
|
26
|
-
input(type: :checkbox, value: @value, class: themed(:selection_checkbox))
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def data_cell(object)
|
31
|
-
case type
|
32
|
-
when :selection
|
33
|
-
selection_cell_class.new(object.value_of(key))
|
34
|
-
else
|
35
|
-
data_cell_class.new(object.value_of(key))
|
36
|
-
end
|
10
|
+
def data_cell(wrapped_object)
|
11
|
+
raise NotImplementedError, "#{self.class} must implement #data_cell"
|
37
12
|
end
|
38
13
|
|
39
|
-
def data_cell_attributes(
|
14
|
+
def data_cell_attributes(wrapped_object)
|
40
15
|
attributes = @attributes.dup
|
41
|
-
case type
|
42
|
-
when :name, :selection
|
43
|
-
attributes["scope"] = "row"
|
44
|
-
end
|
45
16
|
attributes[:class] = tokens(
|
46
17
|
attributes[:class],
|
47
18
|
themed(:"align_#{alignment}"),
|
@@ -49,16 +20,6 @@ module Phlexi
|
|
49
20
|
)
|
50
21
|
attributes
|
51
22
|
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def selection_cell_class
|
56
|
-
self.class::SelectionCell
|
57
|
-
end
|
58
|
-
|
59
|
-
def data_cell_class
|
60
|
-
self.class::DataCell
|
61
|
-
end
|
62
23
|
end
|
63
24
|
end
|
64
25
|
end
|
@@ -7,80 +7,8 @@ module Phlexi
|
|
7
7
|
module DisplaysHeader
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
|
-
class HeaderCell < Phlexi::Table::HTML
|
11
|
-
def initialize(value, sort_params:)
|
12
|
-
@value = value
|
13
|
-
@sort_params = sort_params
|
14
|
-
end
|
15
|
-
|
16
|
-
def view_template
|
17
|
-
div(class: "flex items-center") do
|
18
|
-
if !@sort_params
|
19
|
-
plain @value.to_s
|
20
|
-
next
|
21
|
-
end
|
22
|
-
|
23
|
-
a(class: "flex items-center", href: @sort_params[:url]) do
|
24
|
-
plain @value.to_s
|
25
|
-
|
26
|
-
sort_icon = Phlexi::Table::Theme.instance.resolve_theme(:sort_icon)
|
27
|
-
sort_icon_active = Phlexi::Table::Theme.instance.resolve_theme(:sort_icon_active)
|
28
|
-
|
29
|
-
span(class: "ml-1.5") {
|
30
|
-
svg(
|
31
|
-
class: tokens(sort_icon, -> { @sort_params[:direction] != "DESC" } => sort_icon_active),
|
32
|
-
aria_hidden: "true",
|
33
|
-
xmlns: "http://www.w3.org/2000/svg",
|
34
|
-
fill: "none",
|
35
|
-
viewbox: "0 0 10 6"
|
36
|
-
) do |s|
|
37
|
-
s.path(
|
38
|
-
stroke: "currentColor",
|
39
|
-
stroke_linecap: "round",
|
40
|
-
stroke_linejoin: "round",
|
41
|
-
stroke_width: "2",
|
42
|
-
d: "M9 5 5 1 1 5"
|
43
|
-
)
|
44
|
-
end
|
45
|
-
svg(
|
46
|
-
class: tokens(sort_icon, -> { @sort_params[:direction] != "ASC" } => sort_icon_active),
|
47
|
-
aria_hidden: "true",
|
48
|
-
xmlns: "http://www.w3.org/2000/svg",
|
49
|
-
fill: "none",
|
50
|
-
viewbox: "0 0 10 6"
|
51
|
-
) do |s|
|
52
|
-
s.path(
|
53
|
-
stroke: "currentColor",
|
54
|
-
stroke_linecap: "round",
|
55
|
-
stroke_linejoin: "round",
|
56
|
-
stroke_width: "2",
|
57
|
-
d: "M1 1 5 5 9 1"
|
58
|
-
)
|
59
|
-
end
|
60
|
-
}
|
61
|
-
end
|
62
|
-
|
63
|
-
next if @sort_params[:position].nil?
|
64
|
-
|
65
|
-
sort_index_clear_link = Phlexi::Table::Theme.instance.resolve_theme(:sort_index_clear_link)
|
66
|
-
sort_index_clear_link_text = Phlexi::Table::Theme.instance.resolve_theme(:sort_index_clear_link_text)
|
67
|
-
sort_index_clear_link_icon = Phlexi::Table::Theme.instance.resolve_theme(:sort_index_clear_link_icon)
|
68
|
-
|
69
|
-
a(href: @sort_params[:reset_url], title: "Clear sort", class: sort_index_clear_link) {
|
70
|
-
span(class: sort_index_clear_link_text) { (@sort_params[:position] + 1).to_s }
|
71
|
-
span(class: sort_index_clear_link_icon) { "✕" }
|
72
|
-
}
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
10
|
def header_cell
|
78
|
-
|
79
|
-
when :selection
|
80
|
-
selection_cell_class.new(nil)
|
81
|
-
else
|
82
|
-
header_cell_class.new(label, sort_params:)
|
83
|
-
end
|
11
|
+
HeaderCell.new(label)
|
84
12
|
end
|
85
13
|
|
86
14
|
def header_cell_attributes
|
@@ -95,18 +23,6 @@ module Phlexi
|
|
95
23
|
end
|
96
24
|
|
97
25
|
def colspan = 1
|
98
|
-
|
99
|
-
protected
|
100
|
-
|
101
|
-
def sort_params
|
102
|
-
options[:sort_params]
|
103
|
-
end
|
104
|
-
|
105
|
-
private
|
106
|
-
|
107
|
-
def header_cell_class
|
108
|
-
self.class::HeaderCell
|
109
|
-
end
|
110
26
|
end
|
111
27
|
end
|
112
28
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Table
|
5
|
+
module Components
|
6
|
+
class DataColumn < Column
|
7
|
+
def initialize(*, **, &block)
|
8
|
+
super(*, **)
|
9
|
+
@block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
def header_cell
|
13
|
+
SortableHeaderCell.new(label, sort_params:)
|
14
|
+
end
|
15
|
+
|
16
|
+
def data_cell(wrapped_object)
|
17
|
+
if @block
|
18
|
+
@block.call(wrapped_object, key)
|
19
|
+
else
|
20
|
+
field = wrapped_object.field(key)
|
21
|
+
field.send(:"#{field.inferred_field_component}_tag")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def sort_params
|
28
|
+
options[:sort_params]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Table
|
5
|
+
module Components
|
6
|
+
class HeaderCell < Phlexi::Table::HTML
|
7
|
+
def initialize(value)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def view_template
|
12
|
+
div(class: themed(:header_cell_content_wrapper)) do
|
13
|
+
plain @value.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -7,9 +7,9 @@ module Phlexi
|
|
7
7
|
module Alignment
|
8
8
|
def alignment(alignment = nil)
|
9
9
|
if alignment.nil?
|
10
|
-
options[:
|
10
|
+
options[:align] = options.fetch(:align) { calculate_alignment }
|
11
11
|
else
|
12
|
-
options[:
|
12
|
+
options[:align] = alignment
|
13
13
|
self
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Table
|
5
|
+
module Components
|
6
|
+
class SelectionCell < Phlexi::Table::HTML
|
7
|
+
def initialize(value)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def view_template
|
12
|
+
input(type: :checkbox, value: @value, class: themed(:selection_checkbox))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Table
|
5
|
+
module Components
|
6
|
+
class SelectionColumn < Column
|
7
|
+
def header_cell
|
8
|
+
SelectionCell.new("all")
|
9
|
+
end
|
10
|
+
|
11
|
+
def data_cell(wrapped_object)
|
12
|
+
SelectionCell.new(wrapped_object.field(key).dom.value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def data_cell_attributes(wrapped_object)
|
16
|
+
{scope: :row}.merge(super)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Table
|
5
|
+
module Components
|
6
|
+
class SortableHeaderCell < HeaderCell
|
7
|
+
def initialize(value, sort_params:)
|
8
|
+
@value = value
|
9
|
+
@sort_params = sort_params
|
10
|
+
end
|
11
|
+
|
12
|
+
def view_template
|
13
|
+
div(class: themed(:header_cell_content_wrapper)) do
|
14
|
+
if !@sort_params
|
15
|
+
plain @value.to_s
|
16
|
+
next
|
17
|
+
end
|
18
|
+
|
19
|
+
a(class: themed(:header_cell_sort_wrapper), href: @sort_params[:url]) do
|
20
|
+
plain @value.to_s
|
21
|
+
|
22
|
+
sort_icon = themed(:sort_icon)
|
23
|
+
sort_icon_active = themed(:sort_icon_active)
|
24
|
+
|
25
|
+
span(class: themed(:header_cell_sort_indicator)) {
|
26
|
+
svg(
|
27
|
+
class: tokens(sort_icon, -> { @sort_params[:direction] != "DESC" } => sort_icon_active),
|
28
|
+
aria_hidden: "true",
|
29
|
+
xmlns: "http://www.w3.org/2000/svg",
|
30
|
+
fill: "none",
|
31
|
+
viewbox: "0 0 10 6"
|
32
|
+
) do |s|
|
33
|
+
s.path(
|
34
|
+
stroke: "currentColor",
|
35
|
+
stroke_linecap: "round",
|
36
|
+
stroke_linejoin: "round",
|
37
|
+
stroke_width: "2",
|
38
|
+
d: "M9 5 5 1 1 5"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
svg(
|
42
|
+
class: tokens(sort_icon, -> { @sort_params[:direction] != "ASC" } => sort_icon_active),
|
43
|
+
aria_hidden: "true",
|
44
|
+
xmlns: "http://www.w3.org/2000/svg",
|
45
|
+
fill: "none",
|
46
|
+
viewbox: "0 0 10 6"
|
47
|
+
) do |s|
|
48
|
+
s.path(
|
49
|
+
stroke: "currentColor",
|
50
|
+
stroke_linecap: "round",
|
51
|
+
stroke_linejoin: "round",
|
52
|
+
stroke_width: "2",
|
53
|
+
d: "M1 1 5 5 9 1"
|
54
|
+
)
|
55
|
+
end
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
next if @sort_params[:position].nil?
|
60
|
+
|
61
|
+
sort_index_clear_link = themed(:sort_index_clear_link)
|
62
|
+
sort_index_clear_link_text = themed(:sort_index_clear_link_text)
|
63
|
+
sort_index_clear_link_icon = themed(:sort_index_clear_link_icon)
|
64
|
+
|
65
|
+
a(href: @sort_params[:reset_url], title: "Clear sort", class: sort_index_clear_link) {
|
66
|
+
span(class: sort_index_clear_link_text) { (@sort_params[:position] + 1).to_s }
|
67
|
+
span(class: sort_index_clear_link_icon) { "✕" }
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/phlexi/table/html.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
require "phlex"
|
2
|
-
|
3
1
|
module Phlexi
|
4
2
|
module Table
|
5
3
|
class HTML < (defined?(::ApplicationComponent) ? ::ApplicationComponent : Phlex::HTML)
|
6
|
-
|
4
|
+
module Behaviour
|
5
|
+
protected
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
def themed(component)
|
8
|
+
Phlexi::Table::Theme.instance.resolve_theme(component)
|
9
|
+
end
|
10
10
|
end
|
11
|
+
|
12
|
+
include Behaviour
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/phlexi/table/theme.rb
CHANGED
@@ -1,28 +1,24 @@
|
|
1
1
|
module Phlexi
|
2
2
|
module Table
|
3
|
-
class Theme
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
}.freeze
|
23
|
-
|
24
|
-
def theme
|
25
|
-
DEFAULT_THEME
|
3
|
+
class Theme < Phlexi::Field::Theme
|
4
|
+
def self.theme
|
5
|
+
{
|
6
|
+
wrapper: nil,
|
7
|
+
base: nil,
|
8
|
+
caption: nil,
|
9
|
+
description: nil,
|
10
|
+
header: nil,
|
11
|
+
header_grouping_row: :header_row,
|
12
|
+
header_grouping_cell: :header_row_cell,
|
13
|
+
header_row: nil,
|
14
|
+
header_cell: nil,
|
15
|
+
body_row: nil,
|
16
|
+
body_cell: nil,
|
17
|
+
name_column: nil,
|
18
|
+
align_start: nil,
|
19
|
+
align_end: nil,
|
20
|
+
selection_checkbox: nil
|
21
|
+
}.freeze
|
26
22
|
end
|
27
23
|
end
|
28
24
|
end
|
data/lib/phlexi/table/version.rb
CHANGED
@@ -1,20 +1,27 @@
|
|
1
1
|
module Phlexi
|
2
2
|
module Table
|
3
3
|
class WrappedObject
|
4
|
-
attr_reader :unwrapped
|
4
|
+
attr_reader :unwrapped, :index
|
5
5
|
|
6
|
-
|
6
|
+
delegate :field, to: :as_display
|
7
|
+
|
8
|
+
def initialize(object, index:, display_class:)
|
7
9
|
@unwrapped = object
|
8
10
|
@index = index
|
11
|
+
@display_class = display_class
|
9
12
|
end
|
10
13
|
|
11
|
-
def
|
12
|
-
|
14
|
+
def identifier
|
15
|
+
@identifier ||= Phlexi::Field.object_primary_key(unwrapped) || (index + 1)
|
13
16
|
end
|
14
17
|
|
15
18
|
def value_of(key)
|
16
19
|
@unwrapped.try(key)
|
17
20
|
end
|
21
|
+
|
22
|
+
def as_display
|
23
|
+
@as_display ||= @display_class.new(unwrapped)
|
24
|
+
end
|
18
25
|
end
|
19
26
|
end
|
20
27
|
end
|
data/lib/phlexi/table.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "zeitwerk"
|
4
|
+
require "phlex"
|
5
|
+
require "phlexi-field"
|
6
|
+
require "phlexi-display"
|
4
7
|
require "active_support/core_ext/object/blank"
|
5
8
|
|
6
9
|
module Phlexi
|
@@ -17,8 +20,6 @@ module Phlexi
|
|
17
20
|
loader.setup
|
18
21
|
end
|
19
22
|
|
20
|
-
NIL_VALUE = :__i_phlexi_display_nil_value_i__
|
21
|
-
|
22
23
|
class Error < StandardError; end
|
23
24
|
end
|
24
25
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phlexi-table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: phlexi-display
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rake
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,14 +220,17 @@ files:
|
|
206
220
|
- lib/phlexi/table/components/concerns/displays_data.rb
|
207
221
|
- lib/phlexi/table/components/concerns/displays_header.rb
|
208
222
|
- lib/phlexi/table/components/concerns/groups_columns.rb
|
223
|
+
- lib/phlexi/table/components/data_column.rb
|
224
|
+
- lib/phlexi/table/components/header_cell.rb
|
209
225
|
- lib/phlexi/table/components/options/alignment.rb
|
210
|
-
- lib/phlexi/table/components/options/associations.rb
|
211
|
-
- lib/phlexi/table/components/options/attachments.rb
|
212
226
|
- lib/phlexi/table/components/options/labels.rb
|
213
|
-
- lib/phlexi/table/components/
|
227
|
+
- lib/phlexi/table/components/selection_cell.rb
|
228
|
+
- lib/phlexi/table/components/selection_column.rb
|
229
|
+
- lib/phlexi/table/components/sortable_header_cell.rb
|
230
|
+
- lib/phlexi/table/display_theme.rb
|
214
231
|
- lib/phlexi/table/html.rb
|
215
|
-
- lib/phlexi/table/
|
216
|
-
- lib/phlexi/table/
|
232
|
+
- lib/phlexi/table/options/captions.rb
|
233
|
+
- lib/phlexi/table/options/descriptions.rb
|
217
234
|
- lib/phlexi/table/theme.rb
|
218
235
|
- lib/phlexi/table/version.rb
|
219
236
|
- lib/phlexi/table/wrapped_object.rb
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Phlexi
|
4
|
-
module Table
|
5
|
-
module Components
|
6
|
-
module Options
|
7
|
-
module Associations
|
8
|
-
protected
|
9
|
-
|
10
|
-
def association_reflection
|
11
|
-
@association_reflection ||= find_association_reflection
|
12
|
-
end
|
13
|
-
|
14
|
-
def find_association_reflection
|
15
|
-
if sample.class.respond_to?(:reflect_on_association)
|
16
|
-
sample.class.reflect_on_association(key)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Phlexi
|
4
|
-
module Table
|
5
|
-
module Components
|
6
|
-
module Options
|
7
|
-
module Attachments
|
8
|
-
protected
|
9
|
-
|
10
|
-
def attachment_reflection
|
11
|
-
@attachment_reflection ||= find_attachment_reflection
|
12
|
-
end
|
13
|
-
|
14
|
-
def find_attachment_reflection
|
15
|
-
if sample.class.respond_to?(:reflect_on_attachment)
|
16
|
-
sample.class.reflect_on_attachment(key)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,140 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bigdecimal"
|
4
|
-
|
5
|
-
module Phlexi
|
6
|
-
module Table
|
7
|
-
module Components
|
8
|
-
module Options
|
9
|
-
module Types
|
10
|
-
def type(type = nil)
|
11
|
-
if type.nil?
|
12
|
-
options[:as] = options.fetch(:as) { inferred_db_type }
|
13
|
-
else
|
14
|
-
options[:as] = type
|
15
|
-
self
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def inferred_db_type
|
20
|
-
@inferred_db_type ||= infer_db_type
|
21
|
-
end
|
22
|
-
|
23
|
-
def inferred_display_component
|
24
|
-
@inferred_display_component ||= infer_display_component
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def infer_display_component
|
30
|
-
case type
|
31
|
-
when :string, :text
|
32
|
-
infer_string_display_type(key)
|
33
|
-
when :integer, :float, :decimal
|
34
|
-
:number
|
35
|
-
when :date, :datetime, :time
|
36
|
-
:date
|
37
|
-
when :boolean
|
38
|
-
:boolean
|
39
|
-
when :json, :jsonb, :hstore
|
40
|
-
:code
|
41
|
-
else
|
42
|
-
if association_reflection
|
43
|
-
:association
|
44
|
-
elsif attachment_reflection
|
45
|
-
:attachment
|
46
|
-
else
|
47
|
-
:text
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def infer_db_type
|
53
|
-
if sample.class.respond_to?(:columns_hash)
|
54
|
-
# ActiveRecord model
|
55
|
-
column = sample.class.columns_hash[key.to_s]
|
56
|
-
return column.type if column
|
57
|
-
end
|
58
|
-
|
59
|
-
if sample.class.respond_to?(:attribute_types)
|
60
|
-
# ActiveModel::Attributes
|
61
|
-
custom_type = sample.class.attribute_types[key.to_s]
|
62
|
-
return custom_type.type if custom_type&.type
|
63
|
-
end
|
64
|
-
|
65
|
-
# Check if sample responds to the key
|
66
|
-
if sample.respond_to?(key)
|
67
|
-
# Fallback to inferring type from the value
|
68
|
-
return infer_db_type_from_value(sample.send(key))
|
69
|
-
end
|
70
|
-
|
71
|
-
# Default to string if we can't determine the type
|
72
|
-
:string
|
73
|
-
end
|
74
|
-
|
75
|
-
def infer_db_type_from_value(value)
|
76
|
-
case value
|
77
|
-
when Integer
|
78
|
-
:integer
|
79
|
-
when Float
|
80
|
-
:float
|
81
|
-
when BigDecimal
|
82
|
-
:decimal
|
83
|
-
when TrueClass, FalseClass
|
84
|
-
:boolean
|
85
|
-
when Date
|
86
|
-
:date
|
87
|
-
when Time, DateTime
|
88
|
-
:datetime
|
89
|
-
when Hash
|
90
|
-
:json
|
91
|
-
else
|
92
|
-
:string
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def infer_string_display_type(key)
|
97
|
-
key = key.to_s.downcase
|
98
|
-
|
99
|
-
return :password if is_password_field?
|
100
|
-
|
101
|
-
custom_type = custom_string_display_type(key)
|
102
|
-
return custom_type if custom_type
|
103
|
-
|
104
|
-
:text
|
105
|
-
end
|
106
|
-
|
107
|
-
def custom_string_display_type(key)
|
108
|
-
custom_mappings = {
|
109
|
-
/url$|^link|^site/ => :url,
|
110
|
-
/^email/ => :email,
|
111
|
-
/phone|tel(ephone)?/ => :phone,
|
112
|
-
/^time/ => :time,
|
113
|
-
/^date/ => :date,
|
114
|
-
/^number|_count$|_amount$/ => :number,
|
115
|
-
/^color/ => :color
|
116
|
-
}
|
117
|
-
|
118
|
-
custom_mappings.each do |pattern, type|
|
119
|
-
return type if key.match?(pattern)
|
120
|
-
end
|
121
|
-
|
122
|
-
nil
|
123
|
-
end
|
124
|
-
|
125
|
-
def is_password_field?
|
126
|
-
key = self.key.to_s.downcase
|
127
|
-
|
128
|
-
exact_matches = ["password"]
|
129
|
-
prefixes = ["encrypted_"]
|
130
|
-
suffixes = ["_password", "_digest", "_hash"]
|
131
|
-
|
132
|
-
exact_matches.include?(key) ||
|
133
|
-
prefixes.any? { |prefix| key.start_with?(prefix) } ||
|
134
|
-
suffixes.any? { |suffix| key.end_with?(suffix) }
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|