odac 0.9.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.
- package/.editorconfig +21 -0
- package/.github/workflows/auto-pr-description.yml +49 -0
- package/.github/workflows/release.yml +32 -0
- package/.github/workflows/test-coverage.yml +58 -0
- package/.husky/pre-commit +2 -0
- package/.kiro/steering/code-style.md +56 -0
- package/.kiro/steering/product.md +20 -0
- package/.kiro/steering/structure.md +77 -0
- package/.kiro/steering/tech.md +87 -0
- package/.prettierrc +10 -0
- package/.releaserc.js +134 -0
- package/AGENTS.md +84 -0
- package/CHANGELOG.md +181 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +661 -0
- package/README.md +57 -0
- package/SECURITY.md +26 -0
- package/bin/candy +10 -0
- package/bin/candypack +10 -0
- package/cli/index.js +3 -0
- package/cli/src/Cli.js +348 -0
- package/cli/src/Connector.js +93 -0
- package/cli/src/Monitor.js +416 -0
- package/core/Candy.js +87 -0
- package/core/Commands.js +239 -0
- package/core/Config.js +1094 -0
- package/core/Lang.js +52 -0
- package/core/Log.js +43 -0
- package/core/Process.js +26 -0
- package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
- package/docs/backend/01-overview/03-development-server.md +79 -0
- package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
- package/docs/backend/03-config/00-configuration-overview.md +214 -0
- package/docs/backend/03-config/01-database-connection.md +60 -0
- package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
- package/docs/backend/03-config/03-request-timeout.md +11 -0
- package/docs/backend/03-config/04-environment-variables.md +227 -0
- package/docs/backend/03-config/05-early-hints.md +352 -0
- package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
- package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
- package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
- package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
- package/docs/backend/04-routing/05-advanced-routing.md +14 -0
- package/docs/backend/04-routing/06-error-pages.md +101 -0
- package/docs/backend/04-routing/07-cron-jobs.md +149 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
- package/docs/backend/05-controllers/03-controller-classes.md +93 -0
- package/docs/backend/05-forms/01-custom-forms.md +395 -0
- package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
- package/docs/backend/07-views/01-the-view-directory.md +73 -0
- package/docs/backend/07-views/02-rendering-a-view.md +179 -0
- package/docs/backend/07-views/03-template-syntax.md +181 -0
- package/docs/backend/07-views/03-variables.md +328 -0
- package/docs/backend/07-views/04-request-data.md +231 -0
- package/docs/backend/07-views/05-conditionals.md +290 -0
- package/docs/backend/07-views/06-loops.md +353 -0
- package/docs/backend/07-views/07-translations.md +358 -0
- package/docs/backend/07-views/08-backend-javascript.md +398 -0
- package/docs/backend/07-views/09-comments.md +297 -0
- package/docs/backend/08-database/01-database-connection.md +99 -0
- package/docs/backend/08-database/02-using-mysql.md +322 -0
- package/docs/backend/09-validation/01-the-validator-service.md +424 -0
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
- package/docs/backend/10-authentication/03-register.md +134 -0
- package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
- package/docs/backend/10-authentication/05-session-management.md +159 -0
- package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
- package/docs/backend/11-mail/01-the-mail-service.md +42 -0
- package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
- package/docs/backend/13-utilities/01-candy-var.md +504 -0
- package/docs/frontend/01-overview/01-introduction.md +146 -0
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
- package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
- package/docs/frontend/03-forms/01-form-handling.md +420 -0
- package/docs/frontend/04-api-requests/01-get-post.md +443 -0
- package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
- package/docs/index.json +452 -0
- package/docs/server/01-installation/01-quick-install.md +19 -0
- package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
- package/docs/server/02-get-started/01-core-concepts.md +7 -0
- package/docs/server/02-get-started/02-basic-commands.md +57 -0
- package/docs/server/02-get-started/03-cli-reference.md +276 -0
- package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
- package/docs/server/03-service/01-start-a-new-service.md +57 -0
- package/docs/server/03-service/02-delete-a-service.md +48 -0
- package/docs/server/04-web/01-create-a-website.md +36 -0
- package/docs/server/04-web/02-list-websites.md +9 -0
- package/docs/server/04-web/03-delete-a-website.md +29 -0
- package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
- package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
- package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
- package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
- package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
- package/docs/server/07-mail/04-change-account-password.md +23 -0
- package/eslint.config.mjs +120 -0
- package/framework/index.js +4 -0
- package/framework/src/Auth.js +309 -0
- package/framework/src/Candy.js +81 -0
- package/framework/src/Config.js +79 -0
- package/framework/src/Env.js +60 -0
- package/framework/src/Lang.js +57 -0
- package/framework/src/Mail.js +83 -0
- package/framework/src/Mysql.js +575 -0
- package/framework/src/Request.js +301 -0
- package/framework/src/Route/Cron.js +128 -0
- package/framework/src/Route/Internal.js +439 -0
- package/framework/src/Route.js +455 -0
- package/framework/src/Server.js +15 -0
- package/framework/src/Stream.js +163 -0
- package/framework/src/Token.js +37 -0
- package/framework/src/Validator.js +271 -0
- package/framework/src/Var.js +211 -0
- package/framework/src/View/EarlyHints.js +190 -0
- package/framework/src/View/Form.js +600 -0
- package/framework/src/View.js +513 -0
- package/framework/web/candy.js +838 -0
- package/jest.config.js +22 -0
- package/locale/de-DE.json +80 -0
- package/locale/en-US.json +79 -0
- package/locale/es-ES.json +80 -0
- package/locale/fr-FR.json +80 -0
- package/locale/pt-BR.json +80 -0
- package/locale/ru-RU.json +80 -0
- package/locale/tr-TR.json +85 -0
- package/locale/zh-CN.json +80 -0
- package/package.json +86 -0
- package/server/index.js +5 -0
- package/server/src/Api.js +88 -0
- package/server/src/DNS.js +940 -0
- package/server/src/Hub.js +535 -0
- package/server/src/Mail.js +571 -0
- package/server/src/SSL.js +180 -0
- package/server/src/Server.js +27 -0
- package/server/src/Service.js +248 -0
- package/server/src/Subdomain.js +64 -0
- package/server/src/Web/Firewall.js +170 -0
- package/server/src/Web/Proxy.js +134 -0
- package/server/src/Web.js +451 -0
- package/server/src/mail/imap.js +1091 -0
- package/server/src/mail/server.js +32 -0
- package/server/src/mail/smtp.js +786 -0
- package/test/cli/Cli.test.js +36 -0
- package/test/core/Candy.test.js +234 -0
- package/test/core/Commands.test.js +538 -0
- package/test/core/Config.test.js +1435 -0
- package/test/core/Lang.test.js +250 -0
- package/test/core/Process.test.js +156 -0
- package/test/framework/Route.test.js +239 -0
- package/test/framework/View/EarlyHints.test.js +282 -0
- package/test/scripts/check-coverage.js +132 -0
- package/test/server/Api.test.js +647 -0
- package/test/server/Client.test.js +338 -0
- package/test/server/DNS.test.js +2050 -0
- package/test/server/DNS.test.js.bak +2084 -0
- package/test/server/Log.test.js +73 -0
- package/test/server/Mail.account.test_.js +460 -0
- package/test/server/Mail.init.test_.js +411 -0
- package/test/server/Mail.test_.js +1340 -0
- package/test/server/SSL.test_.js +1491 -0
- package/test/server/Server.test.js +765 -0
- package/test/server/Service.test_.js +1127 -0
- package/test/server/Subdomain.test.js +440 -0
- package/test/server/Web/Firewall.test.js +175 -0
- package/test/server/Web.test_.js +1562 -0
- package/test/server/__mocks__/acme-client.js +17 -0
- package/test/server/__mocks__/bcrypt.js +50 -0
- package/test/server/__mocks__/child_process.js +389 -0
- package/test/server/__mocks__/crypto.js +432 -0
- package/test/server/__mocks__/fs.js +450 -0
- package/test/server/__mocks__/globalCandy.js +227 -0
- package/test/server/__mocks__/http-proxy.js +105 -0
- package/test/server/__mocks__/http.js +575 -0
- package/test/server/__mocks__/https.js +272 -0
- package/test/server/__mocks__/index.js +249 -0
- package/test/server/__mocks__/mail/server.js +100 -0
- package/test/server/__mocks__/mail/smtp.js +31 -0
- package/test/server/__mocks__/mailparser.js +81 -0
- package/test/server/__mocks__/net.js +369 -0
- package/test/server/__mocks__/node-forge.js +328 -0
- package/test/server/__mocks__/os.js +320 -0
- package/test/server/__mocks__/path.js +291 -0
- package/test/server/__mocks__/selfsigned.js +8 -0
- package/test/server/__mocks__/server/src/mail/server.js +100 -0
- package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
- package/test/server/__mocks__/smtp-server.js +106 -0
- package/test/server/__mocks__/sqlite3.js +394 -0
- package/test/server/__mocks__/testFactories.js +299 -0
- package/test/server/__mocks__/testHelpers.js +363 -0
- package/test/server/__mocks__/tls.js +229 -0
- package/watchdog/index.js +3 -0
- package/watchdog/src/Watchdog.js +156 -0
- package/web/config.json +5 -0
- package/web/controller/page/about.js +27 -0
- package/web/controller/page/index.js +34 -0
- package/web/package.json +18 -0
- package/web/public/assets/css/style.css +1835 -0
- package/web/public/assets/js/app.js +96 -0
- package/web/route/www.js +19 -0
- package/web/skeleton/main.html +22 -0
- package/web/view/content/about.html +65 -0
- package/web/view/content/home.html +205 -0
- package/web/view/footer/main.html +11 -0
- package/web/view/head/main.html +5 -0
- package/web/view/header/main.html +14 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
# AJAX Navigation
|
|
2
|
+
|
|
3
|
+
CandyPack framework includes a built-in AJAX navigation system that enables smooth, single-page application (SPA) style navigation without full page reloads.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero Configuration**: Works automatically with all internal links (`/`)
|
|
8
|
+
- **Smooth Transitions**: Load only specific page sections without full page reload
|
|
9
|
+
- **History API Integration**: Browser back/forward buttons work seamlessly
|
|
10
|
+
- **Automatic Token Management**: CSRF tokens are handled automatically
|
|
11
|
+
- **Progressive Enhancement**: Falls back to normal navigation if JavaScript fails
|
|
12
|
+
- **Flexible Element Loading**: Choose which page sections to update
|
|
13
|
+
- **Page-Specific Callbacks**: Run custom code when specific pages load
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### Minimal Setup (Recommended)
|
|
18
|
+
|
|
19
|
+
Just enable navigation - it automatically handles all internal links:
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
Candy.action({
|
|
23
|
+
navigate: 'main' // Update <main> element on navigation
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
That's it! All links starting with `/` will now load via AJAX.
|
|
28
|
+
|
|
29
|
+
### Medium Setup
|
|
30
|
+
|
|
31
|
+
Add a callback for post-navigation actions:
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
Candy.action({
|
|
35
|
+
navigate: {
|
|
36
|
+
update: 'main',
|
|
37
|
+
on: function(page, variables) {
|
|
38
|
+
console.log('Navigated to:', page)
|
|
39
|
+
updateActiveNav()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Advanced Setup
|
|
46
|
+
|
|
47
|
+
Full control over navigation behavior:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
Candy.action({
|
|
51
|
+
navigate: {
|
|
52
|
+
links: 'a[href^="/"]', // Which links to intercept
|
|
53
|
+
update: { // Which elements to update
|
|
54
|
+
content: 'main',
|
|
55
|
+
header: 'header'
|
|
56
|
+
},
|
|
57
|
+
on: function(page, variables) {
|
|
58
|
+
console.log('Page:', page)
|
|
59
|
+
console.log('Data:', variables)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### HTML Markup
|
|
66
|
+
|
|
67
|
+
No special attributes needed! Just use normal links:
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<nav>
|
|
71
|
+
<a href="/">Home</a>
|
|
72
|
+
<a href="/about">About</a>
|
|
73
|
+
<a href="/docs">Docs</a>
|
|
74
|
+
</nav>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
All internal links (starting with `/`) are automatically handled.
|
|
78
|
+
|
|
79
|
+
## Server-Side Setup
|
|
80
|
+
|
|
81
|
+
### Skeleton Structure (Required)
|
|
82
|
+
|
|
83
|
+
For AJAX navigation to work properly, you must define a skeleton template that contains placeholders for the sections you want to update.
|
|
84
|
+
|
|
85
|
+
**Important Rules:**
|
|
86
|
+
- Placeholders must be **UPPERCASE** (e.g., `{{ HEADER }}`, `{{ CONTENT }}`)
|
|
87
|
+
- Each placeholder must be **wrapped in HTML tags** (e.g., `<header>{{ HEADER }}</header>`)
|
|
88
|
+
- HTML tags provide boundaries for AJAX to identify and update specific sections
|
|
89
|
+
|
|
90
|
+
Example `skeleton/main.html`:
|
|
91
|
+
|
|
92
|
+
```html
|
|
93
|
+
<!DOCTYPE html>
|
|
94
|
+
<html>
|
|
95
|
+
<head>
|
|
96
|
+
<title>My Site</title>
|
|
97
|
+
<script src="/assets/js/candy.js"></script>
|
|
98
|
+
<script src="/assets/js/app.js"></script>
|
|
99
|
+
</head>
|
|
100
|
+
<body>
|
|
101
|
+
<header>
|
|
102
|
+
{{ HEADER }}
|
|
103
|
+
</header>
|
|
104
|
+
|
|
105
|
+
<main>
|
|
106
|
+
{{ CONTENT }}
|
|
107
|
+
</main>
|
|
108
|
+
|
|
109
|
+
<footer>
|
|
110
|
+
{{ FOOTER }}
|
|
111
|
+
</footer>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Key Points:**
|
|
117
|
+
- Placeholder names in skeleton are UPPERCASE: `{{ HEADER }}`, `{{ CONTENT }}`
|
|
118
|
+
- Controller keys are lowercase: `header`, `content`, `footer`
|
|
119
|
+
- Frontend selectors target the HTML tags: `'header'`, `'main'`, `'footer'`
|
|
120
|
+
|
|
121
|
+
### Controller Setup
|
|
122
|
+
|
|
123
|
+
Controllers automatically support AJAX loading. Use `Candy.View.skeleton()` to specify which skeleton to use:
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
module.exports = function (Candy) {
|
|
127
|
+
// Define the skeleton template
|
|
128
|
+
Candy.View.skeleton('main')
|
|
129
|
+
|
|
130
|
+
// Set view parts - lowercase keys map to UPPERCASE placeholders
|
|
131
|
+
Candy.View.set({
|
|
132
|
+
header: 'main', // Loads view/header/main.html into {{ HEADER }}
|
|
133
|
+
content: 'about', // Loads view/content/about.html into {{ CONTENT }}
|
|
134
|
+
footer: 'main' // Loads view/footer/main.html into {{ FOOTER }}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Optional: Send variables to frontend (AJAX only)
|
|
138
|
+
Candy.set({
|
|
139
|
+
pageTitle: 'About',
|
|
140
|
+
data: {foo: 'bar'}
|
|
141
|
+
}, true) // true = include in AJAX responses
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Mapping:**
|
|
146
|
+
- Controller key `header` → Skeleton placeholder `{{ HEADER }}`
|
|
147
|
+
- Controller key `content` → Skeleton placeholder `{{ CONTENT }}`
|
|
148
|
+
- Controller key `footer` → Skeleton placeholder `{{ FOOTER }}`
|
|
149
|
+
|
|
150
|
+
## How It Works
|
|
151
|
+
|
|
152
|
+
### Normal Page Load
|
|
153
|
+
|
|
154
|
+
1. User visits `/about`
|
|
155
|
+
2. Server renders full HTML with skeleton + all view parts
|
|
156
|
+
3. Browser displays complete page
|
|
157
|
+
|
|
158
|
+
### AJAX Page Load
|
|
159
|
+
|
|
160
|
+
1. User clicks `<a href="/about">`
|
|
161
|
+
2. JavaScript intercepts click and sends AJAX request with:
|
|
162
|
+
- Header: `X-Candy: ajaxload`
|
|
163
|
+
- Header: `X-Candy-Load: content,header` (requested sections)
|
|
164
|
+
3. Server detects AJAX request and returns only requested sections as JSON:
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"output": {
|
|
168
|
+
"content": "<div>About page content...</div>",
|
|
169
|
+
"header": "<nav>...</nav>"
|
|
170
|
+
},
|
|
171
|
+
"variables": {
|
|
172
|
+
"pageTitle": "About",
|
|
173
|
+
"data": {"foo": "bar"}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
4. JavaScript updates specified DOM elements with fade animation
|
|
178
|
+
5. Browser URL updates via History API
|
|
179
|
+
6. Page-specific callbacks execute
|
|
180
|
+
|
|
181
|
+
**Key Points:**
|
|
182
|
+
- The `output` keys in the JSON response match the lowercase keys from `Candy.View.set()` in your controller
|
|
183
|
+
- These keys correspond to UPPERCASE placeholders in your skeleton (e.g., `content` → `{{ CONTENT }}`)
|
|
184
|
+
- Only the sections specified in `navigate.update` are sent and updated
|
|
185
|
+
- Frontend selectors target the HTML tags wrapping the placeholders
|
|
186
|
+
|
|
187
|
+
## API Reference
|
|
188
|
+
|
|
189
|
+
### Candy.action({ navigate: ... })
|
|
190
|
+
|
|
191
|
+
Initialize AJAX navigation using the action system.
|
|
192
|
+
|
|
193
|
+
#### Minimal Usage
|
|
194
|
+
```javascript
|
|
195
|
+
Candy.action({
|
|
196
|
+
navigate: 'main' // Just specify element to update
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
- **Default selector**: `'a[href^="/"]'` (all internal links)
|
|
200
|
+
- **Default element**: Updates specified element as 'content'
|
|
201
|
+
|
|
202
|
+
#### Medium Usage
|
|
203
|
+
```javascript
|
|
204
|
+
Candy.action({
|
|
205
|
+
navigate: {
|
|
206
|
+
update: 'main', // Element to update
|
|
207
|
+
on: function(page, vars) { // Callback after navigation
|
|
208
|
+
console.log('Navigated to:', page)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Advanced Usage
|
|
215
|
+
```javascript
|
|
216
|
+
Candy.action({
|
|
217
|
+
navigate: {
|
|
218
|
+
links: 'a[href^="/"]', // Which links to intercept
|
|
219
|
+
update: { // Multiple elements to update
|
|
220
|
+
content: 'main',
|
|
221
|
+
header: 'header',
|
|
222
|
+
sidebar: '#sidebar'
|
|
223
|
+
},
|
|
224
|
+
on: function(page, variables) {
|
|
225
|
+
// Called after each navigation
|
|
226
|
+
// page: current page name (e.g., 'about')
|
|
227
|
+
// variables: data from server
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Boolean Usage
|
|
234
|
+
```javascript
|
|
235
|
+
Candy.action({
|
|
236
|
+
navigate: true // Enable with all defaults
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Or disable completely
|
|
240
|
+
Candy.action({
|
|
241
|
+
navigate: false // Disable AJAX navigation
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
- **Selector**: `'a[href^="/"]'`
|
|
245
|
+
- **Update**: `{content: 'main'}`
|
|
246
|
+
|
|
247
|
+
### Excluding Specific Links
|
|
248
|
+
|
|
249
|
+
You can exclude specific links from AJAX navigation using either:
|
|
250
|
+
|
|
251
|
+
**1. Data Attribute:**
|
|
252
|
+
```html
|
|
253
|
+
<a href="/download" data-navigate="false">Download PDF</a>
|
|
254
|
+
<a href="/external" data-navigate="false">External Link</a>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**2. CSS Class:**
|
|
258
|
+
```html
|
|
259
|
+
<a href="/download" class="no-navigate">Download PDF</a>
|
|
260
|
+
<a href="/logout" class="no-navigate">Logout</a>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Both methods work automatically - no additional configuration needed!
|
|
264
|
+
|
|
265
|
+
#### Configuration Options
|
|
266
|
+
|
|
267
|
+
**`links`** or **`selector`** (string, optional)
|
|
268
|
+
- CSS selector for links to intercept
|
|
269
|
+
- Default: `'a[href^="/"]'` (all internal links)
|
|
270
|
+
- Examples: `'a.ajax-link'`, `'nav a'`, `'a[data-ajax]'`
|
|
271
|
+
|
|
272
|
+
**`update`** or **`elements`** (string | object, optional)
|
|
273
|
+
- String: Single element selector (becomes `{content: selector}`)
|
|
274
|
+
- Object: Multiple elements to update
|
|
275
|
+
- Default: `{content: 'main'}`
|
|
276
|
+
- Examples:
|
|
277
|
+
```javascript
|
|
278
|
+
update: 'main' // Single element
|
|
279
|
+
update: { // Multiple elements
|
|
280
|
+
content: 'main',
|
|
281
|
+
header: 'header',
|
|
282
|
+
sidebar: '#sidebar'
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**`on`** or **`callback`** (function, optional)
|
|
287
|
+
- Called after each successful navigation
|
|
288
|
+
- Parameters:
|
|
289
|
+
- `page` (string): Current page name
|
|
290
|
+
- `variables` (object): Server-side data
|
|
291
|
+
- Example:
|
|
292
|
+
```javascript
|
|
293
|
+
on: function(page, variables) {
|
|
294
|
+
console.log('Page:', page)
|
|
295
|
+
updateAnalytics(page)
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Candy.loader(selector, elements, callback)
|
|
300
|
+
|
|
301
|
+
Low-level method for direct initialization (not recommended for new code).
|
|
302
|
+
|
|
303
|
+
**Parameters:** Same as navigate configuration, but as separate arguments.
|
|
304
|
+
|
|
305
|
+
### Candy.load(url, callback, push)
|
|
306
|
+
|
|
307
|
+
Programmatically load a page via AJAX.
|
|
308
|
+
|
|
309
|
+
**Parameters:**
|
|
310
|
+
- `url` (string): URL to load
|
|
311
|
+
- `callback` (function): Optional callback after load
|
|
312
|
+
- `push` (boolean): Whether to update browser history (default: true)
|
|
313
|
+
|
|
314
|
+
**Example:**
|
|
315
|
+
```javascript
|
|
316
|
+
Candy.load('/about', function(page, variables) {
|
|
317
|
+
console.log('Loaded:', page)
|
|
318
|
+
})
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Page-Specific Actions
|
|
322
|
+
|
|
323
|
+
Run code when specific pages load. The page identifier is based on the controller name or view name:
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
Candy.action({
|
|
327
|
+
page: {
|
|
328
|
+
// Runs when controller/page/index.js is used
|
|
329
|
+
index: function(variables) {
|
|
330
|
+
console.log('Home page loaded')
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
// Runs when controller/page/about.js is used
|
|
334
|
+
about: function(variables) {
|
|
335
|
+
console.log('About page loaded')
|
|
336
|
+
console.log('Server data:', variables)
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// Runs when view object has {content: 'dashboard'}
|
|
340
|
+
dashboard: function(variables) {
|
|
341
|
+
console.log('Dashboard loaded')
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Page Identifier Rules:**
|
|
348
|
+
- **With controller**: Uses controller filename (e.g., `user.js` → `'user'`)
|
|
349
|
+
- **With view object**: Uses `content` or `all` value (e.g., `{content: 'dashboard'}` → `'dashboard'`)
|
|
350
|
+
- Accessible via `Candy.page()` or `document.documentElement.dataset.candyPage`
|
|
351
|
+
|
|
352
|
+
## Server Variables
|
|
353
|
+
|
|
354
|
+
Send data from server to client in AJAX responses:
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
// In controller
|
|
358
|
+
Candy.set({
|
|
359
|
+
user: {name: 'John', role: 'admin'},
|
|
360
|
+
stats: {views: 1234}
|
|
361
|
+
}, true) // true = include in AJAX
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Access in client:
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
Candy.action({
|
|
368
|
+
navigate: {
|
|
369
|
+
update: 'main',
|
|
370
|
+
on: function(page, variables) {
|
|
371
|
+
console.log(variables.user.name) // 'John'
|
|
372
|
+
console.log(variables.stats.views) // 1234
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Best Practices
|
|
379
|
+
|
|
380
|
+
1. **Progressive Enhancement**: Always ensure links work without JavaScript
|
|
381
|
+
2. **Loading States**: Show loading indicators during transitions
|
|
382
|
+
3. **Error Handling**: Provide fallback for failed AJAX requests
|
|
383
|
+
4. **SEO**: Ensure content is accessible to search engines
|
|
384
|
+
5. **Performance**: Only load necessary page sections
|
|
385
|
+
|
|
386
|
+
## Example: Complete Setup
|
|
387
|
+
|
|
388
|
+
### Minimal Example
|
|
389
|
+
```javascript
|
|
390
|
+
// Just enable AJAX navigation
|
|
391
|
+
Candy.action({
|
|
392
|
+
navigate: 'main'
|
|
393
|
+
})
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Real-World Example
|
|
397
|
+
```javascript
|
|
398
|
+
// app.js - Everything in one Candy.action() call
|
|
399
|
+
Candy.action({
|
|
400
|
+
// AJAX Navigation - automatically handles all internal links
|
|
401
|
+
navigate: {
|
|
402
|
+
update: 'main',
|
|
403
|
+
on: function(page, variables) {
|
|
404
|
+
Candy.fn.updateActiveNav(window.location.pathname)
|
|
405
|
+
console.log('Navigated to:', page)
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
// Custom functions (accessible as Candy.fn.functionName)
|
|
410
|
+
function: {
|
|
411
|
+
updateActiveNav: function(url) {
|
|
412
|
+
document.querySelectorAll('nav a').forEach(link => {
|
|
413
|
+
link.classList.toggle('active', link.getAttribute('href') === url)
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
// App initialization
|
|
419
|
+
load: function() {
|
|
420
|
+
console.log('App initialized')
|
|
421
|
+
Candy.fn.updateActiveNav(window.location.pathname)
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
// Page-specific code
|
|
425
|
+
page: {
|
|
426
|
+
index: function(variables) {
|
|
427
|
+
// Home page specific code
|
|
428
|
+
Candy.form('#contact-form', function(data) {
|
|
429
|
+
if (data.result.success) {
|
|
430
|
+
alert('Message sent!')
|
|
431
|
+
}
|
|
432
|
+
})
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
about: function(variables) {
|
|
436
|
+
// About page specific code
|
|
437
|
+
console.log('About page:', variables.pageTitle)
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
// Event handlers
|
|
442
|
+
click: {
|
|
443
|
+
'#refresh-btn': function() {
|
|
444
|
+
Candy.load(window.location.pathname)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Advanced Multi-Section Example
|
|
451
|
+
```javascript
|
|
452
|
+
Candy.action({
|
|
453
|
+
navigate: {
|
|
454
|
+
update: {
|
|
455
|
+
content: 'main',
|
|
456
|
+
sidebar: '#sidebar',
|
|
457
|
+
breadcrumb: '.breadcrumb'
|
|
458
|
+
},
|
|
459
|
+
on: function(page, vars) {
|
|
460
|
+
// Update page title
|
|
461
|
+
document.title = vars.title || page
|
|
462
|
+
|
|
463
|
+
// Track analytics
|
|
464
|
+
if (window.gtag) {
|
|
465
|
+
gtag('config', 'GA_ID', {page_path: window.location.pathname})
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
})
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Disabling Navigation
|
|
473
|
+
|
|
474
|
+
### Disable Completely
|
|
475
|
+
```javascript
|
|
476
|
+
Candy.action({
|
|
477
|
+
navigate: false // Disable AJAX navigation entirely
|
|
478
|
+
})
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Disable for Specific Links
|
|
482
|
+
|
|
483
|
+
**Method 1: Data Attribute**
|
|
484
|
+
```html
|
|
485
|
+
<a href="/download.pdf" data-navigate="false">Download PDF</a>
|
|
486
|
+
<a href="/api/export" data-navigate="false">Export Data</a>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Method 2: CSS Class**
|
|
490
|
+
```html
|
|
491
|
+
<a href="/logout" class="no-navigate">Logout</a>
|
|
492
|
+
<a href="/admin" class="no-navigate">Admin Panel</a>
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Common Use Cases:**
|
|
496
|
+
- File downloads
|
|
497
|
+
- External links
|
|
498
|
+
- Logout/login actions
|
|
499
|
+
- Admin panels
|
|
500
|
+
- API endpoints
|
|
501
|
+
- Forms with file uploads
|
|
502
|
+
|
|
503
|
+
## Best Practices
|
|
504
|
+
|
|
505
|
+
### 1. Use Minimal Configuration
|
|
506
|
+
```javascript
|
|
507
|
+
// Simple and effective
|
|
508
|
+
Candy.action({
|
|
509
|
+
navigate: 'main'
|
|
510
|
+
})
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### 2. Exclude Special Links
|
|
514
|
+
```html
|
|
515
|
+
<!-- Downloads -->
|
|
516
|
+
<a href="/files/report.pdf" data-navigate="false">Download Report</a>
|
|
517
|
+
|
|
518
|
+
<!-- External -->
|
|
519
|
+
<a href="https://example.com" target="_blank">External Site</a>
|
|
520
|
+
|
|
521
|
+
<!-- Actions -->
|
|
522
|
+
<a href="/logout" class="no-navigate">Logout</a>
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### 3. Handle Loading States
|
|
526
|
+
```javascript
|
|
527
|
+
Candy.action({
|
|
528
|
+
navigate: {
|
|
529
|
+
update: 'main',
|
|
530
|
+
on: (page, vars) => {
|
|
531
|
+
hideLoadingSpinner()
|
|
532
|
+
updatePageTitle(vars.title)
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
click: {
|
|
537
|
+
'a[href^="/"]': function() {
|
|
538
|
+
showLoadingSpinner()
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
})
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Troubleshooting
|
|
545
|
+
|
|
546
|
+
### Links not loading via AJAX
|
|
547
|
+
|
|
548
|
+
- Check browser console for errors
|
|
549
|
+
- Verify navigate is enabled in `Candy.action()`
|
|
550
|
+
- Ensure links start with `/` for internal navigation
|
|
551
|
+
|
|
552
|
+
### Specific links should not use AJAX
|
|
553
|
+
|
|
554
|
+
- Add `data-navigate="false"` attribute
|
|
555
|
+
- Or add `no-navigate` class
|
|
556
|
+
- Or customize selector to exclude them
|
|
557
|
+
|
|
558
|
+
### Elements not updating
|
|
559
|
+
|
|
560
|
+
This is usually caused by mismatched keys between your skeleton, controller, and frontend configuration.
|
|
561
|
+
|
|
562
|
+
**Check these three places match:**
|
|
563
|
+
|
|
564
|
+
1. **Skeleton template** (`skeleton/main.html`):
|
|
565
|
+
```html
|
|
566
|
+
<header>
|
|
567
|
+
{{ HEADER }}
|
|
568
|
+
</header>
|
|
569
|
+
<main>
|
|
570
|
+
{{ CONTENT }}
|
|
571
|
+
</main>
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
2. **Controller** (`controller/page/about.js`):
|
|
575
|
+
```javascript
|
|
576
|
+
Candy.View.skeleton('main')
|
|
577
|
+
Candy.View.set({
|
|
578
|
+
header: 'main', // Lowercase → {{ HEADER }}
|
|
579
|
+
content: 'about' // Lowercase → {{ CONTENT }}
|
|
580
|
+
})
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
3. **Frontend** (`public/assets/js/app.js`):
|
|
584
|
+
```javascript
|
|
585
|
+
Candy.action({
|
|
586
|
+
navigate: {
|
|
587
|
+
update: {
|
|
588
|
+
header: 'header', // Targets <header> tag
|
|
589
|
+
content: 'main' // Targets <main> tag
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
})
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
**Mapping:**
|
|
596
|
+
- Skeleton: `{{ HEADER }}` (uppercase) wrapped in `<header>` tag
|
|
597
|
+
- Controller: `header: 'main'` (lowercase key)
|
|
598
|
+
- Frontend: `header: 'header'` (lowercase key, CSS selector for `<header>` tag)
|
|
599
|
+
|
|
600
|
+
**Also verify:**
|
|
601
|
+
- Element selectors match actual DOM elements (e.g., `'main'` matches `<main>`)
|
|
602
|
+
- Skeleton template is defined with `Candy.View.skeleton('main')`
|
|
603
|
+
- View parts are defined in controller with `Candy.View.set()`
|
|
604
|
+
|
|
605
|
+
### Variables not available
|
|
606
|
+
|
|
607
|
+
- Confirm `Candy.set(data, true)` has `true` as second parameter
|
|
608
|
+
- Check that variables are set before `View.print()` is called
|