phlexi-table 0.0.1 → 0.0.2

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/phlexi/table/base.rb +343 -74
  3. data/lib/phlexi/table/components/actions_column.rb +49 -0
  4. data/lib/phlexi/table/components/base.rb +23 -18
  5. data/lib/phlexi/table/components/column.rb +24 -0
  6. data/lib/phlexi/table/components/column_group.rb +23 -0
  7. data/lib/phlexi/table/components/concerns/displays_data.rb +66 -0
  8. data/lib/phlexi/table/components/concerns/displays_header.rb +114 -0
  9. data/lib/phlexi/table/components/concerns/groups_columns.rb +36 -0
  10. data/lib/phlexi/table/components/options/alignment.rb +31 -0
  11. data/lib/phlexi/table/components/options/associations.rb +23 -0
  12. data/lib/phlexi/table/components/options/attachments.rb +23 -0
  13. data/lib/phlexi/table/components/options/labels.rb +30 -0
  14. data/lib/phlexi/table/components/options/types.rb +140 -0
  15. data/lib/phlexi/table/html.rb +13 -0
  16. data/lib/phlexi/table/table_options/captions.rb +22 -0
  17. data/lib/phlexi/table/{field_options/description.rb → table_options/descriptions.rb} +5 -5
  18. data/lib/phlexi/table/theme.rb +29 -0
  19. data/lib/phlexi/table/version.rb +1 -1
  20. data/lib/phlexi/table/wrapped_object.rb +20 -0
  21. data/lib/phlexi/table.rb +3 -5
  22. metadata +32 -25
  23. data/lib/phlexi/table/components/concerns/displays_value.rb +0 -54
  24. data/lib/phlexi/table/components/date_time.rb +0 -49
  25. data/lib/phlexi/table/components/description.rb +0 -21
  26. data/lib/phlexi/table/components/hint.rb +0 -21
  27. data/lib/phlexi/table/components/label.rb +0 -15
  28. data/lib/phlexi/table/components/number.rb +0 -37
  29. data/lib/phlexi/table/components/placeholder.rb +0 -15
  30. data/lib/phlexi/table/components/string.rb +0 -17
  31. data/lib/phlexi/table/components/wrapper.rb +0 -17
  32. data/lib/phlexi/table/field_options/associations.rb +0 -21
  33. data/lib/phlexi/table/field_options/attachments.rb +0 -21
  34. data/lib/phlexi/table/field_options/hints.rb +0 -22
  35. data/lib/phlexi/table/field_options/inferred_types.rb +0 -129
  36. data/lib/phlexi/table/field_options/labels.rb +0 -28
  37. data/lib/phlexi/table/field_options/placeholders.rb +0 -18
  38. data/lib/phlexi/table/field_options/themes.rb +0 -132
  39. data/lib/phlexi/table/structure/dom.rb +0 -42
  40. data/lib/phlexi/table/structure/field_builder.rb +0 -158
  41. data/lib/phlexi/table/structure/field_collection.rb +0 -39
  42. data/lib/phlexi/table/structure/namespace.rb +0 -123
  43. data/lib/phlexi/table/structure/namespace_collection.rb +0 -40
  44. data/lib/phlexi/table/structure/node.rb +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e05e322aef835f61e4d9c75e678c54ed3abd96675b7eaedb7db6b8138f826450
4
- data.tar.gz: 3d2e8b90865ab5e8e5b5b405046277a51177508187f3fde2d708612bed01dc6f
3
+ metadata.gz: a2fc89d8ce811c592ca3a145af7ce3c91171d767404ec497a117a609e26c2f6a
4
+ data.tar.gz: 72d3289f57300d9f05a717cc9745d223971c0c143a9245e6e8a54c0ed5b6303c
5
5
  SHA512:
