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,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
|
+
```
|