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
package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
## 📥 The Request Object
|
|
2
|
+
|
|
3
|
+
The `Candy.Request` object contains information about the user's incoming request.
|
|
4
|
+
|
|
5
|
+
### Getting Request Parameters
|
|
6
|
+
|
|
7
|
+
#### Using Candy.request() (Recommended)
|
|
8
|
+
|
|
9
|
+
The easiest way to get request parameters is using `Candy.request()`:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
module.exports = async function (Candy) {
|
|
13
|
+
// Get parameter from GET or POST automatically
|
|
14
|
+
const userName = await Candy.request('name')
|
|
15
|
+
const userId = await Candy.request('id')
|
|
16
|
+
|
|
17
|
+
return `Hello ${userName}!`
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Specify Method (Optional):**
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
module.exports = async function (Candy) {
|
|
25
|
+
// Get from GET parameters only
|
|
26
|
+
const searchQuery = await Candy.request('q', 'GET')
|
|
27
|
+
|
|
28
|
+
// Get from POST parameters only
|
|
29
|
+
const formName = await Candy.request('name', 'POST')
|
|
30
|
+
|
|
31
|
+
return `Searching for: ${searchQuery}`
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Direct Access
|
|
36
|
+
|
|
37
|
+
You can also access request data directly:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
module.exports = function (Candy) {
|
|
41
|
+
// GET parameters (URL query string like ?id=123)
|
|
42
|
+
const userId = Candy.Request.get('id')
|
|
43
|
+
|
|
44
|
+
// POST parameters (form data)
|
|
45
|
+
const userName = Candy.Request.post('name')
|
|
46
|
+
|
|
47
|
+
return `User: ${userName}`
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Request Properties
|
|
52
|
+
|
|
53
|
+
* `Candy.Request.method` - HTTP method ('GET', 'POST', etc.)
|
|
54
|
+
* `Candy.Request.url` - Full URL the user visited
|
|
55
|
+
* `Candy.Request.host` - Website's hostname
|
|
56
|
+
* `Candy.Request.ip` - User's IP address
|
|
57
|
+
* `Candy.Request.ssl` - Whether connection is SSL/HTTPS
|
|
58
|
+
|
|
59
|
+
### Request Headers
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
module.exports = function (Candy) {
|
|
63
|
+
const userAgent = Candy.Request.header('user-agent')
|
|
64
|
+
const contentType = Candy.Request.header('content-type')
|
|
65
|
+
|
|
66
|
+
return `Browser: ${userAgent}`
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Complete Example
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
module.exports = async function (Candy) {
|
|
74
|
+
// Get request parameters
|
|
75
|
+
const productId = await Candy.request('id')
|
|
76
|
+
const quantity = await Candy.request('quantity') || 1
|
|
77
|
+
|
|
78
|
+
// Check request method
|
|
79
|
+
if (Candy.Request.method === 'POST') {
|
|
80
|
+
// Handle form submission
|
|
81
|
+
const result = await processOrder(productId, quantity)
|
|
82
|
+
return { success: true, orderId: result.id }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Show product page
|
|
86
|
+
Candy.set({
|
|
87
|
+
productId: productId,
|
|
88
|
+
quantity: quantity
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
Candy.View.set({
|
|
92
|
+
skeleton: 'main',
|
|
93
|
+
content: 'product.detail'
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
## 📤 Sending a Response: Replying to the User
|
|
2
|
+
|
|
3
|
+
Once you've processed the request, it's time to send something back. You've got a few options.
|
|
4
|
+
|
|
5
|
+
#### The Simple Way: Just Return It!
|
|
6
|
+
|
|
7
|
+
For many cases, you can just `return` a value from your controller. CandyPack is smart enough to figure out what to do.
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// Return some HTML
|
|
11
|
+
module.exports = function (Candy) {
|
|
12
|
+
return '<h1>Welcome to the site!</h1>';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Return some JSON for an API
|
|
16
|
+
module.exports = function (Candy) {
|
|
17
|
+
return { status: 'success', message: 'Your data was saved!' };
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
#### The Helper Functions: More Control
|
|
22
|
+
|
|
23
|
+
Need a bit more control? The `Candy` object has your back.
|
|
24
|
+
|
|
25
|
+
* `Candy.return(data)`: Does the same thing as a direct return, but you can call it from anywhere in your function. It stops everything and sends the response immediately.
|
|
26
|
+
* `Candy.direct(url)`: Need to send the user to a different page? This function performs a redirect, telling the user's browser to go to a new URL.
|
|
27
|
+
|
|
28
|
+
**Example:**
|
|
29
|
+
```javascript
|
|
30
|
+
module.exports = function (Candy) {
|
|
31
|
+
// If the user isn't logged in...
|
|
32
|
+
if (!Candy.Auth.isLogin()) {
|
|
33
|
+
// ...send them to the login page!
|
|
34
|
+
return Candy.direct('/login');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Otherwise, give them their data.
|
|
38
|
+
Candy.return({ data: 'here is your secret stuff' });
|
|
39
|
+
}
|
|
40
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
## 📁 View System Overview
|
|
2
|
+
|
|
3
|
+
CandyPack's view system creates dynamic HTML pages by combining skeleton (layout) and view (content) files. This system provides a modular structure by keeping page layout and content separate.
|
|
4
|
+
|
|
5
|
+
### Directory Structure
|
|
6
|
+
|
|
7
|
+
Your project uses two main directories:
|
|
8
|
+
|
|
9
|
+
- `skeleton/` - Main page skeletons (layout files)
|
|
10
|
+
- `view/` - Page contents and components
|
|
11
|
+
|
|
12
|
+
### Skeleton Files
|
|
13
|
+
|
|
14
|
+
Skeleton files define the overall structure of your page. They contain the basic HTML structure including head and body, and host placeholders for content.
|
|
15
|
+
|
|
16
|
+
Example: `skeleton/main.html`
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<!DOCTYPE html>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="UTF-8">
|
|
23
|
+
<title>My Website</title>
|
|
24
|
+
<meta name="description" content="Welcome to my website">
|
|
25
|
+
<link rel="stylesheet" href="/assets/css/style.css">
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<header>
|
|
29
|
+
{{ HEADER }}
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
<main>
|
|
33
|
+
{{ CONTENT }}
|
|
34
|
+
</main>
|
|
35
|
+
|
|
36
|
+
<footer>
|
|
37
|
+
{{ FOOTER }}
|
|
38
|
+
</footer>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Important Rules for Placeholders:**
|
|
44
|
+
|
|
45
|
+
1. **Each placeholder must be wrapped in HTML tags** - This allows AJAX to identify and update specific sections
|
|
46
|
+
2. **Never place placeholders directly next to each other** - Bad: `{{ HEADER }}{{ CONTENT }}`, Good: `<header>{{ HEADER }}</header><main>{{ CONTENT }}</main>`
|
|
47
|
+
3. **Placeholders are uppercase** - `{{ HEADER }}`, `{{ CONTENT }}`, `{{ FOOTER }}`
|
|
48
|
+
4. **Use semantic HTML tags** - `<header>`, `<main>`, `<footer>`, `<aside>`, `<nav>`, etc.
|
|
49
|
+
|
|
50
|
+
**Why wrap in tags?**
|
|
51
|
+
When using AJAX navigation, the system needs to identify which part of the page to update. HTML tags provide clear boundaries for each section.
|
|
52
|
+
|
|
53
|
+
**Note:** Skeleton files currently support only view part placeholders (uppercase). For dynamic content like page titles, use a view part for the `<head>` section or set them in individual view files.
|
|
54
|
+
|
|
55
|
+
### View Files
|
|
56
|
+
|
|
57
|
+
View files contain the content that will be placed into the placeholders within the skeleton. They are organized under the `view/` directory.
|
|
58
|
+
|
|
59
|
+
Example directory structure:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
view/
|
|
63
|
+
├── header/
|
|
64
|
+
│ ├── main.html
|
|
65
|
+
│ └── dashboard.html
|
|
66
|
+
├── content/
|
|
67
|
+
│ ├── home.html
|
|
68
|
+
│ └── about.html
|
|
69
|
+
└── footer/
|
|
70
|
+
└── main.html
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
## 🎨 Rendering Views
|
|
2
|
+
|
|
3
|
+
In CandyPack, you use the `Candy.View` object to render views. There are two main approaches:
|
|
4
|
+
|
|
5
|
+
### 1. Combining Skeleton and View Parts
|
|
6
|
+
|
|
7
|
+
The most common usage is to select a skeleton and place view parts into it.
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
module.exports = function (Candy) {
|
|
11
|
+
Candy.View
|
|
12
|
+
.skeleton('main') // Use skeleton/main.html
|
|
13
|
+
.set('header', 'main') // Place view/header/main.html into {{ HEADER }}
|
|
14
|
+
.set('content', 'home') // Place view/content/home.html into {{ CONTENT }}
|
|
15
|
+
.set('footer', 'main') // Place view/footer/main.html into {{ FOOTER }}
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 2. Bulk Setting with Object
|
|
20
|
+
|
|
21
|
+
You can set all view parts at once:
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
module.exports = function (Candy) {
|
|
25
|
+
Candy.View.set({
|
|
26
|
+
skeleton: 'main',
|
|
27
|
+
header: 'main',
|
|
28
|
+
content: 'home',
|
|
29
|
+
footer: 'main'
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3. Subdirectories with Dot Notation
|
|
35
|
+
|
|
36
|
+
View files can be organized in subdirectories. You can access them using dot notation:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
Candy.View.set({
|
|
40
|
+
skeleton: 'dashboard',
|
|
41
|
+
header: 'dashboard.main', // view/header/dashboard/main.html
|
|
42
|
+
sidebar: 'dashboard.menu', // view/sidebar/dashboard/menu.html
|
|
43
|
+
content: 'user.profile' // view/content/user/profile.html
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 4. Direct View Rendering from Routes
|
|
48
|
+
|
|
49
|
+
You can render views directly from route files without using a controller:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
// route/www.js
|
|
53
|
+
Candy.Route.page('/about').view({
|
|
54
|
+
skeleton: 'main',
|
|
55
|
+
header: 'main',
|
|
56
|
+
content: 'about',
|
|
57
|
+
footer: 'main'
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 5. All Feature
|
|
62
|
+
|
|
63
|
+
If you're using the same directory structure for all placeholders, you can use the `all()` method:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
Candy.View
|
|
67
|
+
.skeleton('main')
|
|
68
|
+
.all('home') // view/home/header.html, view/home/content.html, view/home/footer.html
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
In this case, placeholders like `{{ HEADER }}`, `{{ CONTENT }}`, `{{ FOOTER }}` in the skeleton are automatically matched with `view/home/header.html`, `view/home/content.html`, `view/home/footer.html` files.
|
|
72
|
+
|
|
73
|
+
### Setting Dynamic Page Titles and Meta Tags
|
|
74
|
+
|
|
75
|
+
Since skeleton files only support view part placeholders, you have two approaches for dynamic titles:
|
|
76
|
+
|
|
77
|
+
#### Approach 1: Include Head as a View Part
|
|
78
|
+
|
|
79
|
+
Create a separate view part for the `<head>` section:
|
|
80
|
+
|
|
81
|
+
**Skeleton (skeleton/main.html):**
|
|
82
|
+
```html
|
|
83
|
+
<!DOCTYPE html>
|
|
84
|
+
<html lang="en">
|
|
85
|
+
<div id="head">
|
|
86
|
+
{{ HEAD }}
|
|
87
|
+
</div>
|
|
88
|
+
<body>
|
|
89
|
+
<header>
|
|
90
|
+
{{ HEADER }}
|
|
91
|
+
</header>
|
|
92
|
+
|
|
93
|
+
<main>
|
|
94
|
+
{{ CONTENT }}
|
|
95
|
+
</main>
|
|
96
|
+
|
|
97
|
+
<footer>
|
|
98
|
+
{{ FOOTER }}
|
|
99
|
+
</footer>
|
|
100
|
+
</body>
|
|
101
|
+
</html>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Note:** Each placeholder is wrapped in an HTML tag so AJAX can identify and update specific sections.
|
|
105
|
+
|
|
106
|
+
**Head View (view/head/main.html):**
|
|
107
|
+
```html
|
|
108
|
+
<head>
|
|
109
|
+
<meta charset="UTF-8">
|
|
110
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
111
|
+
<title>{{ Candy.pageTitle }}</title>
|
|
112
|
+
<meta name="description" content="{{ Candy.pageDescription }}">
|
|
113
|
+
<link rel="stylesheet" href="/assets/css/style.css">
|
|
114
|
+
</head>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Controller:**
|
|
118
|
+
```javascript
|
|
119
|
+
module.exports = async function (Candy) {
|
|
120
|
+
const productId = Candy.Request.get('id')
|
|
121
|
+
const product = await Candy.Mysql.table('products')
|
|
122
|
+
.where('id', productId)
|
|
123
|
+
.first()
|
|
124
|
+
|
|
125
|
+
// Set dynamic title and description
|
|
126
|
+
Candy.pageTitle = product ? `${product.name} - My Store` : 'Product Not Found'
|
|
127
|
+
Candy.pageDescription = product ? product.short_description : ''
|
|
128
|
+
|
|
129
|
+
Candy.product = product
|
|
130
|
+
|
|
131
|
+
Candy.View.set({
|
|
132
|
+
skeleton: 'main',
|
|
133
|
+
head: 'main', // Include dynamic head
|
|
134
|
+
header: 'main',
|
|
135
|
+
content: 'product.detail',
|
|
136
|
+
footer: 'main'
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Approach 2: Set Title in Content View
|
|
142
|
+
|
|
143
|
+
Include the title tag in your content view:
|
|
144
|
+
|
|
145
|
+
**Skeleton (skeleton/simple.html):**
|
|
146
|
+
```html
|
|
147
|
+
<!DOCTYPE html>
|
|
148
|
+
<html lang="en">
|
|
149
|
+
<head>
|
|
150
|
+
<meta charset="UTF-8">
|
|
151
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
152
|
+
<link rel="stylesheet" href="/assets/css/style.css">
|
|
153
|
+
</head>
|
|
154
|
+
<body>
|
|
155
|
+
{{ CONTENT }}
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Content View (view/content/product.html):**
|
|
161
|
+
```html
|
|
162
|
+
<title>{{ Candy.product.name }} - My Store</title>
|
|
163
|
+
|
|
164
|
+
<div class="product">
|
|
165
|
+
<h1>{{ Candy.product.name }}</h1>
|
|
166
|
+
<p>{{ Candy.product.description }}</p>
|
|
167
|
+
</div>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Note:** This approach is less clean but works for simple cases.
|
|
171
|
+
|
|
172
|
+
### Important Notes
|
|
173
|
+
|
|
174
|
+
- View files must have the `.html` extension
|
|
175
|
+
- Skeleton files should be in the `skeleton/` directory, view files in the `view/` directory
|
|
176
|
+
- Placeholders for view parts are written in uppercase: `{{ HEADER }}`, `{{ CONTENT }}`, etc.
|
|
177
|
+
- View part names are specified in lowercase: `header`, `content`, etc.
|
|
178
|
+
- Variables in skeleton/views are accessed via `Candy` object: `{{ Candy.variableName }}`
|
|
179
|
+
- You don't need to use `return` from the controller, `Candy.View.set()` automatically initiates the rendering process
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
## 🔧 Template Syntax Overview
|
|
2
|
+
|
|
3
|
+
CandyPack uses a powerful template engine to create dynamic content in view files. The engine provides a clean, HTML-like syntax for displaying variables, conditionals, loops, translations, and more.
|
|
4
|
+
|
|
5
|
+
> **Note:** CandyPack also supports legacy syntax (`{{ }}`, `{!! !!}`, `{{-- --}}`) for backward compatibility, but the new `<candy>` tag syntax is recommended for all new projects.
|
|
6
|
+
|
|
7
|
+
### Quick Reference
|
|
8
|
+
|
|
9
|
+
This page provides a quick overview of all available template features. For detailed documentation and examples, see the dedicated pages for each feature.
|
|
10
|
+
|
|
11
|
+
### Variables (Controller Data)
|
|
12
|
+
|
|
13
|
+
Display data passed from controllers using `Candy.set()`:
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<!-- HTML-safe output -->
|
|
17
|
+
<candy var="username" />
|
|
18
|
+
|
|
19
|
+
<!-- Raw HTML output -->
|
|
20
|
+
<candy var="htmlContent" raw />
|
|
21
|
+
|
|
22
|
+
<!-- String literals -->
|
|
23
|
+
<candy>Hello World</candy>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**[→ Learn more about Variables](./03-variables.md)**
|
|
27
|
+
|
|
28
|
+
### Request Data (Query Parameters)
|
|
29
|
+
|
|
30
|
+
Access URL query parameters directly:
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<!-- Get query parameter from URL -->
|
|
34
|
+
<!-- URL: /search?q=laptop -->
|
|
35
|
+
<candy get="q" />
|
|
36
|
+
<!-- Output: laptop -->
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Note:** `<candy get>` is for URL parameters. For controller data, use `<candy var>`.
|
|
40
|
+
|
|
41
|
+
**[→ Learn more about Request Data](./04-request-data.md)**
|
|
42
|
+
|
|
43
|
+
### Translations (i18n)
|
|
44
|
+
|
|
45
|
+
Create multi-language applications:
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<!-- Basic translation -->
|
|
49
|
+
<candy translate>Welcome</candy>
|
|
50
|
+
|
|
51
|
+
<!-- With placeholders -->
|
|
52
|
+
<candy translate>Hello <candy var="user.name" /></candy>
|
|
53
|
+
|
|
54
|
+
<!-- With HTML preserved -->
|
|
55
|
+
<candy translate raw>Click <a href="/help">here</a></candy>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**[→ Learn more about Translations](./07-translations.md)**
|
|
59
|
+
|
|
60
|
+
### Comments
|
|
61
|
+
|
|
62
|
+
Two types of comments for different purposes:
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<!--candy Backend comment (not rendered) -->
|
|
66
|
+
|
|
67
|
+
<!--candy
|
|
68
|
+
Multi-line backend comment
|
|
69
|
+
Won't appear in output
|
|
70
|
+
candy-->
|
|
71
|
+
|
|
72
|
+
<!-- Regular HTML comment (rendered) -->
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**[→ Learn more about Comments](./09-comments.md)**
|
|
76
|
+
|
|
77
|
+
### Conditionals
|
|
78
|
+
|
|
79
|
+
Show or hide content based on conditions:
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<candy:if condition="user.isAdmin">
|
|
83
|
+
<p>Admin panel</p>
|
|
84
|
+
<candy:elseif condition="user.isModerator">
|
|
85
|
+
<p>Moderator panel</p>
|
|
86
|
+
<candy:else>
|
|
87
|
+
<p>User panel</p>
|
|
88
|
+
</candy:if>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**[→ Learn more about Conditionals](./05-conditionals.md)**
|
|
92
|
+
|
|
93
|
+
### Loops
|
|
94
|
+
|
|
95
|
+
Iterate over arrays and objects:
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<!-- For loop -->
|
|
99
|
+
<candy:for in="users" key="index" value="user">
|
|
100
|
+
<div><candy var="user.name" /></div>
|
|
101
|
+
</candy:for>
|
|
102
|
+
|
|
103
|
+
<!-- While loop -->
|
|
104
|
+
<candy:while condition="counter < 10">
|
|
105
|
+
<p><candy var="counter" /></p>
|
|
106
|
+
</candy:while>
|
|
107
|
+
|
|
108
|
+
<!-- Loop control -->
|
|
109
|
+
<candy:break />
|
|
110
|
+
<candy:continue />
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**[→ Learn more about Loops](./06-loops.md)**
|
|
114
|
+
|
|
115
|
+
### Backend JavaScript
|
|
116
|
+
|
|
117
|
+
Execute JavaScript on the server during template rendering:
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<script:candy>
|
|
121
|
+
// Runs on SERVER before HTML is sent
|
|
122
|
+
let total = 0;
|
|
123
|
+
for (let item of cart) {
|
|
124
|
+
total += item.price * item.quantity;
|
|
125
|
+
}
|
|
126
|
+
</script:candy>
|
|
127
|
+
|
|
128
|
+
<p>Total: $<candy var="total" /></p>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**[→ Learn more about Backend JavaScript](./08-backend-javascript.md)**
|
|
132
|
+
|
|
133
|
+
### Accessing the Candy Object
|
|
134
|
+
|
|
135
|
+
Full access to the Candy object in templates:
|
|
136
|
+
|
|
137
|
+
```html
|
|
138
|
+
<candy:if condition="Candy.Auth.check()">
|
|
139
|
+
<p>User: <candy var="Candy.Auth.user().name" /></p>
|
|
140
|
+
</candy:if>
|
|
141
|
+
|
|
142
|
+
<p>URL: <candy var="Candy.Request.url" /></p>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Complete Syntax Reference
|
|
146
|
+
|
|
147
|
+
| Feature | Syntax | Documentation |
|
|
148
|
+
|---------|--------|---------------|
|
|
149
|
+
| Variable (Controller) | `<candy var="x" />` | [Variables](./03-variables.md) |
|
|
150
|
+
| Raw HTML | `<candy var="x" raw />` | [Variables](./03-variables.md) |
|
|
151
|
+
| String | `<candy>text</candy>` | [Variables](./03-variables.md) |
|
|
152
|
+
| Query Parameter | `<candy get="key" />` | [Request Data](./04-request-data.md) |
|
|
153
|
+
| Translation | `<candy translate>key</candy>` | [Translations](./07-translations.md) |
|
|
154
|
+
| Translation Raw | `<candy translate raw>key</candy>` | [Translations](./07-translations.md) |
|
|
155
|
+
| If | `<candy:if condition="x">` | [Conditionals](./05-conditionals.md) |
|
|
156
|
+
| Elseif | `<candy:elseif condition="x">` | [Conditionals](./05-conditionals.md) |
|
|
157
|
+
| Else | `<candy:else>` | [Conditionals](./05-conditionals.md) |
|
|
158
|
+
| For | `<candy:for in="x" value="item">` | [Loops](./06-loops.md) |
|
|
159
|
+
| While | `<candy:while condition="x">` | [Loops](./06-loops.md) |
|
|
160
|
+
| Break | `<candy:break />` | [Loops](./06-loops.md) |
|
|
161
|
+
| Continue | `<candy:continue />` | [Loops](./06-loops.md) |
|
|
162
|
+
| JavaScript | `<script:candy>...</script:candy>` | [Backend JavaScript](./08-backend-javascript.md) |
|
|
163
|
+
| Comment | `<!--candy ... candy-->` | [Comments](./09-comments.md) |
|
|
164
|
+
|
|
165
|
+
### Legacy Syntax
|
|
166
|
+
|
|
167
|
+
CandyPack also supports legacy syntax for backward compatibility:
|
|
168
|
+
|
|
169
|
+
```html
|
|
170
|
+
<!-- Variable output -->
|
|
171
|
+
{{ username }}
|
|
172
|
+
|
|
173
|
+
<!-- Raw HTML -->
|
|
174
|
+
{!! htmlContent !!}
|
|
175
|
+
|
|
176
|
+
<!-- Comments -->
|
|
177
|
+
{{-- This is a comment --}}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Note:** The new `<candy>` tag syntax is recommended for all new projects.
|
|
181
|
+
|