6
- metadata.gz: e9e99ffd0dff1b5e92cdc779c8cd3fd5dee46e20094916e50eeec337c1af2b88fd0b9fa637dd56f54ad3a12381e0d2b2434747d536a4bb5fff928d08ccd6dd1f
7
- data.tar.gz: 4f97e0de92ea6e09b17d7e09c6d242ee968638c6d4766991515aa54d3e738a51219190ca4d1fd944205b06b627eb9c3a2b0e7125adf5cb22eda839f14a5c7668
6
+ metadata.gz: 5aab0ea7be94c1f55a554b10b21710d2e4c6dd98ad0f9e1e1671660a03327e569163acaad34ae6ba1f57f7f5f0759ff58d73e553d366f64d939e4ecea9b53bed
7
+ data.tar.gz: 8132fba9adc5135022108759ff28ba5cd568ee818e307ce5fc0f36c11f19715b170af523fb4c90856ee718f2c29415fba8b7a281dc4125e365042f9ce54972fa
@@ -1,118 +1,387 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
- require "active_support/core_ext/string/inflections"
3
+ require "phlex"
4
+ # require "phlexi-display"
5
5
 
6
6
  module Phlexi
7
7
  module Table
8
- # A display component for rendering flexible and customizable data views.
8
+ # Base class for creating customizable table components
9
9
  #
10
10
  # @example Basic usage
11
- # Phlexi::Table.new(user) do |d|
12
- # render d.field(:name).text
13
- # render d.field(:email).text
11
+ # class UsersTable < Phlexi::Table::Base
12
+ # def table_template
13
+ # table_caption "Caption"
14
+ # table_description "Description"
15
+ # selection_column :id
16
+ # column :name
17
+ # column :a_sorted_column, sort_params: {url: "url", reset_url: "reset_url", position: "multisort position", direction: "ASC|DESC"}
18
+ # column_group :a_grouped_column do |g|
19
+ # g.column :sub_col1
20
+ # g.column :sub_col2
21
+ # end
22
+ # actions do |user|
23
+ # link_to "Edit", edit_user_path(user)
24
+ # end
25
+ # end
14
26
  # end
15
27
  #
16
- # @attr_reader [Symbol] key The display's key, derived from the record or explicitly set
17
- # @attr_reader [ActiveModel::Model, nil] object The display's associated object
18
- class Base < COMPONENT_BASE
19
- class Namespace < Structure::Namespace; end
20
-
21
- class FieldBuilder < Structure::FieldBuilder; end
22
-
23
- attr_reader :key, :object
24
-
25
- delegate :field, :nest_one, :nest_many, to: :@namespace
26
-
27
- # Initializes a new Table instance.
28
- #
29
- # @param record [ActiveModel::Model, Symbol, String] The display's associated record or key
30
- # @param attributes [Hash] Additional HTML attributes for the display container
31
- # @param options [Hash] Additional options for display configuration
32
- # @option options [String] :class CSS classes for the display
33
- # @option options [Class] :namespace_klass Custom namespace class
34
- # @option options [Class] :builder_klass Custom field builder class
35
- def initialize(record, attributes: {}, **options)
36
- @display_class = options.delete(:class)
37
- @attributes = attributes
38
- @namespace_klass = options.delete(:namespace_klass) || default_namespace_klass
39
- @builder_klass = options.delete(:builder_klass) || default_builder_klass
28
+ # render UsersTable.new(User.all)
29
+ #
30
+ # @attr_reader [Enumerable] collection The collection of items to display in the table
31
+ # @attr_reader [Hash] columns The columns defined for the table
32
+ class Base < Phlexi::Table::HTML
33
+ include Phlex::DeferredRender
34
+ include Phlexi::Table::TableOptions::Captions
35
+ include Phlexi::Table::TableOptions::Descriptions
36
+
37
+ class Column < Phlexi::Table::Components::Column; end
38
+
39
+ class ColumnGroup < Phlexi::Table::Components::ColumnGroup; end
40
+
41
+ class ActionsColumn < Phlexi::Table::Components::ActionsColumn; end
42
+
43
+ attr_reader :key, :collection, :columns, :options
44
+
45
+ # Initialize a new table component
46
+ #
47
+ # @param collection [Enumerable] The collection of items to display
48
+ # @param options [Hash] Additional options for customizing the table
49
+ # @option options [String] :id The ID attribute for the table
50
+ # @option options [String] :class The CSS class(es) for the table
51
+ # @raise [ArgumentError] If the collection is empty
52
+ def initialize(collection, **options)
53
+ raise ArgumentError, "Collection cannot be empty" if collection.empty?
54
+
55
+ @collection = collection
56
+ @columns = {}
40
57
  @options = options
