tramway 4.1.0.1 → 4.1.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.
- checksums.yaml +4 -4
- data/README.md +104 -1
- 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 +13 -0
- data/lib/tramway/helpers/views_helper.rb +2 -2
- data/lib/tramway/navbar.rb +2 -1
- 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: aba7799f773e66098f92d1471963e6fe5da4079b095264c249dc4545a1a84a00
|
|
4
|
+
data.tar.gz: 98866e7d39aa6d9a31eb9b750949d69ccd16cd2d2f8cb2421f9eb9b96b450046
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27c456947b97709869bf23bab9eb3f72b4f30799766755fd53c58dad2ec070f0df73b574c16cea5983d3f560983ee9adb0194d84535553ba89d1748a90490005
|
|
7
|
+
data.tar.gz: b3b705af11f591965764edac7608d8af72e156d1a78d029a887e96b20b30945818df89f4ea947a0f6d945f06f90bf6db702b1a8951597c5cadb9f196c45e6227
|
data/README.md
CHANGED
|
@@ -177,6 +177,30 @@ Tramway.configure do |config|
|
|
|
177
177
|
end
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
+
**icon**
|
|
181
|
+
|
|
182
|
+
Set `icon` on an entity to render a Font Awesome class string before its auto-generated link in the Tramway Navbar
|
|
183
|
+
(the entity-based items rendered when `tramway_navbar` is called with `with_entities: true`, the default — see
|
|
184
|
+
[Icons on navbar items](#icons-on-navbar-items)):
|
|
185
|
+
|
|
186
|
+
*config/initializers/tramway.rb*
|
|
187
|
+
```ruby
|
|
188
|
+
Tramway.configure do |config|
|
|
189
|
+
config.entities = [
|
|
190
|
+
{
|
|
191
|
+
name: :campaign,
|
|
192
|
+
namespace: :admin,
|
|
193
|
+
icon: 'fa-solid fa-bullhorn',
|
|
194
|
+
pages: [
|
|
195
|
+
{ action: :index }
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Entities without an `icon` keep rendering as plain text links.
|
|
203
|
+
|
|
180
204
|
**scope**
|
|
181
205
|
|
|
182
206
|
By default, Tramway lists all records for an entity on the index page. You can narrow the records displayed by providing a
|
|
@@ -331,6 +355,18 @@ end
|
|
|
331
355
|
With this configuration in place, visiting the show page displays a two-column table where the left column contains the
|
|
332
356
|
localized attribute names and the right column renders their values.
|
|
333
357
|
|
|
358
|
+
Each entry in `show_attributes` can also be a Hash instead of a plain symbol, to pass options to the `tramway_cell`
|
|
359
|
+
rendering that attribute's value. The Hash requires an `attribute:` key (the method to display) and accepts an
|
|
360
|
+
`options:` key (a Hash forwarded as-is to `tramway_cell`, e.g. `truncate: false`):
|
|
361
|
+
|
|
362
|
+
```ruby
|
|
363
|
+
class CampaignDecorator < Tramway::BaseDecorator
|
|
364
|
+
def show_attributes
|
|
365
|
+
[:name, { attribute: :status, options: { truncate: false } }, :starts_at]
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
```
|
|
369
|
+
|
|
334
370
|
**create page**
|
|
335
371
|
|
|
336
372
|
To render a create page for an entity, declare a `:create` action inside the `pages` array in
|
|
@@ -430,6 +466,62 @@ Tramway.configure do |config|
|
|
|
430
466
|
end
|
|
431
467
|
```
|
|
432
468
|
|
|
469
|
+
**custom page**
|
|
470
|
+
|
|
471
|
+
Besides the built-in `index`, `show`, `create`, `update`, and `destroy` pages, an entity can declare **Custom Pages**:
|
|
472
|
+
your own controller action and view, loaded through the same routing, layout, and navbar as the rest of the entity's
|
|
473
|
+
pages. Use a Custom Page for anything that doesn't fit the built-in CRUD actions — a report, a bulk tool, an
|
|
474
|
+
"impersonate" button's target page, and so on.
|
|
475
|
+
|
|
476
|
+
Declare it by adding a page whose `action` is not one of the built-in ones:
|
|
477
|
+
|
|
478
|
+
```ruby
|
|
479
|
+
Tramway.configure do |config|
|
|
480
|
+
config.entities = [
|
|
481
|
+
{
|
|
482
|
+
name: :user,
|
|
483
|
+
namespace: :admin,
|
|
484
|
+
pages: [
|
|
485
|
+
{ action: :index },
|
|
486
|
+
{
|
|
487
|
+
action: :impersonate,
|
|
488
|
+
member: true, # nests the route under /:id (default: false, an entity-level route)
|
|
489
|
+
via: :post, # HTTP method(s) the route accepts (default: :get); also accepts an array, e.g. %i[get post]
|
|
490
|
+
params: [:redirect_to] # extra required path segments, e.g. /admin/users/:id/impersonate/:redirect_to
|
|
491
|
+
}
|
|
492
|
+
]
|
|
493
|
+
}
|
|
494
|
+
]
|
|
495
|
+
end
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Then implement the action yourself, by Rails naming convention, in the controller Tramway would otherwise generate
|
|
499
|
+
for that namespace/entity (here, `Admin::UsersController`), inheriting from `Tramway::EntitiesController` so it gets
|
|
500
|
+
the same layout, navbar, and `entity`/`model_class` lookups as the built-in pages:
|
|
501
|
+
|
|
502
|
+
```ruby
|
|
503
|
+
# app/controllers/admin/users_controller.rb
|
|
504
|
+
module Admin
|
|
505
|
+
class UsersController < Tramway::EntitiesController
|
|
506
|
+
def impersonate
|
|
507
|
+
@user = model_class.find(params[:id])
|
|
508
|
+
# ...
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
And add its view at the conventional Rails path for that controller/action:
|
|
515
|
+
|
|
516
|
+
```haml
|
|
517
|
+
-# app/views/admin/users/impersonate.html.haml
|
|
518
|
+
= tramway_container do
|
|
519
|
+
%p Impersonating #{@user.email}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
See [docs/users/tramway_custom_pages.md](docs/users/tramway_custom_pages.md) for how the routing/controller/view
|
|
523
|
+
resolution works under the hood.
|
|
524
|
+
|
|
433
525
|
**route_helper**
|
|
434
526
|
|
|
435
527
|
To get routes Tramway generated just Tramway::Engine.
|
|
@@ -990,7 +1082,9 @@ will render [this](https://play.tailwindcss.com/UZPTCudFw5)
|
|
|
990
1082
|
|
|
991
1083
|
#### Icons on navbar items
|
|
992
1084
|
|
|
993
|
-
`nav.item` accepts an optional `icon:` option with a Font Awesome class string, rendered before the item's label
|
|
1085
|
+
`nav.item` accepts an optional `icon:` option with a Font Awesome class string, rendered before the item's label.
|
|
1086
|
+
This also applies to the auto-generated entity links described in [Define entities with options](#define-entities-with-options) —
|
|
1087
|
+
set `icon` on an entity in `config/initializers/tramway.rb` to add an icon to its link without writing a custom `nav.item` call.
|
|
994
1088
|
|
|
995
1089
|
```ruby
|
|
996
1090
|
nav.left do
|
|
@@ -1319,6 +1413,15 @@ interactivity.
|
|
|
1319
1413
|
Cell content that does not fit its column is truncated with an ellipsis. This is a pure CSS behavior (`truncate` +
|
|
1320
1414
|
`min-w-0` on each cell) — no JavaScript is involved, and no configuration is needed to enable it.
|
|
1321
1415
|
|
|
1416
|
+
Pass `truncate: false` to `tramway_cell` to opt a specific cell out of this behavior and let its content wrap/overflow
|
|
1417
|
+
normally:
|
|
1418
|
+
|
|
1419
|
+
```erb
|
|
1420
|
+
<%= tramway_cell truncate: false do %>
|
|
1421
|
+
<%= user.bio %>
|
|
1422
|
+
<% end %>
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1322
1425
|
On narrow screens all columns always render, and the table scrolls horizontally within itself (the page never
|
|
1323
1426
|
stretches past the viewport width) so every column stays reachable. See
|
|
1324
1427
|
[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
|
|
@@ -11,11 +11,16 @@ module Tramway
|
|
|
11
11
|
attribute? :pages, Types::Array.of(Tramway::Configs::Entities::Page).default([].freeze)
|
|
12
12
|
attribute? :route, Tramway::Configs::Entities::Route
|
|
13
13
|
attribute? :namespace, Types::Coercible::String
|
|
14
|
+
attribute? :icon, Types::Coercible::String
|
|
14
15
|
|
|
15
16
|
# Route Struct contains implemented in Tramway CRUD and helpful routes for the entity
|
|
16
17
|
ACTIONS = %i[index show new create edit update destroy].freeze
|
|
17
18
|
RouteStruct = Struct.new(*ACTIONS)
|
|
18
19
|
|
|
20
|
+
# Page#action values handled by Tramway's built-in CRUD controller and routing.
|
|
21
|
+
# Any other page is a Custom Page, routed to a host app controller instead.
|
|
22
|
+
BUILT_IN_PAGE_ACTIONS = %w[index show create update destroy].freeze
|
|
23
|
+
|
|
19
24
|
# HumanName Struct contains human names forms for the entity
|
|
20
25
|
HumanNameStruct = Struct.new(:single, :plural)
|
|
21
26
|
|
|
@@ -39,6 +44,14 @@ module Tramway
|
|
|
39
44
|
pages.find { |page| page.action == name.to_s }
|
|
40
45
|
end
|
|
41
46
|
|
|
47
|
+
def built_in_pages
|
|
48
|
+
pages.select { |page| BUILT_IN_PAGE_ACTIONS.include?(page.action) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def custom_pages
|
|
52
|
+
pages.reject { |page| BUILT_IN_PAGE_ACTIONS.include?(page.action) }
|
|
53
|
+
end
|
|
54
|
+
|
|
42
55
|
def show_helper_method
|
|
43
56
|
build_helper_method(name, route:, namespace:, plural: false)
|
|
44
57
|
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/navbar.rb
CHANGED
|
@@ -59,7 +59,8 @@ module Tramway
|
|
|
59
59
|
entities.each do |entity|
|
|
60
60
|
next if entity.routes.index.blank?
|
|
61
61
|
|
|
62
|
-
item entity.human_name.plural, Tramway::Engine.routes.url_helpers.public_send(entity.routes.index)
|
|
62
|
+
item entity.human_name.plural, Tramway::Engine.routes.url_helpers.public_send(entity.routes.index),
|
|
63
|
+
icon: entity.icon
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
reset_filling
|
data/lib/tramway/version.rb
CHANGED