Skip to content

Commit 08e1b8b

Browse files
committed
feat: implement explicit action-aware template naming convention for improved clarity in product catalog
1 parent 9823924 commit 08e1b8b

File tree

7 files changed

+143
-60
lines changed

7 files changed

+143
-60
lines changed

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Facet is a server-side rendering framework that maps REST API responses to HTML
1313
```http
1414
GET /shop/products
1515
Accept: application/json → JSON response (REST API unchanged)
16-
Accept: text/html → HTML rendered from templates/shop/products/index.html
16+
Accept: text/html → HTML rendered from templates/shop/products/list.html
1717
```
1818

1919
Templates are opt-in. Add HTML rendering only where you need it; your REST API continues working unchanged.
@@ -74,14 +74,19 @@ Here's the actual product list template from the example (simplified):
7474

7575
### Convention-Based Routing
7676

77-
Templates automatically resolve based on request path:
77+
Templates automatically resolve based on request path with explicit action templates:
7878

7979
```http
80-
GET /shop/products → templates/shop/products/index.html
81-
GET /shop/products/123 → templates/shop/products/view.html
80+
GET /shop/products → templates/shop/products/list.html (collection view)
81+
GET /shop/products/123 → templates/shop/products/view.html (document view)
8282
```
8383

84-
Hierarchical fallback: if `shop/products/index.html` doesn't exist, tries `shop/index.html`, then `index.html`.
84+
**Naming convention:**
85+
- **`list.html`** - Collection views (recommended - no conditional logic needed)
86+
- **`view.html`** - Document views (recommended - no conditional logic needed)
87+
- **`index.html`** - Optional fallback (when list/view can share template logic)
88+
89+
Hierarchical fallback: if `shop/products/list.html` doesn't exist, tries `shop/products/index.html`, then `shop/list.html`, then `shop/index.html`, finally `list.html` and `index.html`.
8590

8691
### HTMX Support Built In
8792