58
+ initialize_key
59
+ end
60
+
61
+ def view_template
62
+ render_table
63
+ end
64
+
65
+ def table_template
66
+ end
67
+
68
+ def column(key, **options, &)
69
+ if options[:as] == :selection
70
+ selection_column(key, self, **options, &)
71
+ else
72
+
73
+ add_column(column_class.new(key, self, **options, &))
74
+ end
75
+ end
76
+
77
+ def selection_column(key, **, &)
78
+ raise "Selection column already added" if @has_selection_column
41
79
 
42
- initialize_object_and_key(record)
43
- initialize_namespace
80
+ @has_selection_column = true
81
+ add_column(column_class.new(key, self, **, as: :selection, &))
44
82
  end
45
83
 
46
- # Renders the display template.
84
+ def column_group(key, **, &)
85
+ @has_grouped_columns = true
86
+ add_column(column_group_class.new(key, self, **, &))
87
+ end
88
+
89
+ def actions(**, &)
90
+ raise "Action column already added" if @has_action_column
91
+
92
+ @has_action_column = true
93
+ add_column(actions_column_class.new(:phlexi_table_actions, self, label: "Actions", **, &))
94
+ end
95
+
96
+ def sample
97
+ collection[0]
98
+ end
99
+
100
+ def dom_id
101
+ key
102
+ end
103
+
104
+ def column_class
105
+ self.class::Column
106
+ end
107
+
108
+ def column_group_class
109
+ self.class::ColumnGroup
110
+ end
111
+
112
+ def actions_column_class
113
+ self.class::ActionsColumn
114
+ end
115
+
116
+ protected
117
+
118
+ def before_template
119
+ super
120
+ table_template
121
+ end
122
+
123
+ # Render the complete table structure
47
124
  #
48
125
  # @return [void]
49
- def view_template
50
- display_template
126
+ def render_table
127
+ div(**table_wrapper_attributes) {
128
+ table(**table_attributes) do
129
+ render_table_caption
130
+ render_table_header
131
+ render_table_body
132
+ render_table_footer
133
+ end
134
+ }
51
135
  end
52
136
 
53
- # Executes the display's content block.
54
- # Override this in subclasses to define a static display.
137
+ # Render the table caption
55
138
  #
56
139
  # @return [void]
57
- def display_template
58
- instance_exec(&@_content_block) if @_content_block
140
+ def render_table_caption
141
+ return unless has_table_caption? || has_table_description?
142
+
143
+ caption(**table_caption_attributes) {
144
+ plain table_caption if has_table_caption?
145
+ if has_table_description?
146
+ p(**table_description_attributes) {
147
+ table_description
148
+ }
149
+ end
150
+ }
59
151
  end
60
152
 
61
- protected
153
+ # Render the table header
154
+ #
155
+ # @return [void]
156
+ def render_table_header
157
+ thead(**table_header_attributes) do
158
+ if @has_grouped_columns
159
+ tr(**table_header_grouping_row_attributes) do
160
+ blanks = 0
161
+ columns.each_value do |column|
162
+ if column.is_a?(Phlexi::Table::Components::Concerns::GroupsColumns)
163
+ th(**table_header_grouping_cell_attributes(blanks)) {} if blanks > 0
164
+ th(id: column.dom_id, **table_header_grouping_cell_attributes(column.colspan)) { column.label }
165
+ blanks = 0
166
+ else
167
+ blanks += 1
168
+ end
169
+ end
170
+ th(**table_header_grouping_cell_attributes(blanks)) { whitespace } if blanks > 0
171
+ end
172
+ end
62
173
 
