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,398 @@
|
|
|
1
|
+
## 💻 Backend JavaScript (Server-Side Execution)
|
|
2
|
+
|
|
3
|
+
Backend JavaScript allows you to execute JavaScript code during template rendering on the server. This code runs **before** the HTML is sent to the browser.
|
|
4
|
+
|
|
5
|
+
### Basic Usage
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<script:candy>
|
|
9
|
+
// This runs on the SERVER during template rendering
|
|
10
|
+
let total = 0;
|
|
11
|
+
for (let item of cart) {
|
|
12
|
+
total += item.price * item.quantity;
|
|
13
|
+
}
|
|
14
|
+
</script:candy>
|
|
15
|
+
|
|
16
|
+
<p>Total: $<candy var="total" /></p>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Key Characteristics
|
|
20
|
+
|
|
21
|
+
- ✅ Runs on the **server** during template rendering
|
|
22
|
+
- ✅ Has access to all backend variables and Candy object
|
|
23
|
+
- ✅ Perfect for calculations, data manipulation, filtering
|
|
24
|
+
- ✅ Full IDE syntax highlighting and autocomplete
|
|
25
|
+
- ❌ Does NOT run in the browser
|
|
26
|
+
- ❌ Cannot access browser APIs (window, document, localStorage, etc.)
|
|
27
|
+
|
|
28
|
+
### When to Use Backend JavaScript
|
|
29
|
+
|
|
30
|
+
Use backend JavaScript for:
|
|
31
|
+
- **Calculations**: Totals, averages, statistics
|
|
32
|
+
- **Data transformation**: Filtering, sorting, mapping arrays
|
|
33
|
+
- **Complex logic**: Logic that's too complex for inline conditions
|
|
34
|
+
- **Variable preparation**: Creating temporary variables for display
|
|
35
|
+
|
|
36
|
+
### Accessing Variables
|
|
37
|
+
|
|
38
|
+
You have access to all variables set in the controller:
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
// Controller
|
|
42
|
+
module.exports = async function(Candy) {
|
|
43
|
+
Candy.set('products', [
|
|
44
|
+
{ name: 'Laptop', price: 999, quantity: 2 },
|
|
45
|
+
{ name: 'Mouse', price: 29, quantity: 5 }
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
Candy.View.skeleton('main').set('content', 'cart')
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<!-- View -->
|
|
54
|
+
<script:candy>
|
|
55
|
+
let total = 0;
|
|
56
|
+
let itemCount = 0;
|
|
57
|
+
|
|
58
|
+
for (let product of products) {
|
|
59
|
+
total += product.price * product.quantity;
|
|
60
|
+
itemCount += product.quantity;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const avgPrice = total / itemCount;
|
|
64
|
+
</script:candy>
|
|
65
|
+
|
|
66
|
+
<div class="cart-summary">
|
|
67
|
+
<p>Total Items: <candy var="itemCount" /></p>
|
|
68
|
+
<p>Total Price: $<candy var="total" /></p>
|
|
69
|
+
<p>Average Price: $<candy var="avgPrice.toFixed(2)" /></p>
|
|
70
|
+
</div>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Accessing the Candy Object
|
|
74
|
+
|
|
75
|
+
Full access to the Candy object and all its methods:
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<script:candy>
|
|
79
|
+
const isLoggedIn = Candy.Auth.check();
|
|
80
|
+
const currentUser = isLoggedIn ? Candy.Auth.user() : null;
|
|
81
|
+
const requestMethod = Candy.Request.method;
|
|
82
|
+
const currentUrl = Candy.Request.url;
|
|
83
|
+
</script:candy>
|
|
84
|
+
|
|
85
|
+
<candy:if condition="isLoggedIn">
|
|
86
|
+
<p>Welcome, <candy var="currentUser.name" />!</p>
|
|
87
|
+
</candy:if>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Practical Examples
|
|
91
|
+
|
|
92
|
+
#### Shopping Cart Calculations
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<script:candy>
|
|
96
|
+
let subtotal = 0;
|
|
97
|
+
let totalItems = 0;
|
|
98
|
+
|
|
99
|
+
for (let item of cart) {
|
|
100
|
+
subtotal += item.price * item.quantity;
|
|
101
|
+
totalItems += item.quantity;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const tax = subtotal * 0.18; // 18% tax
|
|
105
|
+
const shipping = subtotal > 100 ? 0 : 10;
|
|
106
|
+
const total = subtotal + tax + shipping;
|
|
107
|
+
</script:candy>
|
|
108
|
+
|
|
109
|
+
<div class="cart-summary">
|
|
110
|
+
<h3>Order Summary</h3>
|
|
111
|
+
<p>Items (<candy var="totalItems" />): $<candy var="subtotal.toFixed(2)" /></p>
|
|
112
|
+
<p>Tax (18%): $<candy var="tax.toFixed(2)" /></p>
|
|
113
|
+
<p>Shipping:
|
|
114
|
+
<candy:if condition="shipping === 0">
|
|
115
|
+
<span class="free">FREE</span>
|
|
116
|
+
<candy:else>
|
|
117
|
+
$<candy var="shipping.toFixed(2)" />
|
|
118
|
+
</candy:if>
|
|
119
|
+
</p>
|
|
120
|
+
<hr>
|
|
121
|
+
<p class="total">Total: $<candy var="total.toFixed(2)" /></p>
|
|
122
|
+
</div>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### Filtering and Sorting
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<script:candy>
|
|
129
|
+
// Filter active products
|
|
130
|
+
const activeProducts = products.filter(p => p.isActive && p.stock > 0);
|
|
131
|
+
|
|
132
|
+
// Sort by price
|
|
133
|
+
activeProducts.sort((a, b) => a.price - b.price);
|
|
134
|
+
|
|
135
|
+
// Get featured products
|
|
136
|
+
const featured = activeProducts.filter(p => p.featured).slice(0, 3);
|
|
137
|
+
|
|
138
|
+
// Calculate statistics
|
|
139
|
+
const avgPrice = activeProducts.reduce((sum, p) => sum + p.price, 0) / activeProducts.length;
|
|
140
|
+
const maxPrice = Math.max(...activeProducts.map(p => p.price));
|
|
141
|
+
const minPrice = Math.min(...activeProducts.map(p => p.price));
|
|
142
|
+
</script:candy>
|
|
143
|
+
|
|
144
|
+
<div class="products-section">
|
|
145
|
+
<h2>Featured Products</h2>
|
|
146
|
+
<p>Showing <candy var="featured.length" /> of <candy var="activeProducts.length" /> products</p>
|
|
147
|
+
<p>Price range: $<candy var="minPrice" /> - $<candy var="maxPrice" /></p>
|
|
148
|
+
|
|
149
|
+
<candy:for in="featured" value="product">
|
|
150
|
+
<div class="product">
|
|
151
|
+
<h3><candy var="product.name" /></h3>
|
|
152
|
+
<p>$<candy var="product.price" /></p>
|
|
153
|
+
|
|
154
|
+
<candy:if condition="product.price < avgPrice">
|
|
155
|
+
<span class="badge">Great Deal!</span>
|
|
156
|
+
</candy:if>
|
|
157
|
+
</div>
|
|
158
|
+
</candy:for>
|
|
159
|
+
</div>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Date and Time Formatting
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<script:candy>
|
|
166
|
+
const now = new Date();
|
|
167
|
+
const postDate = new Date(post.createdAt);
|
|
168
|
+
|
|
169
|
+
// Calculate time difference
|
|
170
|
+
const diffMs = now - postDate;
|
|
171
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
172
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
173
|
+
const diffMinutes = Math.floor(diffMs / (1000 * 60));
|
|
174
|
+
|
|
175
|
+
let timeAgo;
|
|
176
|
+
if (diffDays > 0) {
|
|
177
|
+
timeAgo = diffDays + ' days ago';
|
|
178
|
+
} else if (diffHours > 0) {
|
|
179
|
+
timeAgo = diffHours + ' hours ago';
|
|
180
|
+
} else if (diffMinutes > 0) {
|
|
181
|
+
timeAgo = diffMinutes + ' minutes ago';
|
|
182
|
+
} else {
|
|
183
|
+
timeAgo = 'Just now';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const formattedDate = postDate.toLocaleDateString('en-US', {
|
|
187
|
+
year: 'numeric',
|
|
188
|
+
month: 'long',
|
|
189
|
+
day: 'numeric'
|
|
190
|
+
});
|
|
191
|
+
</script:candy>
|
|
192
|
+
|
|
193
|
+
<div class="post">
|
|
194
|
+
<h2><candy var="post.title" /></h2>
|
|
195
|
+
<p class="meta">
|
|
196
|
+
Posted <candy var="timeAgo" /> (<candy var="formattedDate" />)
|
|
197
|
+
</p>
|
|
198
|
+
</div>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Grouping Data
|
|
202
|
+
|
|
203
|
+
```html
|
|
204
|
+
<script:candy>
|
|
205
|
+
// Group products by category
|
|
206
|
+
const grouped = {};
|
|
207
|
+
for (let product of products) {
|
|
208
|
+
if (!grouped[product.category]) {
|
|
209
|
+
grouped[product.category] = [];
|
|
210
|
+
}
|
|
211
|
+
grouped[product.category].push(product);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Sort categories
|
|
215
|
+
const categories = Object.keys(grouped).sort();
|
|
216
|
+
</script:candy>
|
|
217
|
+
|
|
218
|
+
<div class="products-by-category">
|
|
219
|
+
<candy:for in="categories" value="category">
|
|
220
|
+
<div class="category-section">
|
|
221
|
+
<h2><candy var="category" /></h2>
|
|
222
|
+
<p><candy var="grouped[category].length" /> products</p>
|
|
223
|
+
|
|
224
|
+
<candy:for in="grouped[category]" value="product">
|
|
225
|
+
<div class="product">
|
|
226
|
+
<h3><candy var="product.name" /></h3>
|
|
227
|
+
<p>$<candy var="product.price" /></p>
|
|
228
|
+
</div>
|
|
229
|
+
</candy:for>
|
|
230
|
+
</div>
|
|
231
|
+
</candy:for>
|
|
232
|
+
</div>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### Pagination Logic
|
|
236
|
+
|
|
237
|
+
```html
|
|
238
|
+
<script:candy>
|
|
239
|
+
const itemsPerPage = 10;
|
|
240
|
+
const currentPage = parseInt(Candy.Request.get('page')) || 1;
|
|
241
|
+
const totalItems = products.length;
|
|
242
|
+
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
|
243
|
+
|
|
244
|
+
const startIndex = (currentPage - 1) * itemsPerPage;
|
|
245
|
+
const endIndex = Math.min(startIndex + itemsPerPage, totalItems);
|
|
246
|
+
const currentItems = products.slice(startIndex, endIndex);
|
|
247
|
+
|
|
248
|
+
const hasPrevious = currentPage > 1;
|
|
249
|
+
const hasNext = currentPage < totalPages;
|
|
250
|
+
</script:candy>
|
|
251
|
+
|
|
252
|
+
<div class="products">
|
|
253
|
+
<candy:for in="currentItems" value="product">
|
|
254
|
+
<div class="product">
|
|
255
|
+
<h3><candy var="product.name" /></h3>
|
|
256
|
+
</div>
|
|
257
|
+
</candy:for>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div class="pagination">
|
|
261
|
+
<candy:if condition="hasPrevious">
|
|
262
|
+
<a href="?page=<candy var="currentPage - 1" />">Previous</a>
|
|
263
|
+
</candy:if>
|
|
264
|
+
|
|
265
|
+
<span>Page <candy var="currentPage" /> of <candy var="totalPages" /></span>
|
|
266
|
+
|
|
267
|
+
<candy:if condition="hasNext">
|
|
268
|
+
<a href="?page=<candy var="currentPage + 1" />">Next</a>
|
|
269
|
+
</candy:if>
|
|
270
|
+
</div>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### Complex Conditional Logic
|
|
274
|
+
|
|
275
|
+
```html
|
|
276
|
+
<script:candy>
|
|
277
|
+
const user = Candy.Auth.check() ? Candy.Auth.user() : null;
|
|
278
|
+
|
|
279
|
+
const canEdit = user && (
|
|
280
|
+
user.role === 'admin' ||
|
|
281
|
+
user.id === post.authorId
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const canDelete = user && user.role === 'admin';
|
|
285
|
+
|
|
286
|
+
const canComment = user && !user.isBanned && post.commentsEnabled;
|
|
287
|
+
|
|
288
|
+
const showActions = canEdit || canDelete || canComment;
|
|
289
|
+
</script:candy>
|
|
290
|
+
|
|
291
|
+
<div class="post">
|
|
292
|
+
<h2><candy var="post.title" /></h2>
|
|
293
|
+
<p><candy var="post.content" /></p>
|
|
294
|
+
|
|
295
|
+
<candy:if condition="showActions">
|
|
296
|
+
<div class="actions">
|
|
297
|
+
<candy:if condition="canEdit">
|
|
298
|
+
<a href="/posts/<candy var="post.id" />/edit">Edit</a>
|
|
299
|
+
</candy:if>
|
|
300
|
+
|
|
301
|
+
<candy:if condition="canDelete">
|
|
302
|
+
<a href="/posts/<candy var="post.id" />/delete">Delete</a>
|
|
303
|
+
</candy:if>
|
|
304
|
+
|
|
305
|
+
<candy:if condition="canComment">
|
|
306
|
+
<a href="#comments">Add Comment</a>
|
|
307
|
+
</candy:if>
|
|
308
|
+
</div>
|
|
309
|
+
</candy:if>
|
|
310
|
+
</div>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Multiple Script Blocks
|
|
314
|
+
|
|
315
|
+
You can use multiple `<script:candy>` blocks in the same view:
|
|
316
|
+
|
|
317
|
+
```html
|
|
318
|
+
<script:candy>
|
|
319
|
+
let total = 0;
|
|
320
|
+
</script:candy>
|
|
321
|
+
|
|
322
|
+
<candy:for in="items" value="item">
|
|
323
|
+
<div><candy var="item.name" /></div>
|
|
324
|
+
|
|
325
|
+
<script:candy>
|
|
326
|
+
total += item.price;
|
|
327
|
+
</script:candy>
|
|
328
|
+
</candy:for>
|
|
329
|
+
|
|
330
|
+
<p>Total: $<candy var="total" /></p>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Comparison with Client-Side JavaScript
|
|
334
|
+
|
|
335
|
+
**Backend JavaScript (`<script:candy>`):**
|
|
336
|
+
```html
|
|
337
|
+
<script:candy>
|
|
338
|
+
// Runs on SERVER during rendering
|
|
339
|
+
const total = products.reduce((sum, p) => sum + p.price, 0);
|
|
340
|
+
</script:candy>
|
|
341
|
+
<p>Total: $<candy var="total" /></p>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Client-Side JavaScript (`<script>`):**
|
|
345
|
+
```html
|
|
346
|
+
<script>
|
|
347
|
+
// Runs in BROWSER after page loads
|
|
348
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
349
|
+
console.log('Page loaded');
|
|
350
|
+
|
|
351
|
+
// Can access browser APIs
|
|
352
|
+
localStorage.setItem('visited', 'true');
|
|
353
|
+
|
|
354
|
+
// Can manipulate DOM
|
|
355
|
+
document.querySelector('.button').addEventListener('click', function() {
|
|
356
|
+
alert('Clicked!');
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
</script>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Best Practices
|
|
363
|
+
|
|
364
|
+
1. **Keep it simple**: Complex logic should be in controllers
|
|
365
|
+
2. **Use for calculations**: Perfect for totals, averages, filtering
|
|
366
|
+
3. **Avoid heavy operations**: Don't do database queries or API calls
|
|
367
|
+
4. **Use meaningful variable names**: Make code self-documenting
|
|
368
|
+
5. **Comment when necessary**: Explain complex calculations
|
|
369
|
+
|
|
370
|
+
**Good:**
|
|
371
|
+
```html
|
|
372
|
+
<script:candy>
|
|
373
|
+
const discountedPrice = product.price * (1 - product.discount / 100);
|
|
374
|
+
const savings = product.price - discountedPrice;
|
|
375
|
+
</script:candy>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Avoid:**
|
|
379
|
+
```html
|
|
380
|
+
<script:candy>
|
|
381
|
+
// Don't do this - should be in controller
|
|
382
|
+
const users = await Candy.Mysql.query('SELECT * FROM users');
|
|
383
|
+
const apiData = await fetch('https://api.example.com/data');
|
|
384
|
+
</script:candy>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Common Use Cases
|
|
388
|
+
|
|
389
|
+
- ✅ Calculate totals and subtotals
|
|
390
|
+
- ✅ Filter and sort arrays
|
|
391
|
+
- ✅ Format dates and numbers
|
|
392
|
+
- ✅ Group and aggregate data
|
|
393
|
+
- ✅ Create temporary display variables
|
|
394
|
+
- ✅ Simple conditional logic
|
|
395
|
+
- ❌ Database queries (use controller)
|
|
396
|
+
- ❌ API calls (use controller)
|
|
397
|
+
- ❌ File operations (use controller)
|
|
398
|
+
- ❌ Heavy computations (use controller)
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
## 💬 Comments in Views
|
|
2
|
+
|
|
3
|
+
CandyPack supports two types of comments in view files: backend comments (not rendered) and regular HTML comments (rendered).
|
|
4
|
+
|
|
5
|
+
### Backend Comments (Not Rendered)
|
|
6
|
+
|
|
7
|
+
Backend comments are removed during template rendering and never appear in the HTML output sent to the browser.
|
|
8
|
+
|
|
9
|
+
#### Single-Line Backend Comments
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<!--candy This is a backend comment -->
|
|
13
|
+
<p>This will be rendered</p>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
#### Multi-Line Backend Comments
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<!--candy
|
|
20
|
+
This is a multi-line backend comment
|
|
21
|
+
It can span multiple lines
|
|
22
|
+
None of this will appear in the output
|
|
23
|
+
candy-->
|
|
24
|
+
|
|
25
|
+
<div class="content">
|
|
26
|
+
<p>This will be rendered</p>
|
|
27
|
+
</div>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Regular HTML Comments (Rendered)
|
|
31
|
+
|
|
32
|
+
Standard HTML comments are preserved and sent to the browser:
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<!-- This is a regular HTML comment -->
|
|
36
|
+
<!-- It will appear in the browser's HTML source -->
|
|
37
|
+
<p>Content here</p>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### When to Use Each Type
|
|
41
|
+
|
|
42
|
+
#### Use Backend Comments For:
|
|
43
|
+
|
|
44
|
+
**Development Notes:**
|
|
45
|
+
```html
|
|
46
|
+
<!--candy TODO: Add pagination here -->
|
|
47
|
+
<!--candy FIXME: This needs optimization -->
|
|
48
|
+
<!--candy NOTE: This section is for admin users only -->
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Sensitive Information:**
|
|
52
|
+
```html
|
|
53
|
+
<!--candy
|
|
54
|
+
Database query returns: id, name, email, password_hash
|
|
55
|
+
We only display: name, email
|
|
56
|
+
candy-->
|
|
57
|
+
|
|
58
|
+
<candy:for in="users" value="user">
|
|
59
|
+
<p><candy var="user.name" /> - <candy var="user.email" /></p>
|
|
60
|
+
</candy:for>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Debugging Information:**
|
|
64
|
+
```html
|
|
65
|
+
<!--candy Debug: user object structure -->
|
|
66
|
+
<!--candy { id: 1, name: "John", role: "admin" } -->
|
|
67
|
+
|
|
68
|
+
<candy:if condition="user.role === 'admin'">
|
|
69
|
+
<div class="admin-panel">Admin content</div>
|
|
70
|
+
</candy:if>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Temporary Code:**
|
|
74
|
+
```html
|
|
75
|
+
<!--candy
|
|
76
|
+
Old implementation - keeping for reference
|
|
77
|
+
<div class="old-layout">
|
|
78
|
+
<candy:for in="items" value="item">
|
|
79
|
+
<p><candy var="item.name" /></p>
|
|
80
|
+
</candy:for>
|
|
81
|
+
</div>
|
|
82
|
+
candy-->
|
|
83
|
+
|
|
84
|
+
<div class="new-layout">
|
|
85
|
+
<candy:for in="items" value="item">
|
|
86
|
+
<div class="item-card">
|
|
87
|
+
<h3><candy var="item.name" /></h3>
|
|
88
|
+
</div>
|
|
89
|
+
</candy:for>
|
|
90
|
+
</div>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Use HTML Comments For:
|
|
94
|
+
|
|
95
|
+
**Section Markers:**
|
|
96
|
+
```html
|
|
97
|
+
<!-- Header Section -->
|
|
98
|
+
<header>
|
|
99
|
+
<nav>...</nav>
|
|
100
|
+
</header>
|
|
101
|
+
|
|
102
|
+
<!-- Main Content -->
|
|
103
|
+
<main>
|
|
104
|
+
<article>...</article>
|
|
105
|
+
</main>
|
|
106
|
+
|
|
107
|
+
<!-- Footer Section -->
|
|
108
|
+
<footer>
|
|
109
|
+
<p>Copyright 2024</p>
|
|
110
|
+
</footer>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Browser-Specific Hacks:**
|
|
114
|
+
```html
|
|
115
|
+
<!--[if IE]>
|
|
116
|
+
<p>You are using Internet Explorer</p>
|
|
117
|
+
<![endif]-->
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Third-Party Integration Notes:**
|
|
121
|
+
```html
|
|
122
|
+
<!-- Google Analytics -->
|
|
123
|
+
<script>
|
|
124
|
+
// Analytics code here
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
<!-- Facebook Pixel -->
|
|
128
|
+
<script>
|
|
129
|
+
// Pixel code here
|
|
130
|
+
</script>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Practical Examples
|
|
134
|
+
|
|
135
|
+
#### Documenting Complex Logic
|
|
136
|
+
|
|
137
|
+
```html
|
|
138
|
+
<!--candy
|
|
139
|
+
This section displays products based on user role:
|
|
140
|
+
- Admin: sees all products including inactive
|
|
141
|
+
- Regular user: sees only active products
|
|
142
|
+
- Guest: sees only featured products
|
|
143
|
+
candy-->
|
|
144
|
+
|
|
145
|
+
<script:candy>
|
|
146
|
+
let visibleProducts;
|
|
147
|
+
|
|
148
|
+
if (Candy.Auth.check()) {
|
|
149
|
+
const user = Candy.Auth.user();
|
|
150
|
+
if (user.role === 'admin') {
|
|
151
|
+
visibleProducts = products;
|
|
152
|
+
} else {
|
|
153
|
+
visibleProducts = products.filter(p => p.isActive);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
visibleProducts = products.filter(p => p.featured);
|
|
157
|
+
}
|
|
158
|
+
</script:candy>
|
|
159
|
+
|
|
160
|
+
<candy:for in="visibleProducts" value="product">
|
|
161
|
+
<div class="product">
|
|
162
|
+
<h3><candy var="product.name" /></h3>
|
|
163
|
+
</div>
|
|
164
|
+
</candy:for>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### Marking Sections for Developers
|
|
168
|
+
|
|
169
|
+
```html
|
|
170
|
+
<div class="dashboard">
|
|
171
|
+
<!--candy START: User Statistics Section -->
|
|
172
|
+
<div class="stats">
|
|
173
|
+
<h2>Statistics</h2>
|
|
174
|
+
<p>Total Users: <candy var="stats.totalUsers" /></p>
|
|
175
|
+
<p>Active Users: <candy var="stats.activeUsers" /></p>
|
|
176
|
+
</div>
|
|
177
|
+
<!--candy END: User Statistics Section -->
|
|
178
|
+
|
|
179
|
+
<!--candy START: Recent Activity Section -->
|
|
180
|
+
<div class="activity">
|
|
181
|
+
<h2>Recent Activity</h2>
|
|
182
|
+
<candy:for in="activities" value="activity">
|
|
183
|
+
<p><candy var="activity.description" /></p>
|
|
184
|
+
</candy:for>
|
|
185
|
+
</div>
|
|
186
|
+
<!--candy END: Recent Activity Section -->
|
|
187
|
+
</div>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Explaining Template Variables
|
|
191
|
+
|
|
192
|
+
```html
|
|
193
|
+
<!--candy
|
|
194
|
+
Available variables from controller:
|
|
195
|
+
- user: Current user object { id, name, email, role }
|
|
196
|
+
- posts: Array of post objects
|
|
197
|
+
- categories: Array of category objects
|
|
198
|
+
- settings: Site settings object
|
|
199
|
+
candy-->
|
|
200
|
+
|
|
201
|
+
<div class="profile">
|
|
202
|
+
<h1><candy var="user.name" /></h1>
|
|
203
|
+
<p><candy var="user.email" /></p>
|
|
204
|
+
</div>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### Temporary Disabling Code
|
|
208
|
+
|
|
209
|
+
```html
|
|
210
|
+
<div class="products">
|
|
211
|
+
<candy:for in="products" value="product">
|
|
212
|
+
<div class="product-card">
|
|
213
|
+
<h3><candy var="product.name" /></h3>
|
|
214
|
+
<p>$<candy var="product.price" /></p>
|
|
215
|
+
|
|
216
|
+
<!--candy Temporarily disabled - waiting for API
|
|
217
|
+
<div class="reviews">
|
|
218
|
+
<candy var="product.averageRating" /> stars
|
|
219
|
+
</div>
|
|
220
|
+
candy-->
|
|
221
|
+
</div>
|
|
222
|
+
</candy:for>
|
|
223
|
+
</div>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### Version History
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<!--candy
|
|
230
|
+
Version History:
|
|
231
|
+
v1.0 - Initial implementation
|
|
232
|
+
v1.1 - Added sorting functionality
|
|
233
|
+
v1.2 - Added filtering by category
|
|
234
|
+
v2.0 - Complete redesign with new layout
|
|
235
|
+
candy-->
|
|
236
|
+
|
|
237
|
+
<div class="product-list">
|
|
238
|
+
<!-- Product list implementation -->
|
|
239
|
+
</div>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Best Practices
|
|
243
|
+
|
|
244
|
+
1. **Use backend comments for sensitive info**: Never expose internal logic or data structures in HTML comments
|
|
245
|
+
2. **Keep comments concise**: Don't over-comment obvious code
|
|
246
|
+
3. **Update comments**: Remove or update outdated comments
|
|
247
|
+
4. **Use meaningful descriptions**: Make comments helpful for other developers
|
|
248
|
+
5. **Don't commit debug comments**: Remove debug comments before committing
|
|
249
|
+
|
|
250
|
+
**Good:**
|
|
251
|
+
```html
|
|
252
|
+
<!--candy This query is cached for 5 minutes -->
|
|
253
|
+
<candy:for in="products" value="product">
|
|
254
|
+
<div><candy var="product.name" /></div>
|
|
255
|
+
</candy:for>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Avoid:**
|
|
259
|
+
```html
|
|
260
|
+
<!--candy Loop through products -->
|
|
261
|
+
<candy:for in="products" value="product">
|
|
262
|
+
<!--candy Display product name -->
|
|
263
|
+
<div><candy var="product.name" /></div>
|
|
264
|
+
</candy:for>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Security Considerations
|
|
268
|
+
|
|
269
|
+
**Never expose sensitive information in HTML comments:**
|
|
270
|
+
|
|
271
|
+
```html
|
|
272
|
+
<!-- BAD: Visible in browser source -->
|
|
273
|
+
<!-- Database password: secret123 -->
|
|
274
|
+
<!-- API key: abc123xyz -->
|
|
275
|
+
|
|
276
|
+
<!--candy GOOD: Not visible in output -->
|
|
277
|
+
<!--candy Database password: secret123 -->
|
|
278
|
+
<!--candy API key: abc123xyz -->
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Be careful with user data:**
|
|
282
|
+
|
|
283
|
+
```html
|
|
284
|
+
<!-- BAD: Exposes user data -->
|
|
285
|
+
<!-- User ID: 12345, Email: user@example.com -->
|
|
286
|
+
|
|
287
|
+
<!--candy GOOD: Hidden from output -->
|
|
288
|
+
<!--candy User ID: 12345, Email: user@example.com -->
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Comment Syntax Summary
|
|
292
|
+
|
|
293
|
+
| Type | Syntax | Rendered | Use Case |
|
|
294
|
+
|------|--------|----------|----------|
|
|
295
|
+
| Backend Single-Line | `<!--candy comment -->` | No | Development notes, TODOs |
|
|
296
|
+
| Backend Multi-Line | `<!--candy ... candy-->` | No | Detailed explanations, disabled code |
|
|
297
|
+
| HTML Comment | `<!-- comment -->` | Yes | Section markers, browser hacks |
|