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,519 @@
|
|
|
1
|
+
# Advanced AJAX Navigation
|
|
2
|
+
|
|
3
|
+
Advanced techniques and patterns for AJAX navigation in CandyPack.
|
|
4
|
+
|
|
5
|
+
**Note:** All examples assume you have properly configured your skeleton template and controller. See the [Quick Start guide](01-quick-start.md#skeleton-structure-required) for setup instructions.
|
|
6
|
+
|
|
7
|
+
## Programmatic Navigation
|
|
8
|
+
|
|
9
|
+
### Using Candy.load()
|
|
10
|
+
|
|
11
|
+
Navigate programmatically from your code:
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
// Basic usage
|
|
15
|
+
Candy.load('/about')
|
|
16
|
+
|
|
17
|
+
// With callback
|
|
18
|
+
Candy.load('/about', function(page, variables) {
|
|
19
|
+
console.log('Loaded:', page)
|
|
20
|
+
console.log('Data:', variables)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Without updating history
|
|
24
|
+
Candy.load('/about', callback, false)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Use Cases
|
|
28
|
+
|
|
29
|
+
**Redirect after form submission:**
|
|
30
|
+
```javascript
|
|
31
|
+
Candy.form('#my-form', function(data) {
|
|
32
|
+
if (data.result.success) {
|
|
33
|
+
Candy.load('/success')
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Conditional navigation:**
|
|
39
|
+
```javascript
|
|
40
|
+
if (user.isLoggedIn) {
|
|
41
|
+
Candy.load('/dashboard')
|
|
42
|
+
} else {
|
|
43
|
+
Candy.load('/login')
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Timed navigation:**
|
|
48
|
+
```javascript
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
Candy.load('/next-page')
|
|
51
|
+
}, 3000)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Multi-Section Updates
|
|
55
|
+
|
|
56
|
+
### Updating Multiple Areas
|
|
57
|
+
|
|
58
|
+
Update different parts of your page simultaneously:
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
Candy.action({
|
|
62
|
+
navigate: {
|
|
63
|
+
update: {
|
|
64
|
+
content: 'main',
|
|
65
|
+
sidebar: '#sidebar',
|
|
66
|
+
breadcrumb: '.breadcrumb',
|
|
67
|
+
notifications: '#notifications'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Controller Setup
|
|
74
|
+
|
|
75
|
+
Define all parts in your controller:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
module.exports = function(Candy) {
|
|
79
|
+
Candy.View.skeleton('main')
|
|
80
|
+
Candy.View.set({
|
|
81
|
+
header: 'main',
|
|
82
|
+
content: 'dashboard',
|
|
83
|
+
sidebar: 'dashboard',
|
|
84
|
+
breadcrumb: 'dashboard',
|
|
85
|
+
footer: 'main'
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## State Management
|
|
91
|
+
|
|
92
|
+
### Preserving State
|
|
93
|
+
|
|
94
|
+
Maintain application state across navigations:
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
let appState = {
|
|
98
|
+
user: null,
|
|
99
|
+
cart: [],
|
|
100
|
+
filters: {}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Candy.action({
|
|
104
|
+
navigate: {
|
|
105
|
+
update: 'main',
|
|
106
|
+
on: function(page, variables) {
|
|
107
|
+
// Update state from server
|
|
108
|
+
if (variables.user) {
|
|
109
|
+
appState.user = variables.user
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Restore UI state
|
|
113
|
+
restoreFilters(appState.filters)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Session Storage
|
|
120
|
+
|
|
121
|
+
Persist state across page reloads:
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
Candy.action({
|
|
125
|
+
navigate: {
|
|
126
|
+
update: 'main',
|
|
127
|
+
on: function(page, variables) {
|
|
128
|
+
// Save to session storage
|
|
129
|
+
sessionStorage.setItem('lastPage', page)
|
|
130
|
+
sessionStorage.setItem('pageData', JSON.stringify(variables))
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
load: function() {
|
|
135
|
+
// Restore on app load
|
|
136
|
+
const lastPage = sessionStorage.getItem('lastPage')
|
|
137
|
+
if (lastPage) {
|
|
138
|
+
console.log('Last visited:', lastPage)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Animation & Transitions
|
|
145
|
+
|
|
146
|
+
### Custom Transitions
|
|
147
|
+
|
|
148
|
+
Add custom animations:
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
Candy.action({
|
|
152
|
+
navigate: {
|
|
153
|
+
update: 'main',
|
|
154
|
+
on: function(page, variables) {
|
|
155
|
+
const main = document.querySelector('main')
|
|
156
|
+
|
|
157
|
+
// Fade in animation
|
|
158
|
+
main.style.opacity = '0'
|
|
159
|
+
setTimeout(() => {
|
|
160
|
+
main.style.transition = 'opacity 0.3s'
|
|
161
|
+
main.style.opacity = '1'
|
|
162
|
+
}, 10)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Page Transitions
|
|
169
|
+
|
|
170
|
+
Smooth page transitions:
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
Candy.action({
|
|
174
|
+
navigate: {
|
|
175
|
+
update: 'main',
|
|
176
|
+
on: function(page, variables) {
|
|
177
|
+
const main = document.querySelector('main')
|
|
178
|
+
main.classList.add('page-enter')
|
|
179
|
+
|
|
180
|
+
setTimeout(() => {
|
|
181
|
+
main.classList.remove('page-enter')
|
|
182
|
+
main.classList.add('page-enter-active')
|
|
183
|
+
}, 10)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```css
|
|
190
|
+
main {
|
|
191
|
+
transition: transform 0.3s, opacity 0.3s;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main.page-enter {
|
|
195
|
+
opacity: 0;
|
|
196
|
+
transform: translateY(20px);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
main.page-enter-active {
|
|
200
|
+
opacity: 1;
|
|
201
|
+
transform: translateY(0);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Scroll Management
|
|
206
|
+
|
|
207
|
+
### Scroll to Top
|
|
208
|
+
|
|
209
|
+
Automatically scroll to top on navigation:
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
Candy.action({
|
|
213
|
+
navigate: {
|
|
214
|
+
update: 'main',
|
|
215
|
+
on: function(page, variables) {
|
|
216
|
+
window.scrollTo({
|
|
217
|
+
top: 0,
|
|
218
|
+
behavior: 'smooth'
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Preserve Scroll Position
|
|
226
|
+
|
|
227
|
+
Remember scroll position:
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
let scrollPositions = {}
|
|
231
|
+
|
|
232
|
+
Candy.action({
|
|
233
|
+
navigate: {
|
|
234
|
+
update: 'main',
|
|
235
|
+
on: function(page, variables) {
|
|
236
|
+
// Save current scroll position
|
|
237
|
+
const currentPath = window.location.pathname
|
|
238
|
+
scrollPositions[currentPath] = window.scrollY
|
|
239
|
+
|
|
240
|
+
// Restore scroll position for this page
|
|
241
|
+
const savedPosition = scrollPositions[page]
|
|
242
|
+
if (savedPosition !== undefined) {
|
|
243
|
+
setTimeout(() => {
|
|
244
|
+
window.scrollTo(0, savedPosition)
|
|
245
|
+
}, 100)
|
|
246
|
+
} else {
|
|
247
|
+
window.scrollTo(0, 0)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Scroll to Element
|
|
255
|
+
|
|
256
|
+
Scroll to specific element after navigation:
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
Candy.action({
|
|
260
|
+
navigate: {
|
|
261
|
+
update: 'main',
|
|
262
|
+
on: function(page, variables) {
|
|
263
|
+
// Check for hash in URL
|
|
264
|
+
const hash = window.location.hash
|
|
265
|
+
if (hash) {
|
|
266
|
+
setTimeout(() => {
|
|
267
|
+
const element = document.querySelector(hash)
|
|
268
|
+
if (element) {
|
|
269
|
+
element.scrollIntoView({behavior: 'smooth'})
|
|
270
|
+
}
|
|
271
|
+
}, 100)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Error Handling
|
|
279
|
+
|
|
280
|
+
### Handling Failed Requests
|
|
281
|
+
|
|
282
|
+
Gracefully handle navigation errors:
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
// Override Candy.load to add error handling
|
|
286
|
+
const originalLoad = Candy.load.bind(Candy)
|
|
287
|
+
|
|
288
|
+
Candy.load = function(url, callback, push) {
|
|
289
|
+
try {
|
|
290
|
+
originalLoad(url, function(page, variables) {
|
|
291
|
+
if (callback) callback(page, variables)
|
|
292
|
+
}, push)
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error('Navigation failed:', error)
|
|
295
|
+
// Fallback to normal navigation
|
|
296
|
+
window.location.href = url
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Retry Logic
|
|
302
|
+
|
|
303
|
+
Retry failed requests:
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
function loadWithRetry(url, maxRetries = 3) {
|
|
307
|
+
let attempts = 0
|
|
308
|
+
|
|
309
|
+
function attempt() {
|
|
310
|
+
attempts++
|
|
311
|
+
Candy.load(url,
|
|
312
|
+
(page, vars) => {
|
|
313
|
+
console.log('Success after', attempts, 'attempts')
|
|
314
|
+
},
|
|
315
|
+
true
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Initial attempt
|
|
320
|
+
attempt()
|
|
321
|
+
|
|
322
|
+
// Retry on error (implement error detection)
|
|
323
|
+
// This is a simplified example
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Performance Optimization
|
|
328
|
+
|
|
329
|
+
### Debouncing Navigation
|
|
330
|
+
|
|
331
|
+
Prevent rapid navigation:
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
let navigationTimeout
|
|
335
|
+
|
|
336
|
+
Candy.action({
|
|
337
|
+
click: {
|
|
338
|
+
'a[href^="/"]': function(e) {
|
|
339
|
+
clearTimeout(navigationTimeout)
|
|
340
|
+
|
|
341
|
+
navigationTimeout = setTimeout(() => {
|
|
342
|
+
// Navigation happens here
|
|
343
|
+
}, 100)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Prefetching
|
|
350
|
+
|
|
351
|
+
Prefetch pages on hover:
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
let prefetchCache = {}
|
|
355
|
+
|
|
356
|
+
Candy.action({
|
|
357
|
+
mouseover: {
|
|
358
|
+
'a[href^="/"]': function() {
|
|
359
|
+
const url = this.getAttribute('href')
|
|
360
|
+
|
|
361
|
+
if (!prefetchCache[url]) {
|
|
362
|
+
// Prefetch the page
|
|
363
|
+
fetch(url, {
|
|
364
|
+
headers: {
|
|
365
|
+
'X-Candy': 'prefetch'
|
|
366
|
+
}
|
|
367
|
+
}).then(response => response.text())
|
|
368
|
+
.then(html => {
|
|
369
|
+
prefetchCache[url] = html
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Integration with Other Libraries
|
|
378
|
+
|
|
379
|
+
### With Analytics
|
|
380
|
+
|
|
381
|
+
Track page views:
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
Candy.action({
|
|
385
|
+
navigate: {
|
|
386
|
+
update: 'main',
|
|
387
|
+
on: function(page, variables) {
|
|
388
|
+
// Google Analytics
|
|
389
|
+
if (window.gtag) {
|
|
390
|
+
gtag('config', 'GA_ID', {
|
|
391
|
+
page_path: window.location.pathname,
|
|
392
|
+
page_title: variables.title || page
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Plausible
|
|
397
|
+
if (window.plausible) {
|
|
398
|
+
plausible('pageview')
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### With State Management
|
|
406
|
+
|
|
407
|
+
Redux/Vuex integration:
|
|
408
|
+
|
|
409
|
+
```javascript
|
|
410
|
+
Candy.action({
|
|
411
|
+
navigate: {
|
|
412
|
+
update: 'main',
|
|
413
|
+
on: function(page, variables) {
|
|
414
|
+
// Dispatch to Redux
|
|
415
|
+
store.dispatch({
|
|
416
|
+
type: 'PAGE_CHANGED',
|
|
417
|
+
payload: {page, variables}
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Testing
|
|
425
|
+
|
|
426
|
+
### Unit Testing
|
|
427
|
+
|
|
428
|
+
Test navigation logic:
|
|
429
|
+
|
|
430
|
+
```javascript
|
|
431
|
+
describe('Navigation', () => {
|
|
432
|
+
it('should update active nav on page change', () => {
|
|
433
|
+
Candy.action({
|
|
434
|
+
navigate: {
|
|
435
|
+
update: 'main',
|
|
436
|
+
on: (page) => {
|
|
437
|
+
updateActiveNav(page)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
// Simulate navigation
|
|
443
|
+
Candy.load('/about')
|
|
444
|
+
|
|
445
|
+
// Assert
|
|
446
|
+
expect(document.querySelector('.nav-link.active').href)
|
|
447
|
+
.toContain('/about')
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Best Practices
|
|
453
|
+
|
|
454
|
+
1. **Error Handling**: Always handle failed requests
|
|
455
|
+
2. **Loading States**: Show visual feedback during navigation
|
|
456
|
+
3. **Accessibility**: Announce page changes to screen readers
|
|
457
|
+
4. **Performance**: Minimize DOM updates
|
|
458
|
+
5. **SEO**: Ensure content is crawlable
|
|
459
|
+
6. **Progressive Enhancement**: Work without JavaScript
|
|
460
|
+
|
|
461
|
+
## Common Patterns
|
|
462
|
+
|
|
463
|
+
### Dashboard Navigation
|
|
464
|
+
|
|
465
|
+
```javascript
|
|
466
|
+
Candy.action({
|
|
467
|
+
navigate: {
|
|
468
|
+
links: '.sidebar a, .breadcrumb a',
|
|
469
|
+
update: {
|
|
470
|
+
content: '#dashboard-content',
|
|
471
|
+
breadcrumb: '.breadcrumb',
|
|
472
|
+
stats: '#stats-widget'
|
|
473
|
+
},
|
|
474
|
+
on: (page, vars) => {
|
|
475
|
+
updateSidebar(page)
|
|
476
|
+
updateStats(vars.stats)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
})
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### E-commerce
|
|
483
|
+
|
|
484
|
+
```javascript
|
|
485
|
+
Candy.action({
|
|
486
|
+
navigate: {
|
|
487
|
+
update: {
|
|
488
|
+
content: 'main',
|
|
489
|
+
cart: '#cart-widget'
|
|
490
|
+
},
|
|
491
|
+
on: (page, vars) => {
|
|
492
|
+
updateCartCount(vars.cartItems)
|
|
493
|
+
trackPageView(page)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
})
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Blog
|
|
500
|
+
|
|
501
|
+
```javascript
|
|
502
|
+
Candy.action({
|
|
503
|
+
navigate: {
|
|
504
|
+
update: 'main',
|
|
505
|
+
on: (page, vars) => {
|
|
506
|
+
if (page === 'post') {
|
|
507
|
+
initComments()
|
|
508
|
+
highlightCode()
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
})
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
## Next Steps
|
|
516
|
+
|
|
517
|
+
- Learn about [Form Handling](../03-forms/01-form-handling.md)
|
|
518
|
+
- Explore [API Requests](../04-api-requests/01-get-post.md)
|
|
519
|
+
- Check [candy.js Overview](../01-overview/01-introduction.md)
|