63
- attr_reader :options, :attributes, :namespace_klass, :builder_klass
174
+ tr(**table_header_row_attributes) do
175
+ columns.each_value do |column|
176
+ if column.is_a?(Phlexi::Table::Components::Concerns::GroupsColumns)
177
+ column.columns.each_value do |column|
178
+ th(**table_header_cell_attributes(column)) { render column.header_cell }
179
+ end
180
+ else
181
+ th(**table_header_cell_attributes(column)) { render column.header_cell }
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
64
187
 
65
- # Initializes the object and key based on the given record.
188
+ # Render the table body
66
189
  #
67
- # @param record [ActiveModel::Model, Symbol, String] The display's associated record or key
68
190
  # @return [void]
69
- def initialize_object_and_key(record)
70
- @key = options.delete(:key) || options.delete(:as)
191
+ def render_table_body
192
+ tbody(**table_body_attributes) do
193
+ collection.each_with_index do |item, index|
194
+ object = WrappedObject.new(item, index:)
195
+ render_table_body_row(object)
196
+ end
197
+ end
198
+ end
71
199
 
72
- case record
73
- when String, Symbol
74
- @object = nil
75
- @key = record
76
- else
77
- @object = record
78
- @key = if @key.nil? && object.respond_to?(:model_name) && object.model_name.respond_to?(:param_key) && object.model_name.param_key.present?
79
- object.model_name.param_key
80
- else
81
- :object
200
+ # Render a table body row
201
+ #
202
+ # @param item [Object] The current item from the collection
203
+ # @return [void]
204
+ def render_table_body_row(object)
205
+ tr(**table_body_row_attributes(object)) do
206
+ columns.each_value do |column|
207
+ if column.is_a?(Phlexi::Table::Components::Concerns::GroupsColumns)
208
+ column.columns.each_value do |column|
209
+ td(**table_data_cell_attributes(object, column)) { render column.data_cell(object) }
210
+ end
211
+ elsif column.is_a?(actions_column_class)
212
+ td(**table_data_cell_attributes(object, column)) { column.render_actions(object.unwrapped) }
213
+ else
214
+ td(**table_data_cell_attributes(object, column)) { render column.data_cell(object) }
215
+ end
82
216
  end
83
217
  end
84
- @key = @key.to_sym
85
218
  end
86
219
 
87
- # Initializes the namespace for the display.
220
+ # Render the table footer
88
221
  #
89
222
  # @return [void]
90
- def initialize_namespace
91
- @namespace = namespace_klass.root(key, object: object, builder_klass: builder_klass)
223
+ def render_table_footer
224
+ # tfoot(**table_footer_attributes) do
225
+ # # Implement footer content if needed
226
+ # end
227
+ end
228
+
229
+ private
230
+
231
+ def table_wrapper_attributes
232
+ {
233
+ id: @options[:id] || "#{dom_id}_table_wrapper",
234
+ class: tokens("phlexi_table_wrapper", themed(:wrapper))
235
+ }
92
236
  end
93
- # Retrieves the display's CSS classes.
237
+
238
+ # Get the attributes for the table element
94
239
  #
95
- # @return [String] The display's CSS classes
96
- attr_reader :display_class
240
+ # @return [Hash] The table attributes
241
+ def table_attributes
242
+ {
243
+ id: @options[:id] || "#{dom_id}_table",
244
+ class: tokens("phlexi_table", themed(:base), @options[:class])
245
+ }
246
+ end
97
247
 
98
- # Generates the display attributes hash.
248
+ # Get the attributes for the table caption element
99
249
  #
100
- # @return [Hash] The display attributes
101
- def display_attributes
102
- mix({
103
- id: @namespace.dom_id,
104
- class: display_class
105
- }, attributes)
250
+ # @return [Hash] The caption attributes
251
+ def table_caption_attributes
252
+ {
253
+ id: "#{dom_id}_table_caption",
254
+ class: tokens("phlexi_table_caption", themed(:caption))
255
+ }
106
256
  end
107
257
 