core/src/main/java/org/facet/templates/PathBasedTemplateResolver.java

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,46 @@
1212
*
1313
* <p>
1414
* This resolver maps request paths to template files using a convention-over-configuration
15-
* approach. Templates are organized in a directory structure that mirrors the URL structure,
16-
* with each resource having an optional {@code index.html} template.
15+
* approach. Templates are organized in a directory structure that mirrors the URL structure.
1716
*
1817
* <p>
19-
* <strong>Full Page Resolution Algorithm:</strong>
18+
* <strong>Recommended Template Naming Convention:</strong>
19+
* </p>
20+
* <ul>
21+
* <li><strong>list.html</strong> - For collection views (recommended for clarity, no conditional logic needed)</li>
22+
* <li><strong>view.html</strong> - For document views (recommended for clarity, no conditional logic needed)</li>
23+
* <li><strong>index.html</strong> - Optional unified fallback (use when list/view can share template logic)</li>
24+
* </ul>
25+
*
26+
* <p>
27+
* <strong>Collection Request Resolution Example:</strong>
28+
* </p>
29+
*
30+
* <pre>
31+
* Request: GET /products (COLLECTION)
32+
*
33+
* Search order:
34+
* 1. templates/products/list.html (recommended - explicit collection view)
35+
* 2. templates/products/index.html (optional fallback)
36+
* 3. templates/list.html (global collection template)
37+
* 4. templates/index.html (global fallback)
38+
* 5. return empty Optional (no template found)
39+
* </pre>
40+
*
41+
* <p>
42+
* <strong>Document Request Resolution Example:</strong>
2043
* </p>
2144
*
2245
* <pre>
23-
* Request: GET /a/b/c
46+
* Request: GET /products/123 (DOCUMENT)
2447
*
2548
* Search order:
26-
* 1. templates/a/b/c/index.html (most specific - exact match)
27-
* 2. templates/a/b/index.html (parent level)
28-
* 3. templates/a/index.html (top level)
29-
* 4. templates/index.html (root fallback - catch-all)
30-
* 5. return empty Optional (no template found)
49+
* 1. templates/products/123/view.html (document-specific override)
50+
* 2. templates/products/view.html (recommended - explicit document view)
51+
* 3. templates/products/index.html (optional fallback)
52+
* 4. templates/view.html (global document template)
53+
* 5. templates/index.html (global fallback)
54+
* 6. return empty Optional (no template found)
3155
* </pre>
3256
*
3357
* <p>
@@ -86,8 +110,8 @@ public PathBasedTemplateResolver() {
86110
/**
87111
* Resolves a template name for the given request path using hierarchical fallback.
88112
*
89-
* <p>This method uses the legacy resolution strategy (index.html only).
90-
* For action-aware resolution, use {@link #resolve(TemplateProcessor, String, TYPE)}.
113+
* <p>This method only checks for index.html templates.
114+
* For explicit action-aware resolution (list.html, view.html), use {@link #resolve(TemplateProcessor, String, TYPE)}.
91115
*
92116
* @param templateProcessor the template processor to check for template existence
93117
* @param requestPath the request path (e.g., "/restheart/users")
@@ -99,23 +123,30 @@ public Optional<String> resolve(final TemplateProcessor templateProcessor, final
99123
}
100124

101125
/**
102-
* Resolves a template name with action-aware resolution (list.html, view.html, index.html).
126+
* Resolves a template name with explicit action-aware resolution (list.html, view.html, index.html).
127+
*
128+
* <p><strong>Recommended Pattern:</strong> Use explicit templates for clean separation:
129+
* <ul>
130+
* <li><strong>list.html</strong> - Collection views (recommended - no conditional logic needed)</li>
131+
* <li><strong>view.html</strong> - Document views (recommended - no conditional logic needed)</li>
132+
* <li><strong>index.html</strong> - Optional fallback (for simple cases where list/view share logic)</li>
133+
* </ul>
103134
*
104135
* <p><strong>Resolution Strategy:</strong></p>
105136
* <ul>
106137
* <li><strong>COLLECTION requests:</strong>
107138
* <ol>
108-
* <li>products/list.html (explicit - recommended)</li>
109-
* <li>products/index.html (unified fallback)</li>
139+
* <li>products/list.html (recommended - explicit collection view)</li>
140+
* <li>products/index.html (optional fallback)</li>
110141
* <li>Hierarchical parent fallback</li>
111142
* </ol>
112143
* </li>
113144
* <li><strong>DOCUMENT requests:</strong>
114145
* <ol>
115-
* <li>products/:id/view.html (specific override)</li>
116-
* <li>products/:id/index.html (specific unified)</li>
117-
* <li>products/view.html (explicit generic)</li>
118-
* <li>products/index.html (unified fallback)</li>
146+
* <li>products/:id/view.html (document-specific override)</li>
147+
* <li>products/:id/index.html (document-specific fallback)</li>
148+
* <li>products/view.html (recommended - explicit document view)</li>
149+
* <li>products/index.html (optional fallback)</li>
119150
* <li>Hierarchical parent fallback</li>
120151
* </ol>
121152
* </li>

core/src/main/java/org/facet/templates/TemplateResolver.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,27 @@
1111
* in the Facet SSR framework. Implementations determine which template
1212
* file should be used to render a given resource path.
1313
*
14+
* <p><strong>Recommended Convention:</strong> Use explicit action templates for clean separation:
15+
* <ul>
16+
* <li><strong>list.html</strong> - For collection views (multiple items)</li>
17+
* <li><strong>view.html</strong> - For document views (single item)</li>
18+
* <li><strong>index.html</strong> - Optional unified fallback (when list/view can share logic)</li>
19+
* </ul>
20+
*
1421
* <p>Example resolution for path "/products" (collection):
1522
* <pre>
16-
* 1. templates/products/list.html (explicit - recommended)
17-
* 2. templates/products/index.html (unified fallback)
18-
* 3. templates/list.html (global explicit)
23+
* 1. templates/products/list.html (recommended - explicit collection view)
24+
* 2. templates/products/index.html (optional unified fallback)
25+
* 3. templates/list.html (global collection template)
1926
* 4. templates/index.html (global fallback)
2027
* </pre>
2128
*
2229
* <p>Example resolution for path "/products/:id" (document):
2330
* <pre>
24-
* 1. templates/products/:id/view.html (specific override)
25-
* 2. templates/products/view.html (explicit - recommended)
26-
* 3. templates/products/index.html (unified fallback)
27-
* 4. templates/view.html (global explicit)
31+
* 1. templates/products/:id/view.html (document-specific override)
32+
* 2. templates/products/view.html (recommended - explicit document view)
33+
* 3. templates/products/index.html (optional unified fallback)
34+
* 4. templates/view.html (global document template)
2835
* 5. templates/index.html (global fallback)
2936
* </pre>
3037
*
@@ -35,10 +42,10 @@ public interface TemplateResolver {
3542
/**
3643
* Resolves a template name for the given request path.
3744
*
38-
* <p>This method uses the legacy resolution strategy that only checks for index.html.
39-
* For action-aware resolution (list.html, view.html), use {@link #resolve(TemplateProcessor, String, TYPE)}.
45+
* <p>This method only checks for index.html templates.
46+
* For explicit action-aware resolution (list.html, view.html), use {@link #resolve(TemplateProcessor, String, TYPE)}.
4047
*
41-
* <p>The resolver should search for templates in a hierarchical manner,
48+
* <p>The resolver searches for templates in a hierarchical manner,
4249
* starting with the most specific path and falling back to parent paths
4350
* until a template is found.
4451
*
@@ -50,10 +57,14 @@ public interface TemplateResolver {
5057
Optional<String> resolve(TemplateProcessor templateProcessor, String requestPath);
5158

5259
/**
53-
* Resolves a template name for the given request path with action-aware resolution.
60+
* Resolves a template name for the given request path with explicit action-aware resolution.
5461
*
55-
* <p>This method supports both explicit action-based templates (list.html, view.html)
56-
* and unified templates (index.html) as fallback.
62+
* <p><strong>Recommended Pattern:</strong> Use explicit templates for clarity:
63+
* <ul>
64+
* <li><strong>list.html</strong> - Collection views (no conditional logic needed)</li>
65+
* <li><strong>view.html</strong> - Document views (no conditional logic needed)</li>
66+
* <li><strong>index.html</strong> - Optional fallback (for simple cases)</li>
67+
* </ul>
5768
*
5869
* <p><strong>Resolution Strategy:</strong></p>
5970
* <ul>

docs/DEVELOPERS_GUIDE.md

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This guide provides a complete reference for Facet's features and capabilities.
99

1010
Facet is a data-driven web framework that transforms JSON documents into server-rendered HTML through path-based templates. It's a RESTHeart plugin that provides **hybrid API/UI from the same endpoint**—JSON for API clients, HTML for browsers.
1111

12-
**Core principle**: Convention over Configuration. Your template structure mirrors your API paths. A request to `/mydb/products` automatically uses `templates/mydb/products/index.html` when the browser requests HTML.
12+
**Core principle**: Convention over Configuration. Your template structure mirrors your API paths. A request to `/mydb/products` (collection) automatically uses `templates/mydb/products/list.html` or falls back to `index.html` when the browser requests HTML.
1313

1414
**Technology stack**:
1515
- **[RESTHeart](https://restheart.org/)** - MongoDB REST API server (provides the plugin architecture, HTTP layer, auth, and more)
@@ -58,18 +58,46 @@ Accept: text/html
5858
5. Browser receives HTML page
5959
```
6060

61+
### Template Naming Convention
62+
63+
**Recommended Pattern (Explicit):**
64+
- **`list.html`** - For collection views (recommended - clean, no conditional logic)
65+
- **`view.html`** - For document views (recommended - clean, no conditional logic)
66+
- **`index.html`** - Optional fallback (use when list/view can share template logic)
67+
68+
**Why explicit templates?** Templates are cleaner without conditional logic checking request type. File names clearly indicate purpose.
69+
6170
### Template Resolution Algorithm
6271

63-
**Full Page Requests** (no HTMX headers):
72+
**Collection Requests** (e.g., `/mydb/products`):
6473
```
6574
GET /mydb/products?page=1
6675
Accept: text/html
6776
6877
Search order:
69-
1. templates/mydb/products/index.html ✓ (if exists)
70-
2. templates/mydb/index.html (parent fallback)
71-
3. templates/index.html (global fallback)
72-
4. No template → return JSON (the API remains unchanged)
78+
1. templates/mydb/products/list.html (recommended - explicit collection view)
79+
2. templates/mydb/products/index.html (optional fallback)
80+
3. templates/mydb/list.html (parent fallback)
81+
4. templates/mydb/index.html (parent fallback)
82+
5. templates/list.html (global collection template)
83+
6. templates/index.html (global fallback)
84+
7. No template → return JSON (the API remains unchanged)
85+
```
86+
87+
**Document Requests** (e.g., `/mydb/products/123`):
88+
```
89+
GET /mydb/products/123
90+
Accept: text/html
91+
92+
Search order:
93+
1. templates/mydb/products/123/view.html (document-specific override)
94+
2. templates/mydb/products/view.html (recommended - explicit document view)
95+
3. templates/mydb/products/index.html (optional fallback)
96+
4. templates/mydb/view.html (parent fallback)
97+
5. templates/mydb/index.html (parent fallback)
98+
6. templates/view.html (global document template)
99+
7. templates/index.html (global fallback)
100+
8. No template → return JSON (the API remains unchanged)
73101
```
74102

75103
**HTMX Fragment Requests** (with HX-Target header):
@@ -102,22 +130,28 @@ Organize templates by resource path to match your API structure:
102130
```
103131
templates/
104132
├── layout.html # Your main layout
105-
├── index.html # Root resource template (e.g., databases list)
133+
├── list.html # Root resource template (e.g., databases list)
106134
├── error.html # Global error page
107135
├── _fragments/ # HTMX-routable fragments (optional)
108136
│ └── my-fragment.html
109137
└── mydb/ # Database-specific templates
110-
├── index.html # Collections list for 'mydb'
138+
├── list.html # Collections list for 'mydb' (recommended)
139+
├── view.html # Database detail view (if needed)
140+
├── index.html # Optional fallback for both
111141
└── products/
112-
└── index.html # Documents list for 'products' collection
142+
├── list.html # Product collection view (recommended)
143+
├── view.html # Single product detail view (recommended)
144+
└── index.html # Optional fallback for both
113145
```
114146

115147
**You can organize templates however you prefer** - this is just a recommended pattern that mirrors your API paths.
116148

117149
### Naming Conventions
118150

151+
- **`list.html`**: Collection view template (recommended - explicit, no conditional logic)
152+
- **`view.html`**: Document view template (recommended - explicit, no conditional logic)
153+
- **`index.html`**: Optional unified fallback (when list/view share logic, or for simple cases)
119154
- **`_fragments/`**: HTMX-routable fragments (underscore prefix indicates not directly accessible via URL)
120-
- **`index.html`**: Default template for a resource path
121155
- **`layout.html`**: Base layout template using Pebble inheritance
122156
- **Path-based**: Directory structure mirrors your API URLs
123157

docs/TUTORIAL_PRODUCT_CATALOG.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,12 @@ The `documents` variable contains all products from MongoDB.
131131

132132
### Template Naming Convention
133133

134-
Facet uses action-aware resolution:
134+
Facet uses explicit action-aware resolution:
135135

136-
- **Collection requests** → looks for `list.html` first, then `index.html`
137-
- **Document requests** → looks for `view.html` first, then `index.html`
136+
- **Collection requests** → looks for `list.html` first, then `index.html` (optional fallback)
137+
- **Document requests** → looks for `view.html` first, then `index.html` (optional fallback)
138138

139-
This is why our file is named `list.html` not `index.html`.
139+
**Recommended:** Use explicit templates (`list.html` and `view.html`) for cleaner code without conditional logic. This example uses `list.html` for the collection view and `view.html` for the document view, keeping each template focused and simple.
140140

141141
---
142142

@@ -253,10 +253,12 @@ When requesting `/shop/products/65abc123...`:
253253

254254
1. `templates/shop/products/65abc123.../view.html` ❌ (document-specific, doesn't exist)
255255
2. `templates/shop/products/view.html`**FOUND!**
256-
3. `templates/shop/products/index.html` (fallback if view.html missing)
257-
4. `templates/shop/index.html` (parent directory fallback)
258-
5. `templates/index.html` (root fallback)
259-
6. **No template found** → return JSON (API unchanged)
256+
3. `templates/shop/products/index.html` (optional fallback if view.html missing)
257+
4. `templates/shop/view.html` (parent-level document template)
258+
5. `templates/shop/index.html` (parent directory fallback)
259+
6. `templates/view.html` (global document template)
260+
7. `templates/index.html` (root fallback)
261+
8. **No template found** → return JSON (API unchanged)
260262

261263
This is **hierarchical resolution** - walks up the tree until it finds a template.
262264

@@ -701,7 +703,7 @@ document.getElementById('productEditForm').addEventListener('submit', async func
701703

702704
#### Create Product Fragment
703705

704-
The create flow works similarly. Open [templates/shop/products/index.html](../examples/product-catalog/templates/shop/products/index.html) (lines 18-26):
706+
The create flow works similarly. Open [templates/shop/products/list.html](../examples/product-catalog/templates/shop/products/list.html) (lines 18-26):
705707

706708
```html
707709
<button hx-get="{{ path }}"
@@ -787,7 +789,7 @@ async function deleteProduct() {
787789
### Key Files
788790

789791
- [templates/shop/products/view.html](../examples/product-catalog/templates/shop/products/view.html) - Product detail page with HTMX buttons
790-
- [templates/shop/products/index.html](../examples/product-catalog/templates/shop/products/index.html) - Product list with "Add Product" button
792+
- [templates/shop/products/list.html](../examples/product-catalog/templates/shop/products/list.html) - Product list with "Add Product" button
791793
- [templates/_fragments/product-form.html](../examples/product-catalog/templates/_fragments/product-form.html) - Edit form (HTMX fragment)
792794
- [templates/_fragments/product-new.html](../examples/product-catalog/templates/_fragments/product-new.html) - Create form (HTMX fragment)
793795

examples/product-catalog/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ templates/
109109
├── layout.html # Base layout with navigation
110110
├── shop/
111111
│ └── products/
112-
│ ├── index.html # Product list page
113-
│ └── view.html # Product detail page
112+
│ ├── list.html # Product list page (collection view)
113+
│ └── view.html # Product detail page (document view)
114114
└── _fragments/
115115
└── product-list.html # Reusable product list (for HTMX)
116116
```
@@ -130,8 +130,8 @@ When you request different URLs, Facet resolves templates hierarchically:
130130

131131
| Request URL | Template Resolved | Fallback Order |
132132
|-------------|-------------------|----------------|
133-
| `GET /shop/products` | `shop/products/index.html` |`shop/index.html``index.html` |
134-
| `GET /shop/products/65abc...` | `shop/products/view.html` |`shop/products/index.html` → ... |
133+
| `GET /shop/products` | `shop/products/list.html` |`shop/products/index.html``shop/list.html``shop/index.html``list.html``index.html` |
134+
| `GET /shop/products/65abc...` | `shop/products/view.html` |`shop/products/index.html``shop/view.html`... |
135135
| `GET /shop/products` (HTMX, target: `#product-list`) | `_fragments/product-list.html` | No fallback (strict mode) |
136136

137137
## Key Features Explained

examples/product-catalog/templates/shop/products/index.html renamed to examples/product-catalog/templates/shop/products/list.html

File renamed without changes.

0 commit comments

Comments
 (0)