phlexi-table 0.0.1 → 0.0.2

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