108
- private
258
+ # Get the attributes for the table description
259
+ #
260
+ # @return [Hash] The description attributes
261
+ def table_description_attributes
262
+ {
263
+ id: "#{dom_id}_table_description",
264
+ class: tokens("phlexi_table_description", themed(:description))
265
+ }
266
+ end
267
+
268
+ # Get the attributes for the table header element
269
+ #
270
+ # @return [Hash] The header attributes
271
+ def table_header_attributes
272
+ {
273
+ id: "#{dom_id}_table_header",
274
+ class: tokens("phlexi_table_header", themed(:header))
275
+ }
276
+ end
277
+
278
+ def table_header_grouping_row_attributes
279
+ {
280
+ id: "#{dom_id}_table_header_grouping_row",
281
+ class: tokens("phlexi_table_header_grouping_row", themed(:header_grouping_row))
282
+ }
283
+ end
284
+
285
+ def table_header_grouping_cell_attributes(colspan)
286
+ {
287
+ colspan:,
288
+ class: tokens("phlexi_table_header_grouping_cell", themed(:header_grouping_cell))
289
+ }
290
+ end
291
+
292
+ # Get the attributes for the table header row element
293
+ #
294
+ # @return [Hash] The header row attributes
295
+ def table_header_row_attributes
296
+ {
297
+ id: "#{dom_id}_table_header_row",
298
+ class: tokens("phlexi_table_header_row", themed(:header_row))
299
+ }
300
+ end
109
301
 
110
- def default_namespace_klass
111
- self.class::Namespace
302
+ # Get the attributes for a table header cell element
303
+ #
304
+ # @param column [Columns::Base] The column object
305
+ # @return [Hash] The header cell attributes
306
+ def table_header_cell_attributes(column)
307
+ mix(
308
+ {
309
+ scope: "col",
310
+ class: tokens("phlexi_table_header_cell", themed(:header_cell))
311
+ },
312
+ column.header_cell_attributes
313
+ )
112
314
  end
113
315
 
114
- def default_builder_klass
115
- self.class::FieldBuilder
316
+ # Get the attributes for the table body element
317
+ #
318
+ # @return [Hash] The body attributes
319
+ def table_body_attributes
320
+ {
321
+ id: "#{dom_id}_table_body",
322
+ class: tokens("phlexi_table_body", themed(:body))
323
+ }
324
+ end
325
+
326
+ # Get the attributes for a table body row element
327
+ #
328
+ # @param item [Object] The current item from the collection
329
+ # @return [Hash] The body row attributes
330
+ def table_body_row_attributes(item)
331
+ {
332
+ id: "#{dom_id}_table_row_#{item.id}",
333
+ class: tokens("phlexi_table_body_row", themed(:body_row))
334
+ }
335
+ end
336
+
337
+ # Get the attributes for a table body cell element
338
+ #
339
+ # @param item [Object] The current item from the collection
340
+ # @param column [Columns::Base] The column object
341
+ # @return [Hash] The body cell attributes
342
+ def table_data_cell_attributes(item, column)
343
+ mix(
344
+ {
345
+ id: "#{dom_id}_table_row_#{item.id}_#{column.key}",
346
+ class: tokens("phlexi_table_data_cell", themed(:body_cell))
347
+ },
348
+ column.data_cell_attributes(item)
349
+ )
350
+ end
351
+
352
+ # Get the attributes for the table footer element
353
+ #
354
+ # @return [Hash] The footer attributes
355
+ def table_footer_attributes
356
+ {
357
+ id: "#{dom_id}_table_footer",
358
+ class: tokens("phlexi_table_footer", themed(:footer))
359
+ }
360
+ end
361
+
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
+ def initialize_key
374
+ # always pop these keys
375
+ # add support for `as` to make it more rails friendly
376
+ @key = options.delete(:key) || options.delete(:as)
377
+ if @key.nil?
378
+ @key = if sample.respond_to?(:model_name) && sample.model_name.respond_to?(:param_key) && sample.model_name.param_key.present?
379
+ sample.model_name.plural.underscore
380
+ else
381
+ sample.class.name.demodulize.pluralize.underscore
382
+ end
383
+ end
384
+ @key = @key.to_sym
116
385
  end
