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,328 @@
1
+ ## 📦 Variables in Views
2
+
3
+ Variables allow you to display dynamic data in your templates. Data is passed from controllers to views using `Candy.set()` and displayed using the `<candy var>` tag.
4
+
5
+ ### Passing Data from Controller
6
+
7
+ Use `Candy.set()` in your controller to pass data to views:
8
+
9
+ ```javascript
10
+ // Controller: controller/profile.js
11
+ module.exports = async function(Candy) {
12
+ // Set single variable
13
+ Candy.set('username', 'John Doe')
14
+
15
+ // Set multiple variables at once
16
+ Candy.set({
17
+ user: {
18
+ name: 'John Doe',
19
+ email: 'john@example.com',
20
+ role: 'admin'
21
+ },
22
+ pageTitle: 'User Profile'
23
+ })
24
+
25
+ Candy.View.skeleton('main').set('content', 'profile')
26
+ }
27
+ ```
28
+
29
+ ### Displaying Variables
30
+
31
+ #### HTML-Safe Output (Recommended)
32
+
33
+ ```html
34
+ <candy var="username" />
35
+ <candy var="user.email" />
36
+ <candy var="product.price" />
37
+ ```
38
+
39
+ This automatically:
40
+ - Escapes HTML to prevent XSS attacks
41
+ - Converts newlines (`\n`) to `<br>` tags
42
+
43
+ **Example:**
44
+ ```javascript
45
+ // Controller
46
+ Candy.set('message', 'Hello\nWorld')
47
+ ```
48
+
49
+ ```html
50
+ <!-- View -->
51
+ <candy var="message" />
52
+ <!-- Output: Hello<br>World -->
53
+ ```
54
+
55
+ #### Raw HTML Output
56
+
57
+ When you need to display HTML content without escaping:
58
+
59
+ ```html
60
+ <candy var="htmlContent" raw />
61
+ <candy var="user.bio" raw />
62
+ ```
63
+
64
+ **Security Warning:** Only use `raw` with trusted content. Never use it with user-generated content to prevent XSS attacks.
65
+
66
+ **Example:**
67
+ ```javascript
68
+ // Controller
69
+ Candy.set('content', '<strong>Bold text</strong>')
70
+ ```
71
+
72
+ ```html
73
+ <!-- View -->
74
+ <candy var="content" raw />
75
+ <!-- Output: <strong>Bold text</strong> -->
76
+ ```
77
+
78
+ ### Accessing Nested Properties
79
+
80
+ You can access nested object properties using dot notation:
81
+
82
+ ```javascript
83
+ // Controller
84
+ Candy.set('user', {
85
+ name: 'John',
86
+ profile: {
87
+ email: 'john@example.com',
88
+ address: {
89
+ city: 'Istanbul'
90
+ }
91
+ }
92
+ })
93
+ ```
94
+
95
+ ```html
96
+ <!-- View -->
97
+ <p>Name: <candy var="user.name" /></p>
98
+ <p>Email: <candy var="user.profile.email" /></p>
99
+ <p>City: <candy var="user.profile.address.city" /></p>
100
+ ```
101
+
102
+ ### String Literals
103
+
104
+ Display static text directly:
105
+
106
+ ```html
107
+ <candy>Hello World</candy>
108
+ <candy>Welcome to our site</candy>
109
+ ```
110
+
111
+ This is useful when you want consistent syntax throughout your templates.
112
+
113
+ ### Accessing the Candy Object
114
+
115
+ You have full access to the `Candy` object within templates:
116
+
117
+ ```html
118
+ <!-- Authentication -->
119
+ <candy:if condition="Candy.Auth.check()">
120
+ <p>User ID: <candy var="Candy.Auth.user().id" /></p>
121
+ <p>Email: <candy var="Candy.Auth.user().email" /></p>
122
+ </candy:if>
123
+
124
+ <!-- Request Information -->
125
+ <p>Method: <candy var="Candy.Request.method" /></p>
126
+ <p>URL: <candy var="Candy.Request.url" /></p>
127
+ <p>IP: <candy var="Candy.Request.ip" /></p>
128
+
129
+ <!-- Configuration -->
130
+ <candy:if condition="Candy.Config.debug">
131
+ <div class="debug-info">Debug mode enabled</div>
132
+ </candy:if>
133
+ ```
134
+
135
+ ### Practical Examples
136
+
137
+ #### User Profile Card
138
+
139
+ ```javascript
140
+ // Controller: controller/profile.js
141
+ module.exports = async function(Candy) {
142
+ // Fetch user from database
143
+ const userId = Candy.Request.get('id')
144
+ const user = await Candy.Mysql.table('users')
145
+ .where('id', userId)
146
+ .first()
147
+
148
+ // Pass to view
149
+ Candy.set('user', {
150
+ name: user.name,
151
+ email: user.email,
152
+ bio: user.bio,
153
+ isVerified: user.verified
154
+ })
155
+
156
+ Candy.View.skeleton('main').set('content', 'profile')
157
+ }
158
+ ```
159
+
160
+ ```html
161
+ <!-- View: view/content/profile.html -->
162
+ <div class="profile-card">
163
+ <h2><candy var="user.name" /></h2>
164
+ <p><candy var="user.email" /></p>
165
+
166
+ <candy:if condition="user.isVerified">
167
+ <span class="badge">✓ Verified</span>
168
+ </candy:if>
169
+
170
+ <div class="bio">
171
+ <candy var="user.bio" raw />
172
+ </div>
173
+ </div>
174
+ ```
175
+
176
+ #### Product Display with Computed Values
177
+
178
+ ```javascript
179
+ // Controller: controller/product.js
180
+ module.exports = async function(Candy) {
181
+ const productId = Candy.Request.get('id')
182
+ const product = await Candy.Mysql.table('products')
183
+ .where('id', productId)
184
+ .first()
185
+
186
+ // Compute values in controller
187
+ const hasDiscount = product.discount > 0
188
+ const finalPrice = product.price * (1 - product.discount / 100)
189
+
190
+ Candy.set({
191
+ product: product,
192
+ hasDiscount: hasDiscount,
193
+ finalPrice: finalPrice
194
+ })
195
+
196
+ Candy.View.skeleton('main').set('content', 'product')
197
+ }
198
+ ```
199
+
200
+ ```html
201
+ <!-- View: view/content/product.html -->
202
+ <div class="product">
203
+ <h1><candy var="product.name" /></h1>
204
+
205
+ <candy:if condition="hasDiscount">
206
+ <p class="original-price">$<candy var="product.price" /></p>
207
+ <p class="final-price">$<candy var="finalPrice" /></p>
208
+ <span class="discount">-<candy var="product.discount" />%</span>
209
+ <candy:else>
210
+ <p class="price">$<candy var="product.price" /></p>
211
+ </candy:if>
212
+
213
+ <div class="description">
214
+ <candy var="product.description" />
215
+ </div>
216
+ </div>
217
+ ```
218
+
219
+ #### Working with Arrays
220
+
221
+ ```javascript
222
+ // Controller: controller/products.js
223
+ module.exports = async function(Candy) {
224
+ const products = await Candy.Mysql.table('products')
225
+ .where('active', true)
226
+ .get()
227
+
228
+ Candy.set({
229
+ products: products,
230
+ totalProducts: products.length
231
+ })
232
+
233
+ Candy.View.skeleton('main').set('content', 'products')
234
+ }
235
+ ```
236
+
237
+ ```html
238
+ <!-- View: view/content/products.html -->
239
+ <h1>Products (<candy var="totalProducts" />)</h1>
240
+
241
+ <div class="products-grid">
242
+ <candy:for in="products" value="product">
243
+ <div class="product-card">
244
+ <h3><candy var="product.name" /></h3>
245
+ <p>$<candy var="product.price" /></p>
246
+ </div>
247
+ </candy:for>
248
+ </div>
249
+ ```
250
+
251
+ ### Best Practices
252
+
253
+ 1. **Always use Candy.set()**: Pass all data through `Candy.set()` for consistency
254
+ 2. **Set data before rendering**: All `Candy.set()` calls should come before `Candy.View.set()`
255
+ 3. **Compute in controller**: Do calculations in the controller, not in views
256
+ 4. **Use descriptive names**: `pageTitle`, `userProfile` instead of `title`, `data`
257
+ 5. **Group related data**: Use objects to organize related data
258
+
259
+ **Good:**
260
+ ```javascript
261
+ // Controller
262
+ const user = await Candy.Mysql.table('users').first()
263
+ const isAdmin = user.role === 'admin'
264
+
265
+ Candy.set({
266
+ user: user,
267
+ isAdmin: isAdmin
268
+ })
269
+ ```
270
+
271
+ **Avoid:**
272
+ ```html
273
+ <!-- Don't do complex logic in views -->
274
+ <candy:if condition="user.role === 'admin' && user.verified && !user.banned">
275
+ ...
276
+ </candy:if>
277
+ ```
278
+
279
+ ### Error Handling
280
+
281
+ Always handle cases where data might not exist:
282
+
283
+ ```javascript
284
+ // Controller
285
+ module.exports = async function(Candy) {
286
+ const productId = Candy.Request.get('id')
287
+ const product = await Candy.Mysql.table('products')
288
+ .where('id', productId)
289
+ .first()
290
+
291
+ if (!product) {
292
+ Candy.set('error', 'Product not found')
293
+ } else {
294
+ Candy.set('product', product)
295
+ }
296
+
297
+ Candy.View.skeleton('main').set('content', 'product')
298
+ }
299
+ ```
300
+
301
+ ```html
302
+ <!-- View -->
303
+ <candy:if condition="error">
304
+ <div class="alert alert-danger">
305
+ <candy var="error" />
306
+ </div>
307
+ <candy:else>
308
+ <div class="product">
309
+ <h1><candy var="product.name" /></h1>
310
+ </div>
311
+ </candy:if>
312
+ ```
313
+
314
+ ### Legacy Syntax (Backward Compatibility)
315
+
316
+ CandyPack also supports legacy syntax:
317
+
318
+ ```html
319
+ <!-- HTML-safe output -->
320
+ {{ username }}
321
+ {{ user.email }}
322
+
323
+ <!-- Raw HTML output -->
324
+ {!! htmlContent !!}
325
+ {!! user.bio !!}
326
+ ```
327
+
328
+ **Note:** The new `<candy>` tag syntax is recommended for all new projects as it provides better IDE support and readability.
@@ -0,0 +1,231 @@
1
+ ## 🔗 Request Data (Query Parameters)
2
+
3
+ The `<candy get>` tag allows you to access URL query parameters directly in your views. This is useful for forms, filters, and pagination.
4
+
5
+ ### Getting Query Parameters
6
+
7
+ Use `<candy get="key" />` to access URL query parameters:
8
+
9
+ **Important:** `<candy get>` is for **query parameters** (URL parameters), not for data from controllers. For controller data, use `<candy var>` (see [Variables](./03-variables.md)).
10
+
11
+ ```html
12
+ <!-- URL: /search?q=laptop&page=2 -->
13
+
14
+ <p>Search query: <candy get="q" /></p>
15
+ <p>Current page: <candy get="page" /></p>
16
+ ```
17
+
18
+ **How it works:**
19
+ 1. User visits `/search?q=laptop&page=2`
20
+ 2. `<candy get="q" />` retrieves the value of `q` parameter
21
+ 3. If parameter doesn't exist, it returns empty string (no error)
22
+
23
+ ### Undefined Parameters
24
+
25
+ If a parameter doesn't exist, it safely returns an empty string:
26
+
27
+ ```html
28
+ <!-- URL: /products (no query parameters) -->
29
+
30
+ <candy get="search" />
31
+ <!-- Output: (empty string, no error) -->
32
+ ```
33
+
34
+ This prevents errors when parameters are optional.
35
+
36
+ ### Difference: get vs var
37
+
38
+ **`<candy get>` - Query Parameters (from URL):**
39
+ ```html
40
+ <!-- URL: /search?q=laptop -->
41
+ <candy get="q" />
42
+ <!-- Output: laptop -->
43
+ ```
44
+
45
+ **`<candy var>` - Controller Data (from Candy.set()):**
46
+ ```javascript
47
+ // Controller
48
+ Candy.set('productName', 'Laptop')
49
+ ```
50
+ ```html
51
+ <!-- View -->
52
+ <candy var="productName" />
53
+ <!-- Output: Laptop -->
54
+ ```
55
+
56
+ ### Processing Request Data in Controllers
57
+
58
+ While you can access query parameters directly in views with `<candy get>`, it's often better to process them in the controller:
59
+
60
+ ```javascript
61
+ // Controller: controller/search.js
62
+ module.exports = async function(Candy) {
63
+ // Get query parameters
64
+ const query = Candy.Request.get('q') || 'all products'
65
+ const page = parseInt(Candy.Request.get('page')) || 1
66
+
67
+ // Validate and process
68
+ const validatedQuery = query.trim()
69
+ const validatedPage = Math.max(1, page)
70
+
71
+ // Fetch results
72
+ const results = await Candy.Mysql.table('products')
73
+ .where('name', 'like', `%${validatedQuery}%`)
74
+ .limit(20)
75
+ .offset((validatedPage - 1) * 20)
76
+ .get()
77
+
78
+ // Pass processed data to view
79
+ Candy.set({
80
+ query: validatedQuery,
81
+ page: validatedPage,
82
+ results: results
83
+ })
84
+
85
+ Candy.View.skeleton('main').set('content', 'search')
86
+ }
87
+ ```
88
+
89
+ ```html
90
+ <!-- View: view/content/search.html -->
91
+ <h1>Search Results for "<candy var="query" />"</h1>
92
+ <p>Page <candy var="page" /></p>
93
+
94
+ <candy:for in="results" value="product">
95
+ <div class="product">
96
+ <h3><candy var="product.name" /></h3>
97
+ <p><candy var="product.price" /></p>
98
+ </div>
99
+ </candy:for>
100
+ ```
101
+
102
+ ### Accessing Request Object
103
+
104
+ You can access the full Request object through the Candy object:
105
+
106
+ ```html
107
+ <!-- Request method -->
108
+ <p>Method: <candy var="Candy.Request.method" /></p>
109
+
110
+ <!-- Current URL -->
111
+ <p>URL: <candy var="Candy.Request.url" /></p>
112
+
113
+ <!-- Client IP -->
114
+ <p>IP: <candy var="Candy.Request.ip" /></p>
115
+
116
+ <!-- User agent -->
117
+ <p>Browser: <candy var="Candy.Request.headers['user-agent']" /></p>
118
+ ```
119
+
120
+ ### Practical Examples
121
+
122
+ #### Search Form with Results
123
+
124
+ ```html
125
+ <!-- Search form -->
126
+ <form action="/search" method="GET">
127
+ <input
128
+ type="text"
129
+ name="q"
130
+ value="<candy get="q" />"
131
+ placeholder="Search products..."
132
+ >
133
+ <button type="submit">Search</button>
134
+ </form>
135
+
136
+ <!-- Display search query if exists -->
137
+ <candy:if condition="Candy.Request.get('q')">
138
+ <p>Showing results for: "<candy get="q" />"</p>
139
+ </candy:if>
140
+ ```
141
+
142
+ #### Pagination
143
+
144
+ ```html
145
+ <script:candy>
146
+ const currentPage = parseInt(Candy.Request.get('page')) || 1
147
+ const totalPages = 10
148
+ </script:candy>
149
+
150
+ <div class="pagination">
151
+ <candy:if condition="currentPage > 1">
152
+ <a href="?page=<candy var="currentPage - 1" />">Previous</a>
153
+ </candy:if>
154
+
155
+ <span>Page <candy var="currentPage" /> of <candy var="totalPages" /></span>
156
+
157
+ <candy:if condition="currentPage < totalPages">
158
+ <a href="?page=<candy var="currentPage + 1" />">Next</a>
159
+ </candy:if>
160
+ </div>
161
+ ```
162
+
163
+ #### Filter Form
164
+
165
+ ```html
166
+ <!-- URL: /products?category=electronics&sort=price&order=asc -->
167
+
168
+ <form action="/products" method="GET">
169
+ <select name="category">
170
+ <option value="">All Categories</option>
171
+ <option value="electronics" <candy:if condition="Candy.Request.get('category') === 'electronics'">selected</candy:if>>
172
+ Electronics
173
+ </option>
174
+ <option value="clothing" <candy:if condition="Candy.Request.get('category') === 'clothing'">selected</candy:if>>
175
+ Clothing
176
+ </option>
177
+ </select>
178
+
179
+ <select name="sort">
180
+ <option value="name" <candy:if condition="Candy.Request.get('sort') === 'name'">selected</candy:if>>
181
+ Name
182
+ </option>
183
+ <option value="price" <candy:if condition="Candy.Request.get('sort') === 'price'">selected</candy:if>>
184
+ Price
185
+ </option>
186
+ </select>
187
+
188
+ <button type="submit">Filter</button>
189
+ </form>
190
+ ```
191
+
192
+ #### Active Navigation
193
+
194
+ ```html
195
+ <nav>
196
+ <a href="/" class="<candy:if condition="Candy.Request.url === '/'">active</candy:if>">
197
+ Home
198
+ </a>
199
+ <a href="/products" class="<candy:if condition="Candy.Request.url.startsWith('/products')">active</candy:if>">
200
+ Products
201
+ </a>
202
+ <a href="/about" class="<candy:if condition="Candy.Request.url === '/about'">active</candy:if>">
203
+ About
204
+ </a>
205
+ </nav>
206
+ ```
207
+
208
+ ### Best Practices
209
+
210
+ 1. **Validate in Controller**: Always validate and sanitize request data in the controller before using it
211
+ 2. **Default Values**: Provide default values for optional parameters
212
+ 3. **Type Conversion**: Convert string parameters to appropriate types (numbers, booleans)
213
+ 4. **Security**: Never trust user input - always validate and escape
214
+
215
+ **Good:**
216
+ ```javascript
217
+ // Controller
218
+ const page = Math.max(1, parseInt(Candy.Request.get('page')) || 1)
219
+ const limit = Math.min(100, parseInt(Candy.Request.get('limit')) || 20)
220
+
221
+ Candy.set('page', page)
222
+ Candy.set('limit', limit)
223
+ ```
224
+
225
+ **Avoid:**
226
+ ```html
227
+ <!-- Don't do complex logic in views -->
228
+ <candy:if condition="parseInt(Candy.Request.get('page')) > 0 && parseInt(Candy.Request.get('page')) < 100">
229
+ ...
230
+ </candy:if>
231
+ ```