tramway 4.1.0.1 → 4.1.1
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/README.md +77 -0
- data/app/components/tramway/form/tramway_select_component.rb +5 -3
- data/app/components/tramway/table/cell_component.rb +6 -6
- data/app/views/tramway/entities/show.html.haml +5 -3
- data/config/routes.rb +14 -4
- data/lib/tramway/base_decorator.rb +14 -0
- data/lib/tramway/configs/entities/page.rb +3 -0
- data/lib/tramway/configs/entity.rb +12 -0
- data/lib/tramway/helpers/views_helper.rb +2 -2
- data/lib/tramway/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 157c85b21871fba844d2da67a40213a63b7613a9110e5fd8e72b430eea52c1cb
|
|
4
|
+
data.tar.gz: 19448d06038afe21efe938b9abe4db4eca02bb37c79b63a6d3dabc303fbff362
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d0c54e34d0017715e2b4d0b0f351abebf0f02a3029cc4762bf40c07abd0198e8945b7b1bf3e474051263afddb9a65e518fa8febdae127b323e755c67c814049f
|
|
7
|
+
data.tar.gz: 8acef7a5b516284dfa3d44901cf059f8c9632aedc41bf2bbe1a93f8c7b840bb6988f424449e9fde2f53558e0f78a5dba2cc02b61d478669bdb05318d33d6102e
|
data/README.md
CHANGED
|
@@ -331,6 +331,18 @@ end
|
|
|
331
331
|
With this configuration in place, visiting the show page displays a two-column table where the left column contains the
|
|
332
332
|
localized attribute names and the right column renders their values.
|
|
333
333
|
|
|
334
|
+
Each entry in `show_attributes` can also be a Hash instead of a plain symbol, to pass options to the `tramway_cell`
|
|
335
|
+
rendering that attribute's value. The Hash requires an `attribute:` key (the method to display) and accepts an
|
|
336
|
+
`options:` key (a Hash forwarded as-is to `tramway_cell`, e.g. `truncate: false`):
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
class CampaignDecorator < Tramway::BaseDecorator
|
|
340
|
+
def show_attributes
|
|
341
|
+
[:name, { attribute: :status, options: { truncate: false } }, :starts_at]
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
```
|
|
345
|
+
|
|
334
346
|
**create page**
|
|
335
347
|
|
|
336
348
|
To render a create page for an entity, declare a `:create` action inside the `pages` array in
|
|
@@ -430,6 +442,62 @@ Tramway.configure do |config|
|
|
|
430
442
|
end
|
|
431
443
|
```
|
|
432
444
|
|
|
445
|
+
**custom page**
|
|
446
|
+
|
|
447
|
+
Besides the built-in `index`, `show`, `create`, `update`, and `destroy` pages, an entity can declare **Custom Pages**:
|
|
448
|
+
your own controller action and view, loaded through the same routing, layout, and navbar as the rest of the entity's
|
|
449
|
+
pages. Use a Custom Page for anything that doesn't fit the built-in CRUD actions — a report, a bulk tool, an
|
|
450
|
+
"impersonate" button's target page, and so on.
|
|
451
|
+
|
|
452
|
+
Declare it by adding a page whose `action` is not one of the built-in ones:
|
|
453
|
+
|
|
454
|
+
```ruby
|
|
455
|
+
Tramway.configure do |config|
|
|
456
|
+
config.entities = [
|
|
457
|
+
{
|
|
458
|
+
name: :user,
|
|
459
|
+
namespace: :admin,
|
|
460
|
+
pages: [
|
|
461
|
+
{ action: :index },
|
|
462
|
+
{
|
|
463
|
+
action: :impersonate,
|
|
464
|
+
member: true, # nests the route under /:id (default: false, an entity-level route)
|
|
465
|
+
via: :post, # HTTP method(s) the route accepts (default: :get); also accepts an array, e.g. %i[get post]
|
|
466
|
+
params: [:redirect_to] # extra required path segments, e.g. /admin/users/:id/impersonate/:redirect_to
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
}
|
|
470
|
+
]
|
|
471
|
+
end
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Then implement the action yourself, by Rails naming convention, in the controller Tramway would otherwise generate
|
|
475
|
+
for that namespace/entity (here, `Admin::UsersController`), inheriting from `Tramway::EntitiesController` so it gets
|
|
476
|
+
the same layout, navbar, and `entity`/`model_class` lookups as the built-in pages:
|
|
477
|
+
|
|
478
|
+
```ruby
|
|
479
|
+
# app/controllers/admin/users_controller.rb
|
|
480
|
+
module Admin
|
|
481
|
+
class UsersController < Tramway::EntitiesController
|
|
482
|
+
def impersonate
|
|
483
|
+
@user = model_class.find(params[:id])
|
|
484
|
+
# ...
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
And add its view at the conventional Rails path for that controller/action:
|
|
491
|
+
|
|
492
|
+
```haml
|
|
493
|
+
-# app/views/admin/users/impersonate.html.haml
|
|
494
|
+
= tramway_container do
|
|
495
|
+
%p Impersonating #{@user.email}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
See [docs/users/tramway_custom_pages.md](docs/users/tramway_custom_pages.md) for how the routing/controller/view
|
|
499
|
+
resolution works under the hood.
|
|
500
|
+
|
|
433
501
|
**route_helper**
|
|
434
502
|
|
|
435
503
|
To get routes Tramway generated just Tramway::Engine.
|
|
@@ -1319,6 +1387,15 @@ interactivity.
|
|
|
1319
1387
|
Cell content that does not fit its column is truncated with an ellipsis. This is a pure CSS behavior (`truncate` +
|
|
1320
1388
|
`min-w-0` on each cell) — no JavaScript is involved, and no configuration is needed to enable it.
|
|
1321
1389
|
|
|
1390
|
+
Pass `truncate: false` to `tramway_cell` to opt a specific cell out of this behavior and let its content wrap/overflow
|
|
1391
|
+
normally:
|
|
1392
|
+
|
|
1393
|
+
```erb
|
|
1394
|
+
<%= tramway_cell truncate: false do %>
|
|
1395
|
+
<%= user.bio %>
|
|
1396
|
+
<% end %>
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1322
1399
|
On narrow screens all columns always render, and the table scrolls horizontally within itself (the page never
|
|
1323
1400
|
stretches past the viewport width) so every column stays reachable. See
|
|
1324
1401
|
[docs/users/tramway_table.md](docs/users/tramway_table.md) for the full details of the mobile scroll behavior.
|
|
@@ -78,14 +78,16 @@ module Tramway
|
|
|
78
78
|
|
|
79
79
|
private
|
|
80
80
|
|
|
81
|
+
# Selections round-trip as a comma-joined string; normalize back to an array here so the
|
|
82
|
+
# stimulus controller's JSON.parse(data-value) doesn't crash on a saved value.
|
|
83
|
+
def value = Array(super).flat_map { |item| item.is_a?(String) ? item.split(',') : item }.map(&:to_s)
|
|
84
|
+
|
|
81
85
|
def tramway_select_size_class
|
|
82
86
|
classes = size_class(:tramway_select_input)
|
|
83
87
|
multiple ? classes.gsub(/\bh-/, 'min-h-') : classes
|
|
84
88
|
end
|
|
85
89
|
|
|
86
|
-
def action
|
|
87
|
-
'click->tramway-select#toggleDropdown'
|
|
88
|
-
end
|
|
90
|
+
def action = 'click->tramway-select#toggleDropdown'
|
|
89
91
|
|
|
90
92
|
def items
|
|
91
93
|
collection
|
|
@@ -5,17 +5,17 @@ module Tramway
|
|
|
5
5
|
# Component for rendering a cell in a table
|
|
6
6
|
class CellComponent < Tramway::BaseComponent
|
|
7
7
|
SIZE_CLASSES = {
|
|
8
|
-
small: 'div-table-cell
|
|
9
|
-
medium: 'div-table-cell
|
|
10
|
-
|
|
11
|
-
large: 'div-table-cell truncate min-w-0 bg-transparent px-6 py-6 text-lg font-medium ' \
|
|
12
|
-
'text-zinc-100'
|
|
8
|
+
small: 'div-table-cell min-w-0 bg-transparent px-4 py-2 text-sm font-medium text-zinc-100',
|
|
9
|
+
medium: 'div-table-cell min-w-0 bg-transparent px-6 py-4 text-base font-medium text-zinc-100',
|
|
10
|
+
large: 'div-table-cell min-w-0 bg-transparent px-6 py-6 text-lg font-medium text-zinc-100'
|
|
13
11
|
}.freeze
|
|
14
12
|
|
|
15
13
|
option :options, optional: true, default: -> { {} }
|
|
14
|
+
option :truncate, optional: true, default: -> { true }
|
|
16
15
|
|
|
17
16
|
def cell_classes
|
|
18
|
-
SIZE_CLASSES.fetch(tramway_table_size) { SIZE_CLASSES[:medium] }
|
|
17
|
+
base_classes = SIZE_CLASSES.fetch(tramway_table_size) { SIZE_CLASSES[:medium] }
|
|
18
|
+
truncate ? "#{base_classes} truncate" : base_classes
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -35,11 +35,13 @@
|
|
|
35
35
|
= association[:record].title
|
|
36
36
|
|
|
37
37
|
- @record.show_attributes.each do |attribute|
|
|
38
|
+
- attribute_name = @record.show_attribute_name(attribute)
|
|
39
|
+
- attribute_options = @record.show_attribute_options(attribute)
|
|
38
40
|
= tramway_row do
|
|
39
41
|
= tramway_cell do
|
|
40
|
-
= @model_class.human_attribute_name(
|
|
41
|
-
= tramway_cell do
|
|
42
|
-
= @record.public_send(
|
|
42
|
+
= @model_class.human_attribute_name(attribute_name)
|
|
43
|
+
= tramway_cell(**attribute_options) do
|
|
44
|
+
= @record.public_send(attribute_name)
|
|
43
45
|
|
|
44
46
|
- @associations.select { _1[:association_type] == :has_many }.each do |association|
|
|
45
47
|
.flex.flex-row.justify-between.mt-8.text-white
|
data/config/routes.rb
CHANGED
|
@@ -13,9 +13,10 @@ Tramway::Engine.routes.draw do
|
|
|
13
13
|
end => segments
|
|
14
14
|
|
|
15
15
|
resource_name = segments.pop
|
|
16
|
+
controller_path = (segments + [resource_name.pluralize]).join('/')
|
|
16
17
|
|
|
17
18
|
define_resource = proc do
|
|
18
|
-
entity.
|
|
19
|
+
entity.built_in_pages.reduce([]) do |acc, page|
|
|
19
20
|
case page.action
|
|
20
21
|
when 'index'
|
|
21
22
|
acc << :index
|
|
@@ -27,15 +28,24 @@ Tramway::Engine.routes.draw do
|
|
|
27
28
|
acc + %i[edit update]
|
|
28
29
|
when 'destroy'
|
|
29
30
|
acc << :destroy
|
|
30
|
-
else
|
|
31
|
-
acc
|
|
32
31
|
end
|
|
33
32
|
end => actions
|
|
34
33
|
|
|
35
34
|
resources resource_name.pluralize.to_sym,
|
|
36
35
|
only: actions.map(&:to_sym),
|
|
37
36
|
controller: '/tramway/entities',
|
|
38
|
-
defaults: { entity: }
|
|
37
|
+
defaults: { entity: } do
|
|
38
|
+
entity.custom_pages.each do |page|
|
|
39
|
+
path = ([page.action] + page.params.map { |param| ":#{param}" }).join('/')
|
|
40
|
+
to = "/#{controller_path}##{page.action}"
|
|
41
|
+
|
|
42
|
+
if page.member
|
|
43
|
+
member { match path, to:, via: page.via, defaults: { entity: } }
|
|
44
|
+
else
|
|
45
|
+
collection { match path, to:, via: page.via, defaults: { entity: } }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
39
49
|
end
|
|
40
50
|
|
|
41
51
|
if segments.empty?
|
|
@@ -92,6 +92,20 @@ module Tramway
|
|
|
92
92
|
[]
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
def show_attribute_name(attribute)
|
|
96
|
+
return attribute unless attribute.is_a?(Hash)
|
|
97
|
+
|
|
98
|
+
attribute.fetch(:attribute) do
|
|
99
|
+
raise ArgumentError, 'Each show_attributes Hash entry must include an :attribute key'
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def show_attribute_options(attribute)
|
|
104
|
+
return {} unless attribute.is_a?(Hash)
|
|
105
|
+
|
|
106
|
+
attribute.fetch(:options, {})
|
|
107
|
+
end
|
|
108
|
+
|
|
95
109
|
def show_associations
|
|
96
110
|
[]
|
|
97
111
|
end
|
|
@@ -11,6 +11,9 @@ module Tramway
|
|
|
11
11
|
attribute? :search, Types::Bool
|
|
12
12
|
attribute? :includes, Types::Array.default([].freeze)
|
|
13
13
|
attribute? :filters, Types::Array.default([].freeze)
|
|
14
|
+
attribute? :member, Types::Bool.default(false)
|
|
15
|
+
attribute? :via, (Types::Coercible::Symbol | Types::Array.of(Types::Coercible::Symbol)).default(:get)
|
|
16
|
+
attribute? :params, Types::Array.of(Types::Coercible::Symbol).default([].freeze)
|
|
14
17
|
end
|
|
15
18
|
end
|
|
16
19
|
end
|
|
@@ -16,6 +16,10 @@ module Tramway
|
|
|
16
16
|
ACTIONS = %i[index show new create edit update destroy].freeze
|
|
17
17
|
RouteStruct = Struct.new(*ACTIONS)
|
|
18
18
|
|
|
19
|
+
# Page#action values handled by Tramway's built-in CRUD controller and routing.
|
|
20
|
+
# Any other page is a Custom Page, routed to a host app controller instead.
|
|
21
|
+
BUILT_IN_PAGE_ACTIONS = %w[index show create update destroy].freeze
|
|
22
|
+
|
|
19
23
|
# HumanName Struct contains human names forms for the entity
|
|
20
24
|
HumanNameStruct = Struct.new(:single, :plural)
|
|
21
25
|
|
|
@@ -39,6 +43,14 @@ module Tramway
|
|
|
39
43
|
pages.find { |page| page.action == name.to_s }
|
|
40
44
|
end
|
|
41
45
|
|
|
46
|
+
def built_in_pages
|
|
47
|
+
pages.select { |page| BUILT_IN_PAGE_ACTIONS.include?(page.action) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def custom_pages
|
|
51
|
+
pages.reject { |page| BUILT_IN_PAGE_ACTIONS.include?(page.action) }
|
|
52
|
+
end
|
|
53
|
+
|
|
42
54
|
def show_helper_method
|
|
43
55
|
build_helper_method(name, route:, namespace:, plural: false)
|
|
44
56
|
end
|
|
@@ -50,8 +50,8 @@ module Tramway
|
|
|
50
50
|
&
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def tramway_cell(**options, &)
|
|
54
|
-
component 'tramway/table/cell', options:, &
|
|
53
|
+
def tramway_cell(truncate: true, **options, &)
|
|
54
|
+
component 'tramway/table/cell', truncate:, options:, &
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def tramway_button(path: nil, text: nil, method: :get, form_options: {}, **options, &)
|
data/lib/tramway/version.rb
CHANGED