117
386
  end
118
387
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "phlex"
4
+
5
+ module Phlexi
6
+ module Table
7
+ module Components
8
+ class ActionsColumn < Base
9
+ include Phlexi::Table::Components::Concerns::DisplaysHeader
10
+
11
+ def initialize(*, **, &block)
12
+ raise ArgumentError, "block is required" unless block.present?
13
+
14
+ super(*, **)
15
+ @block = block
16
+ end
17
+
18
+ def label(label = nil)
19
+ if label.nil?
20
+ options[:label]
21
+ else
22
+ options[:label] = label
23
+ self
24
+ end
25
+ end
26
+
27
+ def render_actions(object)
28
+ @block.call(object)
29
+ end
30
+
31
+ def dom_id
32
+ "#{super}_actions_cell"
33
+ end
34
+
35
+ def data_cell_attributes(object)
36
+ attributes = @attributes.dup
37
+ attributes[:class] = tokens(
38
+ themed(:actions_row_cell)
39
+ )
40
+ attributes
41
+ end
42
+
43
+ def type = "actions"
44
+
45
+ def alignment = nil
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,36 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "phlex"
4
+
3
5
  module Phlexi
4
6
  module Table
5
7
  module Components
6
- class Base < COMPONENT_BASE
7
- attr_reader :field, :attributes
8
+ class Base
9
+ include Phlex::Helpers
10
+
11
+ attr_reader :key, :parent, :options
12
+
13
+ delegate :sample, to: :parent
8
14
 
9
- def initialize(field, **attributes)
10
- @field = field
11
- @attributes = attributes
15
+ def initialize(key, parent, **options)
16
+ @key = key
17
+ @parent = parent
18
+ @options = options
12
19
 
13
20
  build_attributes
14
- append_attribute_classes
15
21
  end
16
22
 
17
- protected
18
-
19
- def build_attributes
20
- attributes.fetch(:id) { attributes[:id] = "#{field.dom.id}_#{component_name}" }
23
+ def dom_id
24
+ "#{parent.dom_id}_#{key}"
21
25
  end
22
26
 
23
- def append_attribute_classes
24
- return if attributes[:class] == false
27
+ private
25
28
 
26
- attributes[:class] = tokens(
27
- component_name,
28
- attributes[:class]
29
- )
29
+ def build_attributes
30
+ @attributes = {
31
+ class: tokens(
32
+ type
33
+ )
34
+ }
30
35
  end
31
36
 
32
- def component_name
33
- @component_name ||= self.class.name.demodulize.underscore
37
+ def themed(component)
38
+ Phlexi::Table::Theme.instance.resolve_theme(component)
34
39
  end
35
40
  end
36
41
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "phlex"
4
+
5
+ module Phlexi
6
+ module Table
7
+ module Components
8
+ class Column < Base
9
+ include Phlexi::Table::Components::Options::Types
10
+ include Phlexi::Table::Components::Options::Labels
11
+ include Phlexi::Table::Components::Options::Alignment
12
+ include Phlexi::Table::Components::Options::Attachments
13
+ include Phlexi::Table::Components::Options::Associations
14
+ include Phlexi::Table::Components::Concerns::DisplaysHeader
15
+ include Phlexi::Table::Components::Concerns::DisplaysData
16
+
17
+ def initialize(*, **, &block)
18
+ super(*, **)
19
+ @block = block
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module Phlexi
2
+ module Table
3
+ module Components
4
+ class ColumnGroup < Base
5
+ include Phlexi::Table::Components::Options::Labels
6
+ include Phlexi::Table::Components::Concerns::DisplaysHeader
7
+ include Phlexi::Table::Components::Concerns::GroupsColumns
8
+
9
+ def colspan
10
+ @columns.size
11
+ end
12
+
13
+ def dom_id
14
+ "#{super}_header_grouping_cell"
15
+ end
16
+
17
+ def type = "column_group"
18
+
19
+ def alignment = nil
20
+ end
21
+ end
22
+ end
23
+ end