odac 1.4.4 → 1.4.5
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.
- package/.agent/rules/memory.md +5 -1
- package/CHANGELOG.md +28 -0
- package/README.md +2 -1
- package/client/odac.js +121 -2
- package/docs/ai/skills/backend/views.md +34 -9
- package/docs/ai/skills/frontend/navigation.md +45 -1
- package/docs/backend/07-views/03-template-syntax.md +48 -14
- package/docs/backend/07-views/03-variables.md +22 -7
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +22 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +51 -0
- package/package.json +1 -1
- package/template/controller/page/about.js +3 -3
- package/template/controller/page/index.js +2 -2
- package/template/public/assets/js/app.js +38 -54
- package/template/skeleton/main.html +4 -4
- package/template/view/content/about.html +64 -60
- package/template/view/content/home.html +148 -175
- package/template/view/css/app.css +46 -0
- package/template/view/footer/main.html +10 -9
- package/template/view/header/main.html +34 -11
- package/test/Client/load.test.js +306 -0
- package/template/public/assets/css/style.css +0 -1835
package/.agent/rules/memory.md
CHANGED
|
@@ -38,6 +38,7 @@ trigger: always_on
|
|
|
38
38
|
|
|
39
39
|
## Documentation Standards
|
|
40
40
|
- **AI Skill Front Matter:** Every file under `docs/ai/skills/**/*.md` must start with YAML front matter containing `name`, `description`, and `metadata.tags`; values must be specific to that document's topic (never copied from generic examples).
|
|
41
|
+
- **Template Syntax Documentation:** `{{ }}` and `<odac var>` are equal-status syntaxes, NOT legacy vs modern. `{{ }}` is preferred inside HTML attributes and inline text; `<odac var>` is preferred for standalone block output. Never label `{{ }}` as "legacy" or "backward compatibility" in docs.
|
|
41
42
|
|
|
42
43
|
## Testing & Validation
|
|
43
44
|
- **Mandatory Test Coverage:** Every new feature, method, or significant logic change MUST be accompanied by a corresponding unit or integration test.
|
|
@@ -53,4 +54,7 @@ trigger: always_on
|
|
|
53
54
|
- **Automatic JSON Parsing:** The `#ajax` method (and by extension `odac.get`) must automatically parse the response if the `Content-Type` header contains `application/json`, even if `dataType` is not explicitly set to `json`.
|
|
54
55
|
|
|
55
56
|
## Security Logic & Authentication
|
|
56
|
-
- **Enterprise Token Rotation:** The `Auth.js` system utilizes a non-blocking refresh token rotation mechanism for cookies (`odac_x`/`odac_y`). To prevent race conditions during concurrent requests in high-throughput SPAs, rotated tokens are **not** immediately deleted. Instead, their `active` timestamp is set to naturally expire in 60 seconds (Grace Period), and their `date` timestamp is set to the Unix Epoch (`new Date(0)`) as an identifier mark. Never delete rotated tokens immediately.
|
|
57
|
+
- **Enterprise Token Rotation:** The `Auth.js` system utilizes a non-blocking refresh token rotation mechanism for cookies (`odac_x`/`odac_y`). To prevent race conditions during concurrent requests in high-throughput SPAs, rotated tokens are **not** immediately deleted. Instead, their `active` timestamp is set to naturally expire in 60 seconds (Grace Period), and their `date` timestamp is set to the Unix Epoch (`new Date(0)`) as an identifier mark. Never delete rotated tokens immediately.
|
|
58
|
+
|
|
59
|
+
## View Engine & SSR
|
|
60
|
+
- **Auto-Navigation Injection:** The ODAC View engine (`src/View.js`) automatically parses skeleton HTML files and dynamically injects `data-odac-navigate="content"` into the element immediately wrapping `{{ CONTENT }}`. Do NOT manually add this attribute to HTML templates or assume it is missing based on static analysis, as it is handled seamlessly at runtime via SSR.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
### security
|
|
2
|
+
|
|
3
|
+
- **template:** add noopener to external footer links to mitigate reverse tabnabbing
|
|
4
|
+
- **template:** add rel="noopener" to all external links to prevent tabnabbing without losing referer analytics
|
|
5
|
+
|
|
6
|
+
### ✨ What's New
|
|
7
|
+
|
|
8
|
+
- **client:** add native View Transition API support via odac-transition attribute
|
|
9
|
+
- Implement a complete UI redesign using Tailwind CSS, introduce new components, and update branding to ODAC.
|
|
10
|
+
|
|
11
|
+
### 📚 Documentation
|
|
12
|
+
|
|
13
|
+
- Add documentation for auto-navigation injection in the View Engine and SSR.
|
|
14
|
+
- **template:** correct casing for Odac object api references in comments
|
|
15
|
+
- **views:** clarify template syntax as equal-status with usage guidelines
|
|
16
|
+
|
|
17
|
+
### 🛠️ Fixes & Improvements
|
|
18
|
+
|
|
19
|
+
- **client:** clear stale view transition names on aborted navigation promises
|
|
20
|
+
- **client:** decode HTML entities in document title to prevent XSS vulnerabilities
|
|
21
|
+
- **template:** sync active nav state properly across desktop and mobile menus
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
Powered by [⚡ ODAC](https://odac.run)
|
|
28
|
+
|
|
1
29
|
### doc
|
|
2
30
|
|
|
3
31
|
- Introduce WebSocket routing and controllers, update request handling, and refactor language and validator modules to use async operations.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 🚀 **Developer Friendly:** Simple setup and intuitive API design let you start building immediately.
|
|
9
9
|
* 🎨 **Built-in Tailwind CSS:** Zero-config integration with Tailwind CSS v4. Automatic compilation and optimization out of the box.
|
|
10
10
|
* 🔗 **Powerful Routing:** Create clean, custom URLs and manage infinite pages with a flexible routing system.
|
|
11
|
-
* ✨ **Seamless SPA Experience:** Automatic AJAX handling for forms and page transitions
|
|
11
|
+
* ✨ **Seamless SPA Experience:** Automatic AJAX handling for forms and page transitions with native **View Transition API** support. Add a single HTML attribute for smooth, browser-native animations — no client-side code required.
|
|
12
12
|
* 🛡️ **Built-in Security:** Enterprise-grade security out of the box. Includes secure default headers and a **Multi-tab Safe, Single-Use CSRF Protection (Nonce)**. Tokens self-replenish in the background, ensuring maximum defense without ever interrupting the user experience.
|
|
13
13
|
* 🔐 **Authentication:** Ready-to-use session management with enterprise-grade **Refresh Token Rotation**, secure password hashing, and authentication helpers.
|
|
14
14
|
* 🗄️ **Database Agnostic:** Integrated support for major databases (PostgreSQL, MySQL, SQLite) and Redis via Knex.js.
|
|
@@ -72,6 +72,7 @@ project/
|
|
|
72
72
|
├── middleware/ # Route middlewares
|
|
73
73
|
├── public/ # Static assets
|
|
74
74
|
├── route/ # Route definitions
|
|
75
|
+
├── schema/ # Database schemas (auto-migrate)
|
|
75
76
|
├── view/ # HTML templates
|
|
76
77
|
├── .env # Environment variables
|
|
77
78
|
└── odac.json # App configuration
|
package/client/odac.js
CHANGED
|
@@ -195,6 +195,33 @@ if (typeof window !== 'undefined') {
|
|
|
195
195
|
xhr.send(data)
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Assigns `view-transition-name` CSS properties to all elements carrying
|
|
200
|
+
* the `odac-transition` attribute, enabling the browser's native View
|
|
201
|
+
* Transition API to animate them individually during navigation.
|
|
202
|
+
*
|
|
203
|
+
* @returns {Element[]} The list of elements that received transition names.
|
|
204
|
+
*/
|
|
205
|
+
#applyTransitionNames() {
|
|
206
|
+
const elements = document.querySelectorAll('[odac-transition]')
|
|
207
|
+
elements.forEach(el => {
|
|
208
|
+
el.style.viewTransitionName = el.getAttribute('odac-transition')
|
|
209
|
+
})
|
|
210
|
+
return Array.from(elements)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Removes `view-transition-name` from previously tagged elements to
|
|
215
|
+
* prevent stale names from conflicting with future transitions.
|
|
216
|
+
*
|
|
217
|
+
* @param {Element[]} elements - Elements to clean up.
|
|
218
|
+
*/
|
|
219
|
+
#clearTransitionNames(elements) {
|
|
220
|
+
elements.forEach(el => {
|
|
221
|
+
el.style.viewTransitionName = ''
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
198
225
|
#fade(element, type, duration = 400, callback) {
|
|
199
226
|
const isIn = type === 'in'
|
|
200
227
|
const startOpacity = isIn ? 0 : 1
|
|
@@ -720,6 +747,20 @@ if (typeof window !== 'undefined') {
|
|
|
720
747
|
.replace(/\n/g, '<br>')
|
|
721
748
|
}
|
|
722
749
|
|
|
750
|
+
/**
|
|
751
|
+
* Decodes HTML entities back to their plain-text representation.
|
|
752
|
+
* Uses a textarea element as a safe decoder — no script execution risk.
|
|
753
|
+
*
|
|
754
|
+
* @param {string} str - HTML-encoded string (e.g. "&" → "&").
|
|
755
|
+
* @returns {string} Decoded plain-text string.
|
|
756
|
+
*/
|
|
757
|
+
#decodeHtmlEntities(str) {
|
|
758
|
+
if (typeof str !== 'string') return str
|
|
759
|
+
const textarea = document.createElement('textarea')
|
|
760
|
+
textarea.innerHTML = str
|
|
761
|
+
return textarea.value
|
|
762
|
+
}
|
|
763
|
+
|
|
723
764
|
load(url, callback, push = true) {
|
|
724
765
|
if (this.#isNavigating) return false
|
|
725
766
|
|
|
@@ -739,6 +780,84 @@ if (typeof window !== 'undefined') {
|
|
|
739
780
|
if (element) elementsToUpdate.push({key, element})
|
|
740
781
|
})
|
|
741
782
|
|
|
783
|
+
const useViewTransition = document.startViewTransition && document.querySelectorAll('[odac-transition]').length > 0
|
|
784
|
+
|
|
785
|
+
if (useViewTransition) {
|
|
786
|
+
this.#loadWithViewTransition(url, callback, push, currentUrl, currentSkeleton, elementsToUpdate)
|
|
787
|
+
} else {
|
|
788
|
+
this.#loadWithFade(url, callback, push, currentUrl, currentSkeleton, elementsToUpdate)
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Performs page navigation using the browser's native View Transition API.
|
|
794
|
+
* Elements with `odac-transition` attributes receive individual transition
|
|
795
|
+
* names, enabling per-element morphing animations orchestrated by the browser.
|
|
796
|
+
* Non-transition elements still update their content within the transition frame.
|
|
797
|
+
*/
|
|
798
|
+
#loadWithViewTransition(url, callback, push, currentUrl, currentSkeleton, elementsToUpdate) {
|
|
799
|
+
const oldTransitionElements = this.#applyTransitionNames()
|
|
800
|
+
|
|
801
|
+
this.#ajax({
|
|
802
|
+
url,
|
|
803
|
+
type: 'GET',
|
|
804
|
+
headers: {
|
|
805
|
+
'X-Odac': 'ajaxload',
|
|
806
|
+
'X-Odac-Load': Object.keys(this.#loader.elements).join(','),
|
|
807
|
+
'X-Odac-Skeleton': currentSkeleton || ''
|
|
808
|
+
},
|
|
809
|
+
dataType: 'json',
|
|
810
|
+
success: (data, status, xhr) => {
|
|
811
|
+
const finalUrl = xhr.responseURL || url
|
|
812
|
+
if (data.skeletonChanged) {
|
|
813
|
+
this.#clearTransitionNames(oldTransitionElements)
|
|
814
|
+
window.location.href = finalUrl
|
|
815
|
+
return
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const transition = document.startViewTransition(() => {
|
|
819
|
+
if (finalUrl !== currentUrl && push) window.history.pushState(null, document.title, finalUrl)
|
|
820
|
+
|
|
821
|
+
const newPage = xhr.getResponseHeader('X-Odac-Page')
|
|
822
|
+
if (newPage !== null) {
|
|
823
|
+
this.#page = newPage
|
|
824
|
+
document.documentElement.dataset.odacPage = newPage
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (data.data) this.#data = data.data
|
|
828
|
+
if (data.title) document.title = this.#decodeHtmlEntities(data.title)
|
|
829
|
+
|
|
830
|
+
elementsToUpdate.forEach(({key, element}) => {
|
|
831
|
+
if (data.output && data.output[key] !== undefined) element.innerHTML = data.output[key]
|
|
832
|
+
})
|
|
833
|
+
|
|
834
|
+
this.#applyTransitionNames()
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
transition.finished
|
|
838
|
+
.then(() => {
|
|
839
|
+
this.#clearTransitionNames(document.querySelectorAll('[odac-transition]'))
|
|
840
|
+
this.#handleLoadComplete(data, callback)
|
|
841
|
+
})
|
|
842
|
+
.catch(() => {
|
|
843
|
+
this.#clearTransitionNames(document.querySelectorAll('[odac-transition]'))
|
|
844
|
+
this.#isNavigating = false
|
|
845
|
+
})
|
|
846
|
+
},
|
|
847
|
+
error: () => {
|
|
848
|
+
this.#clearTransitionNames(oldTransitionElements)
|
|
849
|
+
this.#isNavigating = false
|
|
850
|
+
window.location.replace(url)
|
|
851
|
+
}
|
|
852
|
+
})
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Performs page navigation using the legacy fade-in/fade-out animation.
|
|
857
|
+
* This is the fallback path when the View Transition API is unavailable
|
|
858
|
+
* or no `odac-transition` elements exist in the DOM.
|
|
859
|
+
*/
|
|
860
|
+
#loadWithFade(url, callback, push, currentUrl, currentSkeleton, elementsToUpdate) {
|
|
742
861
|
let ajaxData = null,
|
|
743
862
|
ajaxXhr = null,
|
|
744
863
|
fadeOutComplete = false,
|
|
@@ -761,7 +880,7 @@ if (typeof window !== 'undefined') {
|
|
|
761
880
|
}
|
|
762
881
|
|
|
763
882
|
if (ajaxData.data) this.#data = ajaxData.data
|
|
764
|
-
if (ajaxData.title) document.title = ajaxData.title
|
|
883
|
+
if (ajaxData.title) document.title = this.#decodeHtmlEntities(ajaxData.title)
|
|
765
884
|
|
|
766
885
|
if (elementsToUpdate.length === 0) {
|
|
767
886
|
this.#handleLoadComplete(ajaxData, callback)
|
|
@@ -794,7 +913,7 @@ if (typeof window !== 'undefined') {
|
|
|
794
913
|
}
|
|
795
914
|
|
|
796
915
|
this.#ajax({
|
|
797
|
-
url
|
|
916
|
+
url,
|
|
798
917
|
type: 'GET',
|
|
799
918
|
headers: {
|
|
800
919
|
'X-Odac': 'ajaxload',
|
|
@@ -14,12 +14,18 @@ Views in ODAC are logic-light but powerful. They support automatic XSS protectio
|
|
|
14
14
|
|
|
15
15
|
## Core Rules
|
|
16
16
|
1. **Skeleton Architecture**: Use `Odac.View.skeleton('name')` to wrap content in a layout.
|
|
17
|
-
2. **Data Binding**:
|
|
18
|
-
-
|
|
19
|
-
- `{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
2. **Data Binding — Two Equivalent Syntaxes**:
|
|
18
|
+
- `<odac var="key" />`: Tag-based output (HTML-escaped, XSS-safe).
|
|
19
|
+
- `{{ key }}`: Inline/interpolation output (HTML-escaped, XSS-safe). Identical behavior to `<odac var>`.
|
|
20
|
+
- `<odac var="key" raw />` or `{!! key !!}`: Raw output (Use with extreme caution).
|
|
21
|
+
3. **Choosing the Right Syntax**:
|
|
22
|
+
- **Inside HTML attributes** (`src`, `alt`, `href`, `class`, `value`, etc.) → Always prefer `{{ }}`. It reads naturally and keeps markup clean.
|
|
23
|
+
- **Inline within text or mixed HTML** → Prefer `{{ }}` for short interpolations.
|
|
24
|
+
- **Standalone block output** (the variable is the only content of an element) → Prefer `<odac var="" />` for structural clarity and IDE support.
|
|
25
|
+
- Both syntaxes compile to the same engine output. The choice is about readability, not functionality.
|
|
26
|
+
4. **Conditionals**: Use `<odac:if condition="VAR"> ... </odac:if>`.
|
|
27
|
+
5. **Looping**: Use `<odac:for in="ARRAY" value="ITEM"> ... </odac:for>` or the performance-optimized `[[odac_for ...]]`.
|
|
28
|
+
6. **Server-Side JS**: Use `<script:odac>` for complex calculations during rendering.
|
|
23
29
|
|
|
24
30
|
## Reference Patterns
|
|
25
31
|
|
|
@@ -35,8 +41,17 @@ Odac.View.set({
|
|
|
35
41
|
|
|
36
42
|
### 2. Template Syntax Reference
|
|
37
43
|
```html
|
|
38
|
-
<!--
|
|
39
|
-
<h1
|
|
44
|
+
<!-- Standalone block output — prefer <odac var> -->
|
|
45
|
+
<h1><odac var="title" /></h1>
|
|
46
|
+
|
|
47
|
+
<!-- Inside attributes — prefer {{ }} -->
|
|
48
|
+
<img src="{{ product.image }}" alt="{{ product.name }}">
|
|
49
|
+
<a href="/user/{{ user.id }}" class="btn {{ isActive ? 'active' : '' }}">Profile</a>
|
|
50
|
+
<input type="text" value="{{ query }}">
|
|
51
|
+
|
|
52
|
+
<!-- Inline text interpolation — prefer {{ }} -->
|
|
53
|
+
<p>Welcome, {{ user.name }}. You have {{ notifications }} new messages.</p>
|
|
54
|
+
<span>${{ product.price }}</span>
|
|
40
55
|
|
|
41
56
|
<!-- Conditional -->
|
|
42
57
|
<odac:if condition="stats.users > 100">
|
|
@@ -62,7 +77,17 @@ Perfect for calculations that shouldn't clutter the controller but are too compl
|
|
|
62
77
|
<p>Tax: ${{ tax }}</p>
|
|
63
78
|
```
|
|
64
79
|
|
|
80
|
+
## Syntax Selection Guide
|
|
81
|
+
|
|
82
|
+
| Context | Preferred Syntax | Example |
|
|
83
|
+
|---------|-----------------|---------|
|
|
84
|
+
| HTML attributes (`src`, `href`, `alt`, `class`, `value`) | `{{ }}` | `<img src="{{ photo.url }}" alt="{{ photo.caption }}">` |
|
|
85
|
+
| Inline text within elements | `{{ }}` | `<p>Hello, {{ user.name }}</p>` |
|
|
86
|
+
| Standalone element content | `<odac var />` | `<h1><odac var="title" /></h1>` |
|
|
87
|
+
| Raw HTML output (trusted only) | `<odac var raw />` or `{!! !!}` | `<div><odac var="content" raw /></div>` |
|
|
88
|
+
|
|
65
89
|
## Security Best Practices
|
|
66
|
-
- **
|
|
90
|
+
- **Both `{{ }}` and `<odac var>` are XSS-safe**: Both apply HTML escaping by default. Use either with confidence.
|
|
91
|
+
- **Raw output requires trust**: Only use `raw` / `{!! !!}` with content you fully control. Never with user input.
|
|
67
92
|
- **Limit `<script:odac>`**: Do not perform database queries or API calls inside views; keep them in the controller.
|
|
68
93
|
- **Partial Awareness**: Use `<odac:include view="path.to.view" />` for reusable components.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: frontend-navigation-spa-skill
|
|
3
3
|
description: Single-page navigation patterns in odac.js for smooth transitions, route control, and lifecycle-safe execution.
|
|
4
4
|
metadata:
|
|
5
|
-
tags: frontend, navigation, spa, ajax-navigation, page-lifecycle, transitions
|
|
5
|
+
tags: frontend, navigation, spa, ajax-navigation, page-lifecycle, transitions, view-transitions
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Frontend Navigation & SPA Skill
|
|
@@ -13,6 +13,7 @@ Smooth transitions and single-page application behavior using `odac.js`.
|
|
|
13
13
|
1. **Selection**: Enable via `Odac.action({ navigate: 'main' })`.
|
|
14
14
|
2. **Exclusion**: Use `data-navigate="false"` or `.no-navigate` class for full reloads.
|
|
15
15
|
3. **Lifecycle**: Use `load` and `page` events to run code after navigation.
|
|
16
|
+
4. **View Transitions**: Add `odac-transition` attribute to elements for native browser View Transition API animations. Zero-config — no JS setup required.
|
|
16
17
|
|
|
17
18
|
## Patterns
|
|
18
19
|
```javascript
|
|
@@ -25,3 +26,46 @@ Odac.action({
|
|
|
25
26
|
}
|
|
26
27
|
});
|
|
27
28
|
```
|
|
29
|
+
|
|
30
|
+
## View Transitions (Native Browser API)
|
|
31
|
+
|
|
32
|
+
Elements with the `odac-transition` attribute automatically use the browser's View Transition API instead of the legacy fade animation. The attribute value becomes the `view-transition-name`, enabling per-element morphing between pages.
|
|
33
|
+
|
|
34
|
+
### HTML Usage
|
|
35
|
+
```html
|
|
36
|
+
<header odac-transition="header">Site Header</header>
|
|
37
|
+
<nav odac-transition="sidebar">Navigation</nav>
|
|
38
|
+
<main>Content updated by AJAX loader</main>
|
|
39
|
+
<img odac-transition="hero" src="/hero.jpg" />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Behavior
|
|
43
|
+
- When `odac-transition` elements exist and the browser supports View Transition API → native transition is used.
|
|
44
|
+
- When no `odac-transition` elements exist or the API is unsupported → legacy fade fallback runs automatically.
|
|
45
|
+
- Transition names are applied before the snapshot and cleaned up after the transition completes.
|
|
46
|
+
- The attribute value must be unique per page (browser requirement for `view-transition-name`).
|
|
47
|
+
|
|
48
|
+
### CSS Customization
|
|
49
|
+
```css
|
|
50
|
+
/* Target a specific element's transition */
|
|
51
|
+
::view-transition-old(hero) {
|
|
52
|
+
animation: fade-out 0.3s ease;
|
|
53
|
+
}
|
|
54
|
+
::view-transition-new(hero) {
|
|
55
|
+
animation: fade-in 0.3s ease;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Slide sidebar from left */
|
|
59
|
+
::view-transition-old(sidebar) {
|
|
60
|
+
animation: slide-out-left 0.25s ease;
|
|
61
|
+
}
|
|
62
|
+
::view-transition-new(sidebar) {
|
|
63
|
+
animation: slide-in-left 0.25s ease;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Rules
|
|
68
|
+
1. Each `odac-transition` value must be unique within the page.
|
|
69
|
+
2. Elements persist across navigations (e.g., shared header) for smooth morphing.
|
|
70
|
+
3. No JavaScript configuration needed — attribute-only setup.
|
|
71
|
+
4. Falls back to fade animation gracefully on unsupported browsers.
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
## 🔧 Template Syntax Overview
|
|
2
2
|
|
|
3
|
-
Odac uses a powerful template engine to create dynamic content in view files. The engine provides
|
|
3
|
+
Odac uses a powerful template engine to create dynamic content in view files. The engine provides two equivalent syntaxes for displaying variables, plus dedicated tags for conditionals, loops, translations, and more.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Two Syntaxes, One Engine
|
|
6
|
+
|
|
7
|
+
ODAC offers two ways to output variables. Both are HTML-escaped (XSS-safe) and compile to the same engine code. The choice is about readability, not functionality.
|
|
8
|
+
|
|
9
|
+
| Syntax | Best For | Example |
|
|
10
|
+
|--------|----------|---------|
|
|
11
|
+
| `<odac var="x" />` | Standalone block output where the variable is the main content | `<h1><odac var="title" /></h1>` |
|
|
12
|
+
| `{{ x }}` | Attributes, inline text, and mixed HTML where tag syntax would be verbose | `<img src="{{ photo.url }}" alt="{{ photo.caption }}">` |
|
|
13
|
+
|
|
14
|
+
> **Guideline:** Use `{{ }}` inside HTML attributes (`src`, `href`, `alt`, `class`, `value`, etc.) and for inline text interpolation. Use `<odac var>` for standalone element content. Both are equally supported and recommended.
|
|
6
15
|
|
|
7
16
|
### Quick Reference
|
|
8
17
|
|
|
@@ -13,10 +22,17 @@ This page provides a quick overview of all available template features. For deta
|
|
|
13
22
|
Display data passed from controllers using `Odac.set()`:
|
|
14
23
|
|
|
15
24
|
```html
|
|
16
|
-
<!--
|
|
17
|
-
<odac var="username"
|
|
25
|
+
<!-- Standalone block output — prefer <odac var> -->
|
|
26
|
+
<h1><odac var="username" /></h1>
|
|
27
|
+
|
|
28
|
+
<!-- Inside attributes — prefer {{ }} -->
|
|
29
|
+
<img src="{{ product.image }}" alt="{{ product.name }}">
|
|
30
|
+
<a href="/user/{{ user.id }}">Profile</a>
|
|
18
31
|
|
|
19
|
-
<!--
|
|
32
|
+
<!-- Inline text — prefer {{ }} -->
|
|
33
|
+
<p>Welcome, {{ user.name }}. You have {{ count }} items.</p>
|
|
34
|
+
|
|
35
|
+
<!-- Raw HTML output (trusted content only) -->
|
|
20
36
|
<odac var="htmlContent" raw />
|
|
21
37
|
|
|
22
38
|
<!-- String literals -->
|
|
@@ -163,8 +179,10 @@ Full access to the Odac object in templates:
|
|
|
163
179
|
|
|
164
180
|
| Feature | Syntax | Documentation |
|
|
165
181
|
|---------|--------|---------------|
|
|
166
|
-
| Variable (
|
|
167
|
-
|
|
|
182
|
+
| Variable (standalone) | `<odac var="x" />` | [Variables](./03-variables.md) |
|
|
183
|
+
| Variable (inline/attribute) | `{{ x }}` | [Variables](./03-variables.md) |
|
|
184
|
+
| Raw HTML (tag) | `<odac var="x" raw />` | [Variables](./03-variables.md) |
|
|
185
|
+
| Raw HTML (inline) | `{!! x !!}` | [Variables](./03-variables.md) |
|
|
168
186
|
| String | `<odac>text</odac>` | [Variables](./03-variables.md) |
|
|
169
187
|
| Query Parameter | `<odac get="key" />` | [Request Data](./04-request-data.md) |
|
|
170
188
|
| Translation | `<odac translate>key</odac>` | [Translations](./07-translations.md) |
|
|
@@ -180,18 +198,34 @@ Full access to the Odac object in templates:
|
|
|
180
198
|
| Comment | `<!--odac ... odac-->` | [Comments](./09-comments.md) |
|
|
181
199
|
| Image | `<odac:img src="..." />` | [Image Optimization](./11-image-optimization.md) |
|
|
182
200
|
|
|
183
|
-
###
|
|
201
|
+
### Syntax Selection Guide
|
|
202
|
+
|
|
203
|
+
Choose the right syntax based on context for clean, readable templates:
|
|
184
204
|
|
|
185
205
|
```html
|
|
186
|
-
<!--
|
|
187
|
-
{{
|
|
206
|
+
<!-- ✅ Attributes — use {{ }} -->
|
|
207
|
+
<img src="{{ product.image }}" alt="{{ product.name }}">
|
|
208
|
+
<a href="/products/{{ product.id }}" class="card {{ isActive ? 'active' : '' }}">
|
|
209
|
+
<input type="text" name="search" value="{{ query }}">
|
|
210
|
+
|
|
211
|
+
<!-- ✅ Inline text — use {{ }} -->
|
|
212
|
+
<p>Hello, {{ user.name }}. You have {{ count }} notifications.</p>
|
|
213
|
+
<span>${{ product.price }}</span>
|
|
214
|
+
|
|
215
|
+
<!-- ✅ Standalone block content — use <odac var> -->
|
|
216
|
+
<h1><odac var="pageTitle" /></h1>
|
|
217
|
+
<td><odac var="user.email" /></td>
|
|
218
|
+
|
|
219
|
+
<!-- ✅ Raw output — either form works -->
|
|
220
|
+
<div><odac var="richContent" raw /></div>
|
|
221
|
+
<div>{!! richContent !!}</div>
|
|
222
|
+
```
|
|
188
223
|
|
|
189
|
-
|
|
190
|
-
{!! htmlContent !!}
|
|
224
|
+
### Legacy Comment Syntax
|
|
191
225
|
|
|
192
|
-
|
|
226
|
+
```html
|
|
193
227
|
{{-- This is a comment --}}
|
|
194
228
|
```
|
|
195
229
|
|
|
196
|
-
**Note:**
|
|
230
|
+
**Note:** For new projects, prefer the `<!--odac ... odac-->` comment syntax for consistency.
|
|
197
231
|
|
|
@@ -311,18 +311,33 @@ module.exports = async function(Odac) {
|
|
|
311
311
|
</odac:if>
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
-
###
|
|
314
|
+
### Inline Syntax (`{{ }}` and `{!! !!}`)
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
ODAC provides an inline interpolation syntax that is equivalent to the `<odac var>` tag. Both compile to the same engine output and are equally supported.
|
|
317
|
+
|
|
318
|
+
**Use `{{ }}` when the variable appears inside HTML attributes or inline within text:**
|
|
317
319
|
|
|
318
320
|
```html
|
|
319
|
-
<!--
|
|
320
|
-
{{
|
|
321
|
-
{{ user.
|
|
321
|
+
<!-- Inside attributes — {{ }} keeps markup clean -->
|
|
322
|
+
<img src="{{ product.image }}" alt="{{ product.name }}">
|
|
323
|
+
<a href="/user/{{ user.id }}" class="btn {{ isActive ? 'active' : '' }}">Profile</a>
|
|
324
|
+
<input type="text" name="q" value="{{ searchQuery }}">
|
|
325
|
+
|
|
326
|
+
<!-- Inline text interpolation -->
|
|
327
|
+
<p>Welcome, {{ user.name }}. You have {{ notifications }} new messages.</p>
|
|
328
|
+
<span>${{ product.price }}</span>
|
|
322
329
|
|
|
323
|
-
<!-- Raw
|
|
330
|
+
<!-- Raw inline output (trusted content only) -->
|
|
324
331
|
{!! htmlContent !!}
|
|
325
332
|
{!! user.bio !!}
|
|
326
333
|
```
|
|
327
334
|
|
|
328
|
-
**
|
|
335
|
+
**Use `<odac var>` when the variable is the standalone content of an element:**
|
|
336
|
+
|
|
337
|
+
```html
|
|
338
|
+
<h1><odac var="pageTitle" /></h1>
|
|
339
|
+
<td><odac var="user.email" /></td>
|
|
340
|
+
<p><odac var="product.description" /></p>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Both syntaxes are XSS-safe by default (HTML-escaped). The choice is purely about readability. See [Template Syntax Overview](./03-template-syntax.md) for the full selection guide.
|
|
@@ -6,6 +6,7 @@ Odac framework includes a built-in AJAX navigation system that enables smooth, s
|
|
|
6
6
|
|
|
7
7
|
- **Zero Configuration**: Works automatically with all internal links (`/`)
|
|
8
8
|
- **Smooth Transitions**: Load only specific page sections without full page reload
|
|
9
|
+
- **Native View Transitions**: Automatic browser View Transition API support via `odac-transition` attribute
|
|
9
10
|
- **History API Integration**: Browser back/forward buttons work seamlessly
|
|
10
11
|
- **Automatic Token Management**: CSRF tokens are handled automatically
|
|
11
12
|
- **Progressive Enhancement**: Falls back to normal navigation if JavaScript fails
|
|
@@ -178,6 +179,27 @@ module.exports = function (Odac) {
|
|
|
178
179
|
5. Browser URL updates via History API
|
|
179
180
|
6. Page-specific callbacks execute
|
|
180
181
|
|
|
182
|
+
### View Transition Load (with `odac-transition` elements)
|
|
183
|
+
|
|
184
|
+
When elements with `odac-transition` attribute exist on the page and the browser supports the View Transition API, ODAC uses native transitions instead of fade:
|
|
185
|
+
|
|
186
|
+
1. User clicks `<a href="/about">`
|
|
187
|
+
2. ODAC assigns `view-transition-name` to all `odac-transition` elements (old state snapshot)
|
|
188
|
+
3. AJAX request is sent (same as above)
|
|
189
|
+
4. `document.startViewTransition()` is called — browser captures old state
|
|
190
|
+
5. DOM is updated with new content inside the transition callback
|
|
191
|
+
6. New `odac-transition` elements receive their names
|
|
192
|
+
7. Browser animates between old and new snapshots
|
|
193
|
+
8. Transition names are cleaned up after completion
|
|
194
|
+
|
|
195
|
+
No configuration needed — just add the attribute to your HTML:
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<header odac-transition="header">{{ HEADER }}</header>
|
|
199
|
+
<main>{{ CONTENT }}</main>
|
|
200
|
+
<img odac-transition="hero" src="/hero.jpg" alt="Hero" />
|
|
201
|
+
```
|
|
202
|
+
|
|
181
203
|
**Key Points:**
|
|
182
204
|
- The `output` keys in the JSON response match the lowercase keys from `Odac.View.set()` in your controller
|
|
183
205
|
- These keys correspond to UPPERCASE placeholders in your skeleton (e.g., `content` → `{{ CONTENT }}`)
|
|
@@ -143,6 +143,57 @@ Odac.action({
|
|
|
143
143
|
|
|
144
144
|
## Animation & Transitions
|
|
145
145
|
|
|
146
|
+
### View Transitions (Recommended)
|
|
147
|
+
|
|
148
|
+
ODAC natively supports the browser's [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API). Add the `odac-transition` attribute to any element that should animate between page navigations. No JavaScript configuration is needed.
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<header odac-transition="header">Site Header</header>
|
|
152
|
+
<nav odac-transition="sidebar">Navigation</nav>
|
|
153
|
+
<main>Regular content (updated by AJAX loader)</main>
|
|
154
|
+
<img odac-transition="hero" src="/hero.jpg" alt="Hero" />
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**How it works:**
|
|
158
|
+
- Before navigation, ODAC assigns `view-transition-name` to each `odac-transition` element
|
|
159
|
+
- The browser captures a snapshot of the old state
|
|
160
|
+
- DOM is updated with new content
|
|
161
|
+
- New `odac-transition` elements receive their transition names
|
|
162
|
+
- The browser animates between old and new snapshots
|
|
163
|
+
|
|
164
|
+
**Rules:**
|
|
165
|
+
1. Each `odac-transition` value must be unique within the page (browser requirement)
|
|
166
|
+
2. Elements that persist across pages (e.g., shared header) will morph smoothly
|
|
167
|
+
3. If the browser doesn't support View Transition API, the legacy fade animation runs automatically
|
|
168
|
+
|
|
169
|
+
**CSS Customization:**
|
|
170
|
+
|
|
171
|
+
```css
|
|
172
|
+
/* Crossfade the hero image */
|
|
173
|
+
::view-transition-old(hero) {
|
|
174
|
+
animation: fade-out 0.3s ease;
|
|
175
|
+
}
|
|
176
|
+
::view-transition-new(hero) {
|
|
177
|
+
animation: fade-in 0.3s ease;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Slide the sidebar */
|
|
181
|
+
::view-transition-old(sidebar) {
|
|
182
|
+
animation: slide-out-left 0.25s ease;
|
|
183
|
+
}
|
|
184
|
+
::view-transition-new(sidebar) {
|
|
185
|
+
animation: slide-in-left 0.25s ease;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Default transition for all elements */
|
|
189
|
+
::view-transition-old(*) {
|
|
190
|
+
animation-duration: 0.2s;
|
|
191
|
+
}
|
|
192
|
+
::view-transition-new(*) {
|
|
193
|
+
animation-duration: 0.2s;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
146
197
|
### Custom Transitions
|
|
147
198
|
|
|
148
199
|
Add custom animations:
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* About Page Controller
|
|
3
3
|
*
|
|
4
|
-
* This controller renders the about page using
|
|
5
|
-
* Provides information about
|
|
4
|
+
* This controller renders the about page using ODAC's skeleton-based view system.
|
|
5
|
+
* Provides information about ODAC and its key components.
|
|
6
6
|
*
|
|
7
7
|
* For AJAX requests, only content is returned. For full page loads, skeleton + content.
|
|
8
8
|
*/
|
|
@@ -11,7 +11,7 @@ module.exports = function (Odac) {
|
|
|
11
11
|
// Set variables for AJAX responses
|
|
12
12
|
Odac.set(
|
|
13
13
|
{
|
|
14
|
-
pageTitle: 'About
|
|
14
|
+
pageTitle: 'About ODAC',
|
|
15
15
|
version: '1.0.0'
|
|
16
16
|
},
|
|
17
17
|
true
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Home Page Controller
|
|
3
3
|
*
|
|
4
|
-
* This controller renders the home page using
|
|
4
|
+
* This controller renders the home page using ODAC's skeleton-based view system.
|
|
5
5
|
* The skeleton provides the layout (header, nav, footer) and the view provides the content.
|
|
6
6
|
*
|
|
7
7
|
* For AJAX requests (odac-link navigation), only the content is returned.
|
|
@@ -18,7 +18,7 @@ module.exports = function (Odac) {
|
|
|
18
18
|
// Set variables that will be available in AJAX responses
|
|
19
19
|
Odac.set(
|
|
20
20
|
{
|
|
21
|
-
welcomeMessage: 'Welcome to
|
|
21
|
+
welcomeMessage: 'Welcome to ODAC!',
|
|
22
22
|
timestamp: Date.now()
|
|
23
23
|
},
|
|
24
24
|
true
|