better_page 2.0.0
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 +7 -0
- data/CHANGELOG.md +62 -0
- data/MIT-LICENSE +20 -0
- data/README.md +357 -0
- data/Rakefile +3 -0
- data/docs/00-README.md +17 -0
- data/docs/01-getting-started.md +137 -0
- data/docs/02-component-registry.md +192 -0
- data/docs/03-base-pages.md +238 -0
- data/docs/04-schema-validation.md +180 -0
- data/docs/05-turbo-support.md +220 -0
- data/docs/06-compliance-analyzer.md +147 -0
- data/docs/07-configuration.md +157 -0
- data/guide/00-README.md +32 -0
- data/guide/01-quick-start.md +148 -0
- data/guide/02-building-index-page.md +258 -0
- data/guide/03-building-show-page.md +266 -0
- data/guide/04-building-form-page.md +309 -0
- data/guide/05-custom-pages.md +325 -0
- data/guide/06-best-practices.md +311 -0
- data/lib/better_page/base_page.rb +161 -0
- data/lib/better_page/compliance/analyzer.rb +409 -0
- data/lib/better_page/component_registry.rb +393 -0
- data/lib/better_page/config.rb +165 -0
- data/lib/better_page/configuration.rb +153 -0
- data/lib/better_page/custom_base_page.rb +85 -0
- data/lib/better_page/default_components.rb +200 -0
- data/lib/better_page/form_base_page.rb +170 -0
- data/lib/better_page/index_base_page.rb +69 -0
- data/lib/better_page/railtie.rb +34 -0
- data/lib/better_page/show_base_page.rb +120 -0
- data/lib/better_page/validation_error.rb +7 -0
- data/lib/better_page/version.rb +3 -0
- data/lib/better_page.rb +80 -0
- data/lib/generators/better_page/component_generator.rb +131 -0
- data/lib/generators/better_page/install_generator.rb +160 -0
- data/lib/generators/better_page/page_generator.rb +101 -0
- data/lib/generators/better_page/sync_generator.rb +109 -0
- data/lib/generators/better_page/templates/application_page.rb.tt +12 -0
- data/lib/generators/better_page/templates/better_page_initializer.rb.tt +53 -0
- data/lib/generators/better_page/templates/custom_base_page.rb.tt +83 -0
- data/lib/generators/better_page/templates/custom_page.rb.tt +31 -0
- data/lib/generators/better_page/templates/edit_page.rb.tt +46 -0
- data/lib/generators/better_page/templates/form_base_page.rb.tt +126 -0
- data/lib/generators/better_page/templates/index_base_page.rb.tt +65 -0
- data/lib/generators/better_page/templates/index_page.rb.tt +56 -0
- data/lib/generators/better_page/templates/javascript/controllers/app_nav_controller.js +57 -0
- data/lib/generators/better_page/templates/javascript/controllers/drawer_controller.js +99 -0
- data/lib/generators/better_page/templates/javascript/controllers/dropdown_controller.js +60 -0
- data/lib/generators/better_page/templates/javascript/controllers/index.js +36 -0
- data/lib/generators/better_page/templates/javascript/controllers/modal_controller.js +70 -0
- data/lib/generators/better_page/templates/javascript/controllers/sidebar_controller.js +152 -0
- data/lib/generators/better_page/templates/javascript/controllers/table_controller.js +60 -0
- data/lib/generators/better_page/templates/javascript/controllers/tabs_controller.js +89 -0
- data/lib/generators/better_page/templates/new_page.rb.tt +46 -0
- data/lib/generators/better_page/templates/show_base_page.rb.tt +117 -0
- data/lib/generators/better_page/templates/show_page.rb.tt +45 -0
- data/lib/generators/better_page/templates/view_components/application_view_component.rb.tt +7 -0
- data/lib/generators/better_page/templates/view_components/custom_view_component.html.erb.tt +21 -0
- data/lib/generators/better_page/templates/view_components/custom_view_component.rb.tt +21 -0
- data/lib/generators/better_page/templates/view_components/form_view_component.html.erb.tt +25 -0
- data/lib/generators/better_page/templates/view_components/form_view_component.rb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/index_view_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/index_view_component.rb.tt +29 -0
- data/lib/generators/better_page/templates/view_components/show_view_component.html.erb.tt +29 -0
- data/lib/generators/better_page/templates/view_components/show_view_component.rb.tt +25 -0
- data/lib/generators/better_page/templates/view_components/ui/alerts_component.html.erb.tt +47 -0
- data/lib/generators/better_page/templates/view_components/ui/alerts_component.rb.tt +47 -0
- data/lib/generators/better_page/templates/view_components/ui/content_section_component.html.erb.tt +42 -0
- data/lib/generators/better_page/templates/view_components/ui/content_section_component.rb.tt +34 -0
- data/lib/generators/better_page/templates/view_components/ui/drawer_component.html.erb.tt +73 -0
- data/lib/generators/better_page/templates/view_components/ui/drawer_component.rb.tt +78 -0
- data/lib/generators/better_page/templates/view_components/ui/errors_component.html.erb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/ui/errors_component.rb.tt +18 -0
- data/lib/generators/better_page/templates/view_components/ui/field_component.html.erb.tt +65 -0
- data/lib/generators/better_page/templates/view_components/ui/field_component.rb.tt +91 -0
- data/lib/generators/better_page/templates/view_components/ui/footer_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/ui/footer_component.rb.tt +32 -0
- data/lib/generators/better_page/templates/view_components/ui/header_component.html.erb.tt +55 -0
- data/lib/generators/better_page/templates/view_components/ui/header_component.rb.tt +39 -0
- data/lib/generators/better_page/templates/view_components/ui/modal_component.html.erb.tt +70 -0
- data/lib/generators/better_page/templates/view_components/ui/modal_component.rb.tt +54 -0
- data/lib/generators/better_page/templates/view_components/ui/overview_component.html.erb.tt +22 -0
- data/lib/generators/better_page/templates/view_components/ui/overview_component.rb.tt +71 -0
- data/lib/generators/better_page/templates/view_components/ui/pagination_component.html.erb.tt +63 -0
- data/lib/generators/better_page/templates/view_components/ui/pagination_component.rb.tt +69 -0
- data/lib/generators/better_page/templates/view_components/ui/panel_component.html.erb.tt +31 -0
- data/lib/generators/better_page/templates/view_components/ui/panel_component.rb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/ui/statistics_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/ui/statistics_component.rb.tt +51 -0
- data/lib/generators/better_page/templates/view_components/ui/table_component.html.erb.tt +112 -0
- data/lib/generators/better_page/templates/view_components/ui/table_component.rb.tt +88 -0
- data/lib/generators/better_page/templates/view_components/ui/tabs_component.html.erb.tt +52 -0
- data/lib/generators/better_page/templates/view_components/ui/tabs_component.rb.tt +76 -0
- data/lib/generators/better_page/templates/view_components/ui/widget_component.html.erb.tt +72 -0
- data/lib/generators/better_page/templates/view_components/ui/widget_component.rb.tt +34 -0
- data/lib/tasks/better_page.rake +70 -0
- data/lib/tasks/better_page_tasks.rake +4 -0
- metadata +188 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Turbo Support
|
|
2
|
+
|
|
3
|
+
BetterPage provides built-in support for Turbo Frames and Turbo Streams.
|
|
4
|
+
|
|
5
|
+
### Turbo Helpers in ViewComponents
|
|
6
|
+
|
|
7
|
+
All BetterPage ViewComponents inherit from `ApplicationViewComponent`, which includes `Turbo::FramesHelper`. This means you have access to Turbo helpers like `turbo_frame_tag` directly in your component templates.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# app/components/better_page/application_view_component.rb
|
|
11
|
+
module BetterPage
|
|
12
|
+
class ApplicationViewComponent < ViewComponent::Base
|
|
13
|
+
include Turbo::FramesHelper
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This allows you to use Turbo helpers in any component template:
|
|
19
|
+
|
|
20
|
+
```erb
|
|
21
|
+
<%# In any component .html.erb template %>
|
|
22
|
+
<%= turbo_frame_tag "my_frame" do %>
|
|
23
|
+
<!-- Content -->
|
|
24
|
+
<% end %>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
--------------------------------
|
|
28
|
+
|
|
29
|
+
### Overview
|
|
30
|
+
|
|
31
|
+
| Method Type | Use Case | Returns |
|
|
32
|
+
|-------------|----------|---------|
|
|
33
|
+
| `frame_*` | Lazy loading, navigation | Single Hash |
|
|
34
|
+
| `stream_*` | Real-time updates, form responses | Array of Hashes |
|
|
35
|
+
|
|
36
|
+
--------------------------------
|
|
37
|
+
|
|
38
|
+
### Turbo Frame - Lazy Load Component
|
|
39
|
+
|
|
40
|
+
Use `frame_<action>(:component)` to get a single component for Turbo Frame.
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
# In controller
|
|
44
|
+
def table
|
|
45
|
+
@products = Product.all
|
|
46
|
+
component = Products::IndexPage.new(@products, current_user).frame_index(:table)
|
|
47
|
+
|
|
48
|
+
render component[:klass].new(**component[:config])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# frame_index(:table) returns:
|
|
52
|
+
# {
|
|
53
|
+
# component: :table,
|
|
54
|
+
# config: { items: [...], columns: [...] },
|
|
55
|
+
# klass: TableComponent,
|
|
56
|
+
# target: "better_page_table"
|
|
57
|
+
# }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
--------------------------------
|
|
61
|
+
|
|
62
|
+
### Turbo Frame View Setup
|
|
63
|
+
|
|
64
|
+
```erb
|
|
65
|
+
<turbo-frame id="better_page_table" src="<%= table_products_path %>" loading="lazy">
|
|
66
|
+
<p>Loading products...</p>
|
|
67
|
+
</turbo-frame>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
--------------------------------
|
|
71
|
+
|
|
72
|
+
### Turbo Stream - Update Multiple Components
|
|
73
|
+
|
|
74
|
+
Use `stream_<action>(*components)` to get multiple components for Turbo Stream.
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
# In controller
|
|
78
|
+
def refresh
|
|
79
|
+
@products = Product.filtered(params)
|
|
80
|
+
components = Products::IndexPage.new(@products, current_user).stream_index(:table, :statistics)
|
|
81
|
+
|
|
82
|
+
respond_to do |format|
|
|
83
|
+
format.turbo_stream do
|
|
84
|
+
render turbo_stream: components.map { |c|
|
|
85
|
+
turbo_stream.replace(c[:target], c[:klass].new(**c[:config]))
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# stream_index(:table, :statistics) returns:
|
|
92
|
+
# [
|
|
93
|
+
# { component: :table, config: {...}, klass: TableComponent, target: "better_page_table" },
|
|
94
|
+
# { component: :statistics, config: {...}, klass: StatsComponent, target: "better_page_statistics" }
|
|
95
|
+
# ]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
--------------------------------
|
|
99
|
+
|
|
100
|
+
### Turbo Stream View Setup
|
|
101
|
+
|
|
102
|
+
```erb
|
|
103
|
+
<div id="better_page_alerts">
|
|
104
|
+
<%# Alerts rendered here %>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div id="better_page_statistics">
|
|
108
|
+
<%# Statistics rendered here %>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div id="better_page_table">
|
|
112
|
+
<%# Table rendered here %>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div id="better_page_pagination">
|
|
116
|
+
<%# Pagination rendered here %>
|
|
117
|
+
</div>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
--------------------------------
|
|
121
|
+
|
|
122
|
+
### Dynamic Methods by Page Type
|
|
123
|
+
|
|
124
|
+
Methods are generated based on the page's main action.
|
|
125
|
+
|
|
126
|
+
| Page Type | Main Action | Frame Method | Stream Method |
|
|
127
|
+
|-----------|-------------|--------------|---------------|
|
|
128
|
+
| IndexBasePage | `index` | `frame_index(:component)` | `stream_index(*components)` |
|
|
129
|
+
| ShowBasePage | `show` | `frame_show(:component)` | `stream_show(*components)` |
|
|
130
|
+
| FormBasePage | `form` | `frame_form(:component)` | `stream_form(*components)` |
|
|
131
|
+
| CustomBasePage | `custom` | `frame_custom(:component)` | `stream_custom(*components)` |
|
|
132
|
+
|
|
133
|
+
--------------------------------
|
|
134
|
+
|
|
135
|
+
### Custom Action Methods
|
|
136
|
+
|
|
137
|
+
If you define a custom action, turbo methods work automatically.
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
class Reports::DailyPage < CustomBasePage
|
|
141
|
+
register_component :chart, default: {}
|
|
142
|
+
|
|
143
|
+
def daily
|
|
144
|
+
build_page
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def chart
|
|
148
|
+
{ type: :line, data: @data }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Usage
|
|
153
|
+
page = Reports::DailyPage.new(@data)
|
|
154
|
+
page.frame_daily(:chart) # Single component
|
|
155
|
+
page.stream_daily(:chart, :summary) # Multiple components
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
--------------------------------
|
|
159
|
+
|
|
160
|
+
### Customize Default Stream Components
|
|
161
|
+
|
|
162
|
+
Override `stream_components` to define defaults.
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
class Products::IndexPage < IndexBasePage
|
|
166
|
+
def stream_components
|
|
167
|
+
%i[alerts statistics table pagination]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Now stream_index without arguments returns these four
|
|
172
|
+
page.stream_index # => alerts, statistics, table, pagination
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
--------------------------------
|
|
176
|
+
|
|
177
|
+
### Customize Frame Target
|
|
178
|
+
|
|
179
|
+
Override `frame_target` to customize DOM element IDs.
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
def frame_target(name)
|
|
183
|
+
"products_#{name}" # "products_table" instead of "better_page_table"
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
--------------------------------
|
|
188
|
+
|
|
189
|
+
### Customize Stream Target
|
|
190
|
+
|
|
191
|
+
Override `stream_target` to customize DOM element IDs.
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
def stream_target(name)
|
|
195
|
+
"products_#{name}"
|
|
196
|
+
end
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
--------------------------------
|
|
200
|
+
|
|
201
|
+
### Available Turbo Stream Actions
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
components.each do |c|
|
|
205
|
+
# Replace - replaces entire element
|
|
206
|
+
turbo_stream.replace(c[:target], c[:klass].new(**c[:config]))
|
|
207
|
+
|
|
208
|
+
# Update - replaces content only
|
|
209
|
+
turbo_stream.update(c[:target], c[:klass].new(**c[:config]))
|
|
210
|
+
|
|
211
|
+
# Append - adds after existing content
|
|
212
|
+
turbo_stream.append(c[:target], c[:klass].new(**c[:config]))
|
|
213
|
+
|
|
214
|
+
# Prepend - adds before existing content
|
|
215
|
+
turbo_stream.prepend(c[:target], c[:klass].new(**c[:config]))
|
|
216
|
+
|
|
217
|
+
# Remove - removes element
|
|
218
|
+
turbo_stream.remove(c[:target])
|
|
219
|
+
end
|
|
220
|
+
```
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Compliance Analyzer
|
|
2
|
+
|
|
3
|
+
The Compliance Analyzer checks that pages follow BetterPage architecture rules.
|
|
4
|
+
|
|
5
|
+
### Run Compliance Analyzer
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
rake better_page:compliance:analyze
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
--------------------------------
|
|
12
|
+
|
|
13
|
+
### Run in Verbose Mode
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
VERBOSE=true rake better_page:compliance:analyze
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
--------------------------------
|
|
20
|
+
|
|
21
|
+
### Architecture Rules
|
|
22
|
+
|
|
23
|
+
Pages must follow these rules:
|
|
24
|
+
|
|
25
|
+
1. **No database queries** - Data passed via constructor
|
|
26
|
+
2. **No business logic** - UI configuration only
|
|
27
|
+
3. **No service layer access** - No service objects
|
|
28
|
+
4. **Hash-only structures** - No OpenStruct/Struct
|
|
29
|
+
5. **Separate panels for checkboxes** - Checkbox/radio fields in separate panels
|
|
30
|
+
|
|
31
|
+
--------------------------------
|
|
32
|
+
|
|
33
|
+
### Required Component Methods
|
|
34
|
+
|
|
35
|
+
Each page type must implement required methods.
|
|
36
|
+
|
|
37
|
+
| Page Type | Required Methods |
|
|
38
|
+
|-----------|-----------------|
|
|
39
|
+
| IndexPage | `header`, `table` |
|
|
40
|
+
| ShowPage | `header` |
|
|
41
|
+
| FormPage | `header`, `panels` |
|
|
42
|
+
| CustomPage | `content` |
|
|
43
|
+
|
|
44
|
+
--------------------------------
|
|
45
|
+
|
|
46
|
+
### Forbidden Database Patterns
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# WRONG - Database queries in page
|
|
50
|
+
def header
|
|
51
|
+
{ title: "#{User.count} Users" }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def table
|
|
55
|
+
{ items: User.where(active: true) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# CORRECT - Data passed via constructor
|
|
59
|
+
def initialize(users, stats)
|
|
60
|
+
@users = users
|
|
61
|
+
@stats = stats
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def header
|
|
65
|
+
{ title: "#{@stats[:count]} Users" }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def table
|
|
69
|
+
{ items: @users }
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
--------------------------------
|
|
74
|
+
|
|
75
|
+
### Forbidden Business Logic Patterns
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# WRONG - Business logic in page
|
|
79
|
+
def calculate_total # Business calculation
|
|
80
|
+
def process_data # Business processing
|
|
81
|
+
def validate_user # Validation logic
|
|
82
|
+
def save_record # Persistence
|
|
83
|
+
|
|
84
|
+
# WRONG - Service access
|
|
85
|
+
def header
|
|
86
|
+
result = UserService.new.get_stats
|
|
87
|
+
{ title: result[:title] }
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
--------------------------------
|
|
92
|
+
|
|
93
|
+
### Forbidden External Dependencies
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
# WRONG - External HTTP clients
|
|
97
|
+
Net::HTTP.get(...)
|
|
98
|
+
HTTParty.get(...)
|
|
99
|
+
Faraday.get(...)
|
|
100
|
+
|
|
101
|
+
# WRONG - External services
|
|
102
|
+
Redis.current.get(...)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
--------------------------------
|
|
106
|
+
|
|
107
|
+
### Compliance Report Output
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
SUMMARY
|
|
111
|
+
=======
|
|
112
|
+
Total pages analyzed: 25
|
|
113
|
+
[OK] Fully compliant: 20 (80.0%)
|
|
114
|
+
[WARN] With warnings: 3 (12.0%)
|
|
115
|
+
[ERROR] With errors: 2 (8.0%)
|
|
116
|
+
|
|
117
|
+
CRITICAL ISSUES
|
|
118
|
+
===============
|
|
119
|
+
- app/pages/admin/users/index_page.rb
|
|
120
|
+
- Database queries forbidden in Page
|
|
121
|
+
- Missing required component method: table
|
|
122
|
+
|
|
123
|
+
RECOMMENDATIONS
|
|
124
|
+
===============
|
|
125
|
+
1. Remove database queries from Pages
|
|
126
|
+
2. Remove business logic - keep UI configuration only
|
|
127
|
+
3. Implement required component methods
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
--------------------------------
|
|
131
|
+
|
|
132
|
+
### Programmatic Usage
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
analyzer = BetterPage::Compliance::Analyzer.new
|
|
136
|
+
|
|
137
|
+
# Analyze single page
|
|
138
|
+
result = analyzer.analyze_page("app/pages/admin/users/index_page.rb")
|
|
139
|
+
puts result[:status] # :compliant, :warning, or :error
|
|
140
|
+
puts result[:issues] # Array of issue messages
|
|
141
|
+
puts result[:warnings] # Array of warning messages
|
|
142
|
+
|
|
143
|
+
# Analyze all pages
|
|
144
|
+
analyzer.analyze_all
|
|
145
|
+
puts analyzer.compliant_count
|
|
146
|
+
puts analyzer.error_count
|
|
147
|
+
```
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
BetterPage uses a hybrid configuration system that combines global configuration with local customization.
|
|
4
|
+
|
|
5
|
+
### Configuration Levels
|
|
6
|
+
|
|
7
|
+
Components can be registered at three levels:
|
|
8
|
+
|
|
9
|
+
1. **Gem Defaults** - Built-in components registered by `BetterPage::DefaultComponents`
|
|
10
|
+
2. **Global Configuration** - User customizations in `config/initializers/better_page.rb`
|
|
11
|
+
3. **Local Classes** - Components in base page classes or individual pages
|
|
12
|
+
|
|
13
|
+
--------------------------------
|
|
14
|
+
|
|
15
|
+
### Global Configuration
|
|
16
|
+
|
|
17
|
+
Configure BetterPage in your initializer:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# config/initializers/better_page.rb
|
|
21
|
+
BetterPage.configure do |config|
|
|
22
|
+
# Register a new component
|
|
23
|
+
config.register_component :sidebar, default: { enabled: false } do
|
|
24
|
+
optional(:enabled).filled(:bool)
|
|
25
|
+
optional(:items).array(:hash)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Map component to page types
|
|
29
|
+
config.allow_components :index, :sidebar
|
|
30
|
+
config.allow_components :show, :sidebar
|
|
31
|
+
|
|
32
|
+
# Make component required for a page type
|
|
33
|
+
config.require_components :index, :sidebar
|
|
34
|
+
|
|
35
|
+
# Override a default component
|
|
36
|
+
config.register_component :pagination, default: { enabled: true, per_page: 25 }
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
--------------------------------
|
|
41
|
+
|
|
42
|
+
### Configuration API
|
|
43
|
+
|
|
44
|
+
| Method | Description |
|
|
45
|
+
|--------|-------------|
|
|
46
|
+
| `register_component(name, options, &schema)` | Register a component with optional schema |
|
|
47
|
+
| `allow_components(page_type, *names)` | Map components to a page type |
|
|
48
|
+
| `require_components(page_type, *names)` | Mark components as required for a page type |
|
|
49
|
+
| `components_for(page_type)` | Get component names for a page type |
|
|
50
|
+
| `component(name)` | Get a component definition |
|
|
51
|
+
| `component_required?(page_type, name)` | Check if component is required |
|
|
52
|
+
|
|
53
|
+
--------------------------------
|
|
54
|
+
|
|
55
|
+
### Page Type DSL
|
|
56
|
+
|
|
57
|
+
Base page classes use `page_type` to inherit components from global configuration:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# app/pages/index_base_page.rb
|
|
61
|
+
class IndexBasePage < ApplicationPage
|
|
62
|
+
page_type :index # Inherits components mapped to :index
|
|
63
|
+
|
|
64
|
+
# Add local components
|
|
65
|
+
register_component :quick_filters, default: []
|
|
66
|
+
|
|
67
|
+
def index
|
|
68
|
+
build_page
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def view_component_class
|
|
72
|
+
BetterPage::IndexViewComponent
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Available page types: `:index`, `:show`, `:form`, `:custom`
|
|
78
|
+
|
|
79
|
+
--------------------------------
|
|
80
|
+
|
|
81
|
+
### Default Components
|
|
82
|
+
|
|
83
|
+
BetterPage registers these components by default:
|
|
84
|
+
|
|
85
|
+
**Shared Components:**
|
|
86
|
+
- `header` - Page header with title, breadcrumbs, actions
|
|
87
|
+
- `alerts` - Alert messages (default: `[]`)
|
|
88
|
+
- `footer` - Footer section (default: `{ enabled: false }`)
|
|
89
|
+
- `statistics` - Statistic cards (default: `[]`)
|
|
90
|
+
- `overview` - Overview section (default: `{ enabled: false }`)
|
|
91
|
+
|
|
92
|
+
**Index Components:**
|
|
93
|
+
- `table` - Data table with columns and actions
|
|
94
|
+
- `metrics` - Metric displays
|
|
95
|
+
- `tabs` - Tab navigation
|
|
96
|
+
- `search` - Search configuration
|
|
97
|
+
- `pagination` - Pagination settings
|
|
98
|
+
- `calendar` - Calendar view
|
|
99
|
+
- `modals` - Modal dialogs
|
|
100
|
+
- `split_view` - Split view layout
|
|
101
|
+
|
|
102
|
+
**Show Components:**
|
|
103
|
+
- `content_sections` - Content sections
|
|
104
|
+
|
|
105
|
+
**Form Components:**
|
|
106
|
+
- `panels` - Form panels with fields
|
|
107
|
+
- `errors` - Error display
|
|
108
|
+
|
|
109
|
+
**Custom Components:**
|
|
110
|
+
- `content` - Custom content
|
|
111
|
+
|
|
112
|
+
--------------------------------
|
|
113
|
+
|
|
114
|
+
### Sync Generator
|
|
115
|
+
|
|
116
|
+
Check for new components when upgrading BetterPage:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
rails generate better_page:sync
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This compares your configuration with gem defaults and reports:
|
|
123
|
+
- New components available from the gem
|
|
124
|
+
- Components you've customized
|
|
125
|
+
|
|
126
|
+
--------------------------------
|
|
127
|
+
|
|
128
|
+
### Example: Adding a Custom Component
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# 1. Register in initializer
|
|
132
|
+
# config/initializers/better_page.rb
|
|
133
|
+
BetterPage.configure do |config|
|
|
134
|
+
config.register_component :activity_feed, default: { events: [], limit: 10 } do
|
|
135
|
+
optional(:events).array(:hash)
|
|
136
|
+
optional(:limit).filled(:integer)
|
|
137
|
+
end
|
|
138
|
+
config.allow_components :show, :activity_feed
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# 2. Use in a page
|
|
142
|
+
# app/pages/admin/users/show_page.rb
|
|
143
|
+
class Admin::Users::ShowPage < ShowBasePage
|
|
144
|
+
def activity_feed
|
|
145
|
+
{
|
|
146
|
+
events: @user.recent_activities.map { |a| format_activity(a) },
|
|
147
|
+
limit: 20
|
|
148
|
+
}
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def format_activity(activity)
|
|
154
|
+
{ type: activity.type, message: activity.message, at: activity.created_at }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
```
|
data/guide/00-README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# BetterPage Guides
|
|
2
|
+
|
|
3
|
+
Step-by-step tutorials for building pages with BetterPage.
|
|
4
|
+
|
|
5
|
+
### Guide Index
|
|
6
|
+
|
|
7
|
+
| Guide | Description |
|
|
8
|
+
|-------|-------------|
|
|
9
|
+
| [01-quick-start](01-quick-start.md) | Get started in 5 minutes |
|
|
10
|
+
| [02-building-index-page](02-building-index-page.md) | Build feature-rich list pages |
|
|
11
|
+
| [03-building-show-page](03-building-show-page.md) | Build detail pages with content sections |
|
|
12
|
+
| [04-building-form-page](04-building-form-page.md) | Build new and edit forms |
|
|
13
|
+
| [05-custom-pages](05-custom-pages.md) | Build dashboards and reports |
|
|
14
|
+
| [06-best-practices](06-best-practices.md) | Guidelines for maintainable pages |
|
|
15
|
+
|
|
16
|
+
--------------------------------
|
|
17
|
+
|
|
18
|
+
### Prerequisites
|
|
19
|
+
|
|
20
|
+
Before starting, ensure you have:
|
|
21
|
+
- Rails >= 8.1.1
|
|
22
|
+
- BetterPage gem installed
|
|
23
|
+
- Basic familiarity with Ruby and Rails
|
|
24
|
+
|
|
25
|
+
--------------------------------
|
|
26
|
+
|
|
27
|
+
### Getting Help
|
|
28
|
+
|
|
29
|
+
If you need assistance:
|
|
30
|
+
- Check the [documentation](../docs/00-README.md)
|
|
31
|
+
- Review the example code in each guide
|
|
32
|
+
- Run compliance checks: `rake better_page:analyze`
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Quick Start Guide
|
|
2
|
+
|
|
3
|
+
Get started with BetterPage in 5 minutes.
|
|
4
|
+
|
|
5
|
+
### Install the Gem
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# Gemfile
|
|
9
|
+
gem "better_page"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bundle install
|
|
14
|
+
rails g better_page:install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This creates:
|
|
18
|
+
- `app/pages/application_page.rb` - Base page class
|
|
19
|
+
- `app/pages/index_base_page.rb` - Base for index pages
|
|
20
|
+
- `app/pages/show_base_page.rb` - Base for show pages
|
|
21
|
+
- `app/pages/form_base_page.rb` - Base for form pages
|
|
22
|
+
- `app/pages/custom_base_page.rb` - Base for custom pages
|
|
23
|
+
- `config/initializers/better_page.rb` - Configuration file
|
|
24
|
+
|
|
25
|
+
--------------------------------
|
|
26
|
+
|
|
27
|
+
### Generate Your First Page
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
rails g better_page:page Admin::Products index show new edit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This creates:
|
|
34
|
+
- `app/pages/admin/products/index_page.rb`
|
|
35
|
+
- `app/pages/admin/products/show_page.rb`
|
|
36
|
+
- `app/pages/admin/products/new_page.rb`
|
|
37
|
+
- `app/pages/admin/products/edit_page.rb`
|
|
38
|
+
|
|
39
|
+
--------------------------------
|
|
40
|
+
|
|
41
|
+
### Basic Index Page
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# app/pages/admin/products/index_page.rb
|
|
45
|
+
module Admin
|
|
46
|
+
module Products
|
|
47
|
+
class IndexPage < IndexBasePage
|
|
48
|
+
def initialize(products, metadata = {})
|
|
49
|
+
@products = products
|
|
50
|
+
@user = metadata[:user]
|
|
51
|
+
super(products, metadata)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def header
|
|
57
|
+
{
|
|
58
|
+
title: "Products",
|
|
59
|
+
breadcrumbs: [
|
|
60
|
+
{ label: "Home", path: "/" },
|
|
61
|
+
{ label: "Products" }
|
|
62
|
+
],
|
|
63
|
+
actions: [
|
|
64
|
+
{ label: "New Product", path: "/products/new", icon: "plus", style: :primary }
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def table
|
|
70
|
+
{
|
|
71
|
+
items: @products,
|
|
72
|
+
columns: [
|
|
73
|
+
{ key: :name, label: "Name", type: :link },
|
|
74
|
+
{ key: :price, label: "Price", format: :currency },
|
|
75
|
+
{ key: :active, label: "Status", type: :boolean }
|
|
76
|
+
],
|
|
77
|
+
empty_state: {
|
|
78
|
+
icon: "box",
|
|
79
|
+
title: "No products yet",
|
|
80
|
+
message: "Create your first product to get started"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
--------------------------------
|
|
90
|
+
|
|
91
|
+
### Use in Controller
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
# app/controllers/admin/products_controller.rb
|
|
95
|
+
class Admin::ProductsController < ApplicationController
|
|
96
|
+
def index
|
|
97
|
+
products = Product.all
|
|
98
|
+
@page = Admin::Products::IndexPage.new(products, user: current_user).index
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
--------------------------------
|
|
104
|
+
|
|
105
|
+
### Render in View
|
|
106
|
+
|
|
107
|
+
```erb
|
|
108
|
+
<%# app/views/admin/products/index.html.erb %>
|
|
109
|
+
<%= render "shared/page", page: @page %>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Or access data directly:
|
|
113
|
+
|
|
114
|
+
```erb
|
|
115
|
+
<h1><%= @page[:header][:title] %></h1>
|
|
116
|
+
|
|
117
|
+
<table>
|
|
118
|
+
<thead>
|
|
119
|
+
<tr>
|
|
120
|
+
<% @page[:table][:columns].each do |column| %>
|
|
121
|
+
<th><%= column[:label] %></th>
|
|
122
|
+
<% end %>
|
|
123
|
+
</tr>
|
|
124
|
+
</thead>
|
|
125
|
+
<tbody>
|
|
126
|
+
<% @page[:table][:items].each do |item| %>
|
|
127
|
+
<tr>
|
|
128
|
+
<% @page[:table][:columns].each do |column| %>
|
|
129
|
+
<td><%= item.send(column[:key]) %></td>
|
|
130
|
+
<% end %>
|
|
131
|
+
</tr>
|
|
132
|
+
<% end %>
|
|
133
|
+
</tbody>
|
|
134
|
+
</table>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
--------------------------------
|
|
138
|
+
|
|
139
|
+
### Verify Compliance
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
rake better_page:analyze
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This ensures your pages follow the architecture rules:
|
|
146
|
+
- No database queries in pages
|
|
147
|
+
- No business logic
|
|
148
|
+
- Data passed via constructor
|