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.
Files changed (213) hide show
  1. package/.editorconfig +21 -0
  2. package/.github/workflows/auto-pr-description.yml +49 -0
  3. package/.github/workflows/release.yml +32 -0
  4. package/.github/workflows/test-coverage.yml +58 -0
  5. package/.husky/pre-commit +2 -0
  6. package/.kiro/steering/code-style.md +56 -0
  7. package/.kiro/steering/product.md +20 -0
  8. package/.kiro/steering/structure.md +77 -0
  9. package/.kiro/steering/tech.md +87 -0
  10. package/.prettierrc +10 -0
  11. package/.releaserc.js +134 -0
  12. package/AGENTS.md +84 -0
  13. package/CHANGELOG.md +181 -0
  14. package/CODE_OF_CONDUCT.md +83 -0
  15. package/CONTRIBUTING.md +63 -0
  16. package/LICENSE +661 -0
  17. package/README.md +57 -0
  18. package/SECURITY.md +26 -0
  19. package/bin/candy +10 -0
  20. package/bin/candypack +10 -0
  21. package/cli/index.js +3 -0
  22. package/cli/src/Cli.js +348 -0
  23. package/cli/src/Connector.js +93 -0
  24. package/cli/src/Monitor.js +416 -0
  25. package/core/Candy.js +87 -0
  26. package/core/Commands.js +239 -0
  27. package/core/Config.js +1094 -0
  28. package/core/Lang.js +52 -0
  29. package/core/Log.js +43 -0
  30. package/core/Process.js +26 -0
  31. package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
  32. package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
  33. package/docs/backend/01-overview/03-development-server.md +79 -0
  34. package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
  35. package/docs/backend/03-config/00-configuration-overview.md +214 -0
  36. package/docs/backend/03-config/01-database-connection.md +60 -0
  37. package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
  38. package/docs/backend/03-config/03-request-timeout.md +11 -0
  39. package/docs/backend/03-config/04-environment-variables.md +227 -0
  40. package/docs/backend/03-config/05-early-hints.md +352 -0
  41. package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
  42. package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
  43. package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
  44. package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
  45. package/docs/backend/04-routing/05-advanced-routing.md +14 -0
  46. package/docs/backend/04-routing/06-error-pages.md +101 -0
  47. package/docs/backend/04-routing/07-cron-jobs.md +149 -0
  48. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
  49. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
  50. package/docs/backend/05-controllers/03-controller-classes.md +93 -0
  51. package/docs/backend/05-forms/01-custom-forms.md +395 -0
  52. package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
  53. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
  54. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
  55. package/docs/backend/07-views/01-the-view-directory.md +73 -0
  56. package/docs/backend/07-views/02-rendering-a-view.md +179 -0
  57. package/docs/backend/07-views/03-template-syntax.md +181 -0
  58. package/docs/backend/07-views/03-variables.md +328 -0
  59. package/docs/backend/07-views/04-request-data.md +231 -0
  60. package/docs/backend/07-views/05-conditionals.md +290 -0
  61. package/docs/backend/07-views/06-loops.md +353 -0
  62. package/docs/backend/07-views/07-translations.md +358 -0
  63. package/docs/backend/07-views/08-backend-javascript.md +398 -0
  64. package/docs/backend/07-views/09-comments.md +297 -0
  65. package/docs/backend/08-database/01-database-connection.md +99 -0
  66. package/docs/backend/08-database/02-using-mysql.md +322 -0
  67. package/docs/backend/09-validation/01-the-validator-service.md +424 -0
  68. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
  69. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
  70. package/docs/backend/10-authentication/03-register.md +134 -0
  71. package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
  72. package/docs/backend/10-authentication/05-session-management.md +159 -0
  73. package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
  74. package/docs/backend/11-mail/01-the-mail-service.md +42 -0
  75. package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
  76. package/docs/backend/13-utilities/01-candy-var.md +504 -0
  77. package/docs/frontend/01-overview/01-introduction.md +146 -0
  78. package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
  79. package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
  80. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
  81. package/docs/frontend/03-forms/01-form-handling.md +420 -0
  82. package/docs/frontend/04-api-requests/01-get-post.md +443 -0
  83. package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
  84. package/docs/index.json +452 -0
  85. package/docs/server/01-installation/01-quick-install.md +19 -0
  86. package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
  87. package/docs/server/02-get-started/01-core-concepts.md +7 -0
  88. package/docs/server/02-get-started/02-basic-commands.md +57 -0
  89. package/docs/server/02-get-started/03-cli-reference.md +276 -0
  90. package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
  91. package/docs/server/03-service/01-start-a-new-service.md +57 -0
  92. package/docs/server/03-service/02-delete-a-service.md +48 -0
  93. package/docs/server/04-web/01-create-a-website.md +36 -0
  94. package/docs/server/04-web/02-list-websites.md +9 -0
  95. package/docs/server/04-web/03-delete-a-website.md +29 -0
  96. package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
  97. package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
  98. package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
  99. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
  100. package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
  101. package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
  102. package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
  103. package/docs/server/07-mail/04-change-account-password.md +23 -0
  104. package/eslint.config.mjs +120 -0
  105. package/framework/index.js +4 -0
  106. package/framework/src/Auth.js +309 -0
  107. package/framework/src/Candy.js +81 -0
  108. package/framework/src/Config.js +79 -0
  109. package/framework/src/Env.js +60 -0
  110. package/framework/src/Lang.js +57 -0
  111. package/framework/src/Mail.js +83 -0
  112. package/framework/src/Mysql.js +575 -0
  113. package/framework/src/Request.js +301 -0
  114. package/framework/src/Route/Cron.js +128 -0
  115. package/framework/src/Route/Internal.js +439 -0
  116. package/framework/src/Route.js +455 -0
  117. package/framework/src/Server.js +15 -0
  118. package/framework/src/Stream.js +163 -0
  119. package/framework/src/Token.js +37 -0
  120. package/framework/src/Validator.js +271 -0
  121. package/framework/src/Var.js +211 -0
  122. package/framework/src/View/EarlyHints.js +190 -0
  123. package/framework/src/View/Form.js +600 -0
  124. package/framework/src/View.js +513 -0
  125. package/framework/web/candy.js +838 -0
  126. package/jest.config.js +22 -0
  127. package/locale/de-DE.json +80 -0
  128. package/locale/en-US.json +79 -0
  129. package/locale/es-ES.json +80 -0
  130. package/locale/fr-FR.json +80 -0
  131. package/locale/pt-BR.json +80 -0
  132. package/locale/ru-RU.json +80 -0
  133. package/locale/tr-TR.json +85 -0
  134. package/locale/zh-CN.json +80 -0
  135. package/package.json +86 -0
  136. package/server/index.js +5 -0
  137. package/server/src/Api.js +88 -0
  138. package/server/src/DNS.js +940 -0
  139. package/server/src/Hub.js +535 -0
  140. package/server/src/Mail.js +571 -0
  141. package/server/src/SSL.js +180 -0
  142. package/server/src/Server.js +27 -0
  143. package/server/src/Service.js +248 -0
  144. package/server/src/Subdomain.js +64 -0
  145. package/server/src/Web/Firewall.js +170 -0
  146. package/server/src/Web/Proxy.js +134 -0
  147. package/server/src/Web.js +451 -0
  148. package/server/src/mail/imap.js +1091 -0
  149. package/server/src/mail/server.js +32 -0
  150. package/server/src/mail/smtp.js +786 -0
  151. package/test/cli/Cli.test.js +36 -0
  152. package/test/core/Candy.test.js +234 -0
  153. package/test/core/Commands.test.js +538 -0
  154. package/test/core/Config.test.js +1435 -0
  155. package/test/core/Lang.test.js +250 -0
  156. package/test/core/Process.test.js +156 -0
  157. package/test/framework/Route.test.js +239 -0
  158. package/test/framework/View/EarlyHints.test.js +282 -0
  159. package/test/scripts/check-coverage.js +132 -0
  160. package/test/server/Api.test.js +647 -0
  161. package/test/server/Client.test.js +338 -0
  162. package/test/server/DNS.test.js +2050 -0
  163. package/test/server/DNS.test.js.bak +2084 -0
  164. package/test/server/Log.test.js +73 -0
  165. package/test/server/Mail.account.test_.js +460 -0
  166. package/test/server/Mail.init.test_.js +411 -0
  167. package/test/server/Mail.test_.js +1340 -0
  168. package/test/server/SSL.test_.js +1491 -0
  169. package/test/server/Server.test.js +765 -0
  170. package/test/server/Service.test_.js +1127 -0
  171. package/test/server/Subdomain.test.js +440 -0
  172. package/test/server/Web/Firewall.test.js +175 -0
  173. package/test/server/Web.test_.js +1562 -0
  174. package/test/server/__mocks__/acme-client.js +17 -0
  175. package/test/server/__mocks__/bcrypt.js +50 -0
  176. package/test/server/__mocks__/child_process.js +389 -0
  177. package/test/server/__mocks__/crypto.js +432 -0
  178. package/test/server/__mocks__/fs.js +450 -0
  179. package/test/server/__mocks__/globalCandy.js +227 -0
  180. package/test/server/__mocks__/http-proxy.js +105 -0
  181. package/test/server/__mocks__/http.js +575 -0
  182. package/test/server/__mocks__/https.js +272 -0
  183. package/test/server/__mocks__/index.js +249 -0
  184. package/test/server/__mocks__/mail/server.js +100 -0
  185. package/test/server/__mocks__/mail/smtp.js +31 -0
  186. package/test/server/__mocks__/mailparser.js +81 -0
  187. package/test/server/__mocks__/net.js +369 -0
  188. package/test/server/__mocks__/node-forge.js +328 -0
  189. package/test/server/__mocks__/os.js +320 -0
  190. package/test/server/__mocks__/path.js +291 -0
  191. package/test/server/__mocks__/selfsigned.js +8 -0
  192. package/test/server/__mocks__/server/src/mail/server.js +100 -0
  193. package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
  194. package/test/server/__mocks__/smtp-server.js +106 -0
  195. package/test/server/__mocks__/sqlite3.js +394 -0
  196. package/test/server/__mocks__/testFactories.js +299 -0
  197. package/test/server/__mocks__/testHelpers.js +363 -0
  198. package/test/server/__mocks__/tls.js +229 -0
  199. package/watchdog/index.js +3 -0
  200. package/watchdog/src/Watchdog.js +156 -0
  201. package/web/config.json +5 -0
  202. package/web/controller/page/about.js +27 -0
  203. package/web/controller/page/index.js +34 -0
  204. package/web/package.json +18 -0
  205. package/web/public/assets/css/style.css +1835 -0
  206. package/web/public/assets/js/app.js +96 -0
  207. package/web/route/www.js +19 -0
  208. package/web/skeleton/main.html +22 -0
  209. package/web/view/content/about.html +65 -0
  210. package/web/view/content/home.html +205 -0
  211. package/web/view/footer/main.html +11 -0
  212. package/web/view/head/main.html +5 -0
  213. 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