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,395 @@
|
|
|
1
|
+
# Custom Forms
|
|
2
|
+
|
|
3
|
+
CandyPack provides an automatic form system with built-in validation, CSRF protection, and seamless client-side integration. The `<candy:form>` tag allows you to create forms with minimal code while maintaining full control.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<candy:form action="/contact/submit" method="POST">
|
|
9
|
+
<candy:field name="email" type="email" label="Email">
|
|
10
|
+
<candy:validate rule="required|email" message="Valid email required"/>
|
|
11
|
+
</candy:field>
|
|
12
|
+
|
|
13
|
+
<candy:submit text="Send" loading="Sending..."/>
|
|
14
|
+
</candy:form>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Form Attributes
|
|
18
|
+
|
|
19
|
+
### `<candy:form>`
|
|
20
|
+
|
|
21
|
+
- `action` - Form submission URL (optional if using `table`)
|
|
22
|
+
- `method` - HTTP method (default: POST)
|
|
23
|
+
- `table` - Database table name for automatic insert (optional)
|
|
24
|
+
- `redirect` - Redirect URL after success (optional)
|
|
25
|
+
- `success` - Success message (optional)
|
|
26
|
+
- `class` - Additional CSS classes
|
|
27
|
+
- `id` - Form ID attribute
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<!-- With custom controller -->
|
|
31
|
+
<candy:form action="/api/save" method="POST" class="my-form" id="contact-form">
|
|
32
|
+
<!-- fields here -->
|
|
33
|
+
</candy:form>
|
|
34
|
+
|
|
35
|
+
<!-- With automatic DB insert -->
|
|
36
|
+
<candy:form table="waitlist" redirect="/" success="Thank you for joining!">
|
|
37
|
+
<!-- fields here -->
|
|
38
|
+
</candy:form>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Field Types
|
|
42
|
+
|
|
43
|
+
### `<candy:field>`
|
|
44
|
+
|
|
45
|
+
Supports all standard HTML input types:
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<!-- Text input with multiple validations -->
|
|
49
|
+
<candy:field name="username" type="text" label="Username" placeholder="Enter username">
|
|
50
|
+
<candy:validate rule="required" message="Username is required"/>
|
|
51
|
+
<candy:validate rule="minlen:3" message="Username must be at least 3 characters"/>
|
|
52
|
+
<candy:validate rule="maxlen:20" message="Username cannot exceed 20 characters"/>
|
|
53
|
+
<candy:validate rule="alphanumeric" message="Username can only contain letters and numbers"/>
|
|
54
|
+
</candy:field>
|
|
55
|
+
|
|
56
|
+
<!-- Email input -->
|
|
57
|
+
<candy:field name="email" type="email" label="Email Address" placeholder="your@email.com">
|
|
58
|
+
<candy:validate rule="required" message="Email address is required"/>
|
|
59
|
+
<candy:validate rule="email" message="Please enter a valid email address"/>
|
|
60
|
+
<candy:validate rule="maxlen:100" message="Email is too long"/>
|
|
61
|
+
</candy:field>
|
|
62
|
+
|
|
63
|
+
<!-- Password input with strong validation -->
|
|
64
|
+
<candy:field name="password" type="password" label="Password">
|
|
65
|
+
<candy:validate rule="required" message="Password is required"/>
|
|
66
|
+
<candy:validate rule="minlen:8" message="Password must be at least 8 characters long"/>
|
|
67
|
+
<candy:validate rule="maxlen:50" message="Password is too long"/>
|
|
68
|
+
</candy:field>
|
|
69
|
+
|
|
70
|
+
<!-- Textarea with character limits -->
|
|
71
|
+
<candy:field name="message" type="textarea" label="Your Message" placeholder="Tell us what you think...">
|
|
72
|
+
<candy:validate rule="required" message="Please enter your message"/>
|
|
73
|
+
<candy:validate rule="minlen:10" message="Message must be at least 10 characters"/>
|
|
74
|
+
<candy:validate rule="maxlen:500" message="Message cannot exceed 500 characters"/>
|
|
75
|
+
</candy:field>
|
|
76
|
+
|
|
77
|
+
<!-- Checkbox for terms acceptance -->
|
|
78
|
+
<candy:field name="agree" type="checkbox" label="I agree to the Terms of Service and Privacy Policy">
|
|
79
|
+
<candy:validate rule="accepted" message="You must accept the terms to continue"/>
|
|
80
|
+
</candy:field>
|
|
81
|
+
|
|
82
|
+
<!-- Number input with range -->
|
|
83
|
+
<candy:field name="age" type="number" label="Your Age">
|
|
84
|
+
<candy:validate rule="required" message="Age is required"/>
|
|
85
|
+
<candy:validate rule="min:18" message="You must be at least 18 years old"/>
|
|
86
|
+
<candy:validate rule="max:120" message="Please enter a valid age"/>
|
|
87
|
+
</candy:field>
|
|
88
|
+
|
|
89
|
+
<!-- Phone number -->
|
|
90
|
+
<candy:field name="phone" type="text" label="Phone Number" placeholder="+1 (555) 123-4567">
|
|
91
|
+
<candy:validate rule="required" message="Phone number is required"/>
|
|
92
|
+
<candy:validate rule="minlen:10" message="Phone number must be at least 10 digits"/>
|
|
93
|
+
</candy:field>
|
|
94
|
+
|
|
95
|
+
<!-- URL input -->
|
|
96
|
+
<candy:field name="website" type="url" label="Website" placeholder="https://example.com">
|
|
97
|
+
<candy:validate rule="url" message="Please enter a valid URL"/>
|
|
98
|
+
</candy:field>
|
|
99
|
+
|
|
100
|
+
<!-- Name with alpha validation -->
|
|
101
|
+
<candy:field name="full_name" type="text" label="Full Name" placeholder="John Doe">
|
|
102
|
+
<candy:validate rule="required" message="Full name is required"/>
|
|
103
|
+
<candy:validate rule="minlen:2" message="Name must be at least 2 characters"/>
|
|
104
|
+
<candy:validate rule="maxlen:50" message="Name is too long"/>
|
|
105
|
+
</candy:field>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Field Attributes
|
|
109
|
+
|
|
110
|
+
- `name` - Field name (required)
|
|
111
|
+
- `type` - Input type (default: text)
|
|
112
|
+
- `label` - Field label
|
|
113
|
+
- `placeholder` - Placeholder text
|
|
114
|
+
- `class` - CSS classes
|
|
115
|
+
- `id` - Field ID
|
|
116
|
+
|
|
117
|
+
## Validation Rules
|
|
118
|
+
|
|
119
|
+
### `<candy:validate>`
|
|
120
|
+
|
|
121
|
+
Add validation rules to fields:
|
|
122
|
+
|
|
123
|
+
```html
|
|
124
|
+
<candy:field name="username" type="text">
|
|
125
|
+
<candy:validate rule="required|minlen:3|maxlen:20" message="Username must be 3-20 characters"/>
|
|
126
|
+
</candy:field>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Available Rules
|
|
130
|
+
|
|
131
|
+
- `required` - Field is required
|
|
132
|
+
- `email` - Must be valid email
|
|
133
|
+
- `url` - Must be valid URL
|
|
134
|
+
- `minlen:n` - Minimum length
|
|
135
|
+
- `maxlen:n` - Maximum length
|
|
136
|
+
- `min:n` - Minimum value (numbers)
|
|
137
|
+
- `max:n` - Maximum value (numbers)
|
|
138
|
+
- `numeric` - Only numbers
|
|
139
|
+
- `alpha` - Only letters
|
|
140
|
+
- `alphanumeric` - Letters and numbers only
|
|
141
|
+
- `accepted` - Checkbox must be checked
|
|
142
|
+
|
|
143
|
+
### Multiple Rules
|
|
144
|
+
|
|
145
|
+
Combine rules with `|`:
|
|
146
|
+
|
|
147
|
+
```html
|
|
148
|
+
<candy:validate rule="required|email|maxlen:100" message="Invalid email"/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Unique Validation
|
|
152
|
+
|
|
153
|
+
For automatic DB insert, use `unique` to check if value already exists:
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<candy:validate rule="required|email|unique" message="This email is already registered"/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Auto-Set Values
|
|
160
|
+
|
|
161
|
+
### `<candy:set>`
|
|
162
|
+
|
|
163
|
+
Automatically set field values without user input:
|
|
164
|
+
|
|
165
|
+
```html
|
|
166
|
+
<candy:set name="created_at" compute="now"/>
|
|
167
|
+
<candy:set name="ip" compute="ip"/>
|
|
168
|
+
<candy:set name="user_agent" compute="user_agent"/>
|
|
169
|
+
<candy:set name="status" value="pending"/>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Available Compute Types
|
|
173
|
+
|
|
174
|
+
- `now` - Current Unix timestamp (seconds)
|
|
175
|
+
- `date` - Current date (YYYY-MM-DD)
|
|
176
|
+
- `datetime` - Current ISO datetime
|
|
177
|
+
- `timestamp` - Current timestamp (milliseconds)
|
|
178
|
+
- `ip` - User's IP address
|
|
179
|
+
- `user_agent` - User's browser user agent
|
|
180
|
+
- `uuid` - Generate UUID v4
|
|
181
|
+
|
|
182
|
+
### Set Attributes
|
|
183
|
+
|
|
184
|
+
- `name` - Field name (required)
|
|
185
|
+
- `value` - Static value
|
|
186
|
+
- `compute` - Computed value type
|
|
187
|
+
- `callback` - Custom function name
|
|
188
|
+
- `if-empty` - Only set if field is empty
|
|
189
|
+
|
|
190
|
+
## Submit Button
|
|
191
|
+
|
|
192
|
+
### `<candy:submit>`
|
|
193
|
+
|
|
194
|
+
```html
|
|
195
|
+
<!-- Simple -->
|
|
196
|
+
<candy:submit text="Submit"/>
|
|
197
|
+
|
|
198
|
+
<!-- With loading state -->
|
|
199
|
+
<candy:submit text="Send Message" loading="Sending..."/>
|
|
200
|
+
|
|
201
|
+
<!-- With styling -->
|
|
202
|
+
<candy:submit text="Save" loading="Saving..." class="btn btn-primary" id="save-btn"/>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Controller Handler
|
|
206
|
+
|
|
207
|
+
Handle form submission in your controller:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
module.exports = {
|
|
211
|
+
submit: Candy => {
|
|
212
|
+
// Access validated form data
|
|
213
|
+
const data = Candy.formData
|
|
214
|
+
|
|
215
|
+
// data contains all field values
|
|
216
|
+
console.log(data.email, data.message)
|
|
217
|
+
|
|
218
|
+
// Process the data (save to database, send email, etc.)
|
|
219
|
+
|
|
220
|
+
// Return success response
|
|
221
|
+
return Candy.return({
|
|
222
|
+
result: {
|
|
223
|
+
success: true,
|
|
224
|
+
message: 'Form submitted successfully!',
|
|
225
|
+
redirect: '/thank-you' // Optional redirect
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Error Handling
|
|
233
|
+
|
|
234
|
+
Return validation errors:
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
module.exports = {
|
|
238
|
+
submit: Candy => {
|
|
239
|
+
const data = Candy.formData
|
|
240
|
+
|
|
241
|
+
// Custom validation
|
|
242
|
+
if (data.email.includes('spam')) {
|
|
243
|
+
return Candy.return({
|
|
244
|
+
result: {success: false},
|
|
245
|
+
errors: {
|
|
246
|
+
email: 'This email is not allowed'
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return Candy.return({
|
|
252
|
+
result: {success: true, message: 'Success!'}
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Automatic Database Insert
|
|
259
|
+
|
|
260
|
+
Forms can automatically insert data into database without writing a controller:
|
|
261
|
+
|
|
262
|
+
### View (view/content/waitlist.html)
|
|
263
|
+
|
|
264
|
+
```html
|
|
265
|
+
<candy:form table="waitlist" redirect="/" success="Thank you for joining!">
|
|
266
|
+
<candy:field name="email" type="email" label="Email">
|
|
267
|
+
<candy:validate rule="required|email|unique" message="Valid email required"/>
|
|
268
|
+
</candy:field>
|
|
269
|
+
|
|
270
|
+
<candy:field name="name" type="text" label="Name">
|
|
271
|
+
<candy:validate rule="required|minlen:2" message="Name required"/>
|
|
272
|
+
</candy:field>
|
|
273
|
+
|
|
274
|
+
<candy:set name="created_at" compute="now"/>
|
|
275
|
+
<candy:set name="ip" compute="ip"/>
|
|
276
|
+
|
|
277
|
+
<candy:submit text="Join Waitlist" loading="Joining..."/>
|
|
278
|
+
</candy:form>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Database Table
|
|
282
|
+
|
|
283
|
+
```sql
|
|
284
|
+
CREATE TABLE `waitlist` (
|
|
285
|
+
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
286
|
+
`email` VARCHAR(255) NOT NULL UNIQUE,
|
|
287
|
+
`name` VARCHAR(255) NOT NULL,
|
|
288
|
+
`created_at` INT UNSIGNED NOT NULL,
|
|
289
|
+
`ip` VARCHAR(45) NULL,
|
|
290
|
+
INDEX `idx_email` (`email`)
|
|
291
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Route (route/www.js)
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
Candy.Route.page('/waitlist', 'waitlist')
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
That's it! No controller needed. The form will:
|
|
301
|
+
- Validate all fields
|
|
302
|
+
- Check email uniqueness
|
|
303
|
+
- Insert data into `waitlist` table
|
|
304
|
+
- Show success message
|
|
305
|
+
- Redirect to home page
|
|
306
|
+
|
|
307
|
+
## Complete Example with Custom Controller
|
|
308
|
+
|
|
309
|
+
### View (view/content/contact.html)
|
|
310
|
+
|
|
311
|
+
```html
|
|
312
|
+
<div class="contact-page">
|
|
313
|
+
<h1>Contact Us</h1>
|
|
314
|
+
|
|
315
|
+
<candy:form action="/contact/submit" method="POST" class="contact-form">
|
|
316
|
+
<candy:field name="name" type="text" label="Your Name" placeholder="Enter your name">
|
|
317
|
+
<candy:validate rule="required|minlen:3" message="Name must be at least 3 characters"/>
|
|
318
|
+
</candy:field>
|
|
319
|
+
|
|
320
|
+
<candy:field name="email" type="email" label="Email" placeholder="your@email.com">
|
|
321
|
+
<candy:validate rule="required|email" message="Please enter a valid email"/>
|
|
322
|
+
</candy:field>
|
|
323
|
+
|
|
324
|
+
<candy:field name="subject" type="text" label="Subject" placeholder="What is this about?">
|
|
325
|
+
<candy:validate rule="required|minlen:5" message="Subject must be at least 5 characters"/>
|
|
326
|
+
</candy:field>
|
|
327
|
+
|
|
328
|
+
<candy:field name="message" type="textarea" label="Message" placeholder="Your message...">
|
|
329
|
+
<candy:validate rule="required|minlen:10" message="Message must be at least 10 characters"/>
|
|
330
|
+
</candy:field>
|
|
331
|
+
|
|
332
|
+
<candy:submit text="Send Message" loading="Sending..." class="btn btn-primary"/>
|
|
333
|
+
</candy:form>
|
|
334
|
+
</div>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Controller (controller/contact.js)
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
module.exports = {
|
|
341
|
+
index: Candy => {
|
|
342
|
+
Candy.View.skeleton('default')
|
|
343
|
+
Candy.View.set({content: 'contact'})
|
|
344
|
+
Candy.View.print()
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
submit: Candy => {
|
|
348
|
+
const data = Candy.formData
|
|
349
|
+
|
|
350
|
+
// Save to database
|
|
351
|
+
// await Candy.Mysql.query('INSERT INTO contacts SET ?', data)
|
|
352
|
+
|
|
353
|
+
// Send email notification
|
|
354
|
+
// await Candy.Mail().to('admin@example.com').subject('New Contact').send(data.message)
|
|
355
|
+
|
|
356
|
+
return Candy.return({
|
|
357
|
+
result: {
|
|
358
|
+
success: true,
|
|
359
|
+
message: 'Thank you! We will get back to you soon.',
|
|
360
|
+
redirect: '/'
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Route (route/www.js)
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
Candy.Route.page('/contact', 'contact')
|
|
371
|
+
Candy.Route.post('/contact/submit', 'contact.submit')
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Features
|
|
375
|
+
|
|
376
|
+
- **Automatic CSRF Protection** - Built-in token validation
|
|
377
|
+
- **Client-Side Validation** - HTML5 validation with custom messages
|
|
378
|
+
- **Server-Side Validation** - Automatic validation before controller execution
|
|
379
|
+
- **Session Security** - Form tokens tied to user session, IP, and user agent
|
|
380
|
+
- **Loading States** - Automatic button state management
|
|
381
|
+
- **Error Display** - Automatic error message rendering
|
|
382
|
+
- **Success Messages** - Built-in success message handling
|
|
383
|
+
- **Redirect Support** - Optional redirect after successful submission
|
|
384
|
+
|
|
385
|
+
## Security
|
|
386
|
+
|
|
387
|
+
Forms automatically include:
|
|
388
|
+
|
|
389
|
+
- CSRF token validation
|
|
390
|
+
- Session verification
|
|
391
|
+
- IP address validation
|
|
392
|
+
- User agent verification
|
|
393
|
+
- Token expiration (30 minutes)
|
|
394
|
+
|
|
395
|
+
All validation happens before your controller is executed, ensuring only valid, secure data reaches your code.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Automatic Database Insert
|
|
2
|
+
|
|
3
|
+
Forms can automatically insert data into your database without writing any controller code. This is perfect for simple use cases like waitlists, newsletter signups, contact forms, and feedback collection.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<candy:form table="waitlist">
|
|
9
|
+
<candy:field name="email" type="email" label="Email">
|
|
10
|
+
<candy:validate rule="required|email|unique"/>
|
|
11
|
+
</candy:field>
|
|
12
|
+
|
|
13
|
+
<candy:submit text="Join"/>
|
|
14
|
+
</candy:form>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That's it! The form will automatically:
|
|
18
|
+
1. Validate the email
|
|
19
|
+
2. Check if it's unique in the database
|
|
20
|
+
3. Insert the record into `waitlist` table
|
|
21
|
+
4. Show success message
|
|
22
|
+
|
|
23
|
+
## Complete Example
|
|
24
|
+
|
|
25
|
+
### 1. Create Database Table
|
|
26
|
+
|
|
27
|
+
```sql
|
|
28
|
+
CREATE TABLE `waitlist` (
|
|
29
|
+
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
30
|
+
`email` VARCHAR(255) NOT NULL UNIQUE,
|
|
31
|
+
`name` VARCHAR(255) NOT NULL,
|
|
32
|
+
`created_at` INT UNSIGNED NOT NULL,
|
|
33
|
+
`ip` VARCHAR(45) NULL,
|
|
34
|
+
`user_agent` TEXT NULL,
|
|
35
|
+
INDEX `idx_email` (`email`),
|
|
36
|
+
INDEX `idx_created_at` (`created_at`)
|
|
37
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Create View
|
|
41
|
+
|
|
42
|
+
**view/content/waitlist.html**
|
|
43
|
+
```html
|
|
44
|
+
<div class="waitlist-page">
|
|
45
|
+
<h1>Join Our Waitlist</h1>
|
|
46
|
+
|
|
47
|
+
<candy:form table="waitlist" redirect="/" success="Thank you for joining!">
|
|
48
|
+
<candy:field name="email" type="email" label="Email" placeholder="your@email.com">
|
|
49
|
+
<candy:validate rule="required|email|unique" message="Please enter a valid email"/>
|
|
50
|
+
</candy:field>
|
|
51
|
+
|
|
52
|
+
<candy:field name="name" type="text" label="Name" placeholder="Your name">
|
|
53
|
+
<candy:validate rule="required|minlen:2" message="Name is required"/>
|
|
54
|
+
</candy:field>
|
|
55
|
+
|
|
56
|
+
<candy:set name="created_at" compute="now"/>
|
|
57
|
+
<candy:set name="ip" compute="ip"/>
|
|
58
|
+
<candy:set name="user_agent" compute="user_agent"/>
|
|
59
|
+
|
|
60
|
+
<candy:submit text="Join Waitlist" loading="Joining..." class="btn btn-primary"/>
|
|
61
|
+
</candy:form>
|
|
62
|
+
</div>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. Create Controller
|
|
66
|
+
|
|
67
|
+
**controller/waitlist.js**
|
|
68
|
+
```javascript
|
|
69
|
+
module.exports = Candy => {
|
|
70
|
+
Candy.View.skeleton('default')
|
|
71
|
+
Candy.View.set({content: 'waitlist'})
|
|
72
|
+
Candy.View.print()
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 4. Add Route
|
|
77
|
+
|
|
78
|
+
**route/www.js**
|
|
79
|
+
```javascript
|
|
80
|
+
Candy.Route.page('/waitlist', 'waitlist')
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Done! No form submission handler needed.
|
|
84
|
+
|
|
85
|
+
## Form Attributes
|
|
86
|
+
|
|
87
|
+
### `table` (required)
|
|
88
|
+
Database table name where data will be inserted.
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<candy:form table="newsletter_subscribers">
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `redirect` (optional)
|
|
95
|
+
URL to redirect after successful submission.
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<candy:form table="waitlist" redirect="/thank-you">
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `success` (optional)
|
|
102
|
+
Custom success message to display.
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<candy:form table="waitlist" success="Welcome! We'll notify you soon.">
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Unique Validation
|
|
109
|
+
|
|
110
|
+
Use `unique` rule to prevent duplicate entries:
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<candy:field name="email" type="email">
|
|
114
|
+
<candy:validate rule="required|email|unique" message="This email is already registered"/>
|
|
115
|
+
</candy:field>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The system will:
|
|
119
|
+
1. Check if the value exists in the table
|
|
120
|
+
2. Return error if duplicate found
|
|
121
|
+
3. Show the custom error message
|
|
122
|
+
|
|
123
|
+
## Auto-Set Values
|
|
124
|
+
|
|
125
|
+
Use `<candy:set>` to automatically populate fields:
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<candy:set name="created_at" compute="now"/>
|
|
129
|
+
<candy:set name="ip" compute="ip"/>
|
|
130
|
+
<candy:set name="user_agent" compute="user_agent"/>
|
|
131
|
+
<candy:set name="status" value="pending"/>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Compute Types
|
|
135
|
+
|
|
136
|
+
- `now` - Unix timestamp in seconds
|
|
137
|
+
- `date` - Current date (YYYY-MM-DD)
|
|
138
|
+
- `datetime` - ISO datetime string
|
|
139
|
+
- `timestamp` - Timestamp in milliseconds
|
|
140
|
+
- `ip` - User's IP address
|
|
141
|
+
- `user_agent` - Browser user agent
|
|
142
|
+
- `uuid` - Generate UUID v4
|
|
143
|
+
|
|
144
|
+
### Static Values
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<candy:set name="status" value="pending"/>
|
|
148
|
+
<candy:set name="source" value="website"/>
|
|
149
|
+
<candy:set name="plan" value="free"/>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Conditional Set
|
|
153
|
+
|
|
154
|
+
Only set if field is empty:
|
|
155
|
+
|
|
156
|
+
```html
|
|
157
|
+
<candy:set name="country" value="US" if-empty/>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Use Cases
|
|
161
|
+
|
|
162
|
+
### Newsletter Signup
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<candy:form table="newsletter" success="Thanks for subscribing!">
|
|
166
|
+
<candy:field name="email" type="email">
|
|
167
|
+
<candy:validate rule="required|email|unique"/>
|
|
168
|
+
</candy:field>
|
|
169
|
+
|
|
170
|
+
<candy:set name="subscribed_at" compute="now"/>
|
|
171
|
+
<candy:set name="status" value="active"/>
|
|
172
|
+
|
|
173
|
+
<candy:submit text="Subscribe"/>
|
|
174
|
+
</candy:form>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Feedback Form
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<candy:form table="feedback" redirect="/" success="Thank you for your feedback!">
|
|
181
|
+
<candy:field name="rating" type="number" label="Rating (1-5)">
|
|
182
|
+
<candy:validate rule="required|min:1|max:5"/>
|
|
183
|
+
</candy:field>
|
|
184
|
+
|
|
185
|
+
<candy:field name="comment" type="textarea" label="Comment">
|
|
186
|
+
<candy:validate rule="required|minlen:10"/>
|
|
187
|
+
</candy:field>
|
|
188
|
+
|
|
189
|
+
<candy:set name="created_at" compute="now"/>
|
|
190
|
+
<candy:set name="ip" compute="ip"/>
|
|
191
|
+
|
|
192
|
+
<candy:submit text="Submit Feedback"/>
|
|
193
|
+
</candy:form>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Beta Access Request
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<candy:form table="beta_requests" success="You're on the list!">
|
|
200
|
+
<candy:field name="email" type="email">
|
|
201
|
+
<candy:validate rule="required|email|unique"/>
|
|
202
|
+
</candy:field>
|
|
203
|
+
|
|
204
|
+
<candy:field name="company" type="text">
|
|
205
|
+
<candy:validate rule="required"/>
|
|
206
|
+
</candy:field>
|
|
207
|
+
|
|
208
|
+
<candy:field name="use_case" type="textarea">
|
|
209
|
+
<candy:validate rule="required|minlen:20"/>
|
|
210
|
+
</candy:field>
|
|
211
|
+
|
|
212
|
+
<candy:set name="requested_at" compute="now"/>
|
|
213
|
+
<candy:set name="status" value="pending"/>
|
|
214
|
+
|
|
215
|
+
<candy:submit text="Request Access"/>
|
|
216
|
+
</candy:form>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Error Handling
|
|
220
|
+
|
|
221
|
+
The system automatically handles:
|
|
222
|
+
|
|
223
|
+
- **Validation errors** - Shows field-specific error messages
|
|
224
|
+
- **Duplicate entries** - Shows unique constraint errors
|
|
225
|
+
- **Database errors** - Shows generic error message
|
|
226
|
+
- **Missing database** - Shows configuration error
|
|
227
|
+
|
|
228
|
+
All errors are displayed inline next to the relevant field.
|
|
229
|
+
|
|
230
|
+
## Security
|
|
231
|
+
|
|
232
|
+
Automatic DB insert includes all security features:
|
|
233
|
+
|
|
234
|
+
- CSRF token validation
|
|
235
|
+
- Session verification
|
|
236
|
+
- IP address validation
|
|
237
|
+
- User agent verification
|
|
238
|
+
- SQL injection prevention (parameterized queries)
|
|
239
|
+
- Token expiration (30 minutes)
|
|
240
|
+
|
|
241
|
+
## When to Use
|
|
242
|
+
|
|
243
|
+
**Use automatic DB insert when:**
|
|
244
|
+
- Simple data collection (waitlist, newsletter, feedback)
|
|
245
|
+
- No complex business logic needed
|
|
246
|
+
- Direct database insert is sufficient
|
|
247
|
+
- You want rapid development
|
|
248
|
+
|
|
249
|
+
**Use custom controller when:**
|
|
250
|
+
- Need to send emails
|
|
251
|
+
- Complex validation logic
|
|
252
|
+
- Multiple database operations
|
|
253
|
+
- External API calls
|
|
254
|
+
- Custom response handling
|
|
255
|
+
|
|
256
|
+
## Combining with Custom Logic
|
|
257
|
+
|
|
258
|
+
You can add custom logic by specifying a custom `action` attribute. When you do this, the form data is validated and prepared, but the automatic DB insert is skipped. Instead, your controller receives the validated data via `Candy.formData`:
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
// In your view:
|
|
262
|
+
// <candy:form action="/contact/submit" table="contacts">
|
|
263
|
+
|
|
264
|
+
// In your controller:
|
|
265
|
+
Candy.Route.post('/contact/submit', async Candy => {
|
|
266
|
+
// Candy.formData contains validated form data
|
|
267
|
+
// Candy.formConfig contains form configuration
|
|
268
|
+
|
|
269
|
+
// Perform custom logic (send email, call API, etc.)
|
|
270
|
+
await sendEmail(Candy.formData.email, 'Thank you!')
|
|
271
|
+
|
|
272
|
+
// Manually insert to database if needed
|
|
273
|
+
await Candy.Mysql.query('INSERT INTO contacts SET ?', Candy.formData)
|
|
274
|
+
|
|
275
|
+
return Candy.return({
|
|
276
|
+
result: {success: true, message: 'Message sent!'}
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
const data = Candy.formData
|
|
280
|
+
|
|
281
|
+
// Send welcome email
|
|
282
|
+
Candy.Mail()
|
|
283
|
+
.to(data.email)
|
|
284
|
+
.subject('Welcome!')
|
|
285
|
+
.send('Thanks for joining!')
|
|
286
|
+
|
|
287
|
+
// Return custom response
|
|
288
|
+
return Candy.return({
|
|
289
|
+
result: {
|
|
290
|
+
success: true,
|
|
291
|
+
message: 'Check your email!'
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
But for most cases, automatic insert is all you need!
|