Files
awesome-copilot/skills/create-web-form/references/form-data-handling.md
jhauga 27149859a4 Add skill to create web forms
Add skill to create web forms
2026-02-09 19:22:54 -05:00

628 lines
17 KiB
Markdown

# Form Data Handling Reference
---
## Section 1: Sending and Retrieving Form Data
**Source:** https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Sending_and_retrieving_form_data
### Overview
Once form data is validated on the client-side, it is ready to submit. This section covers what happens when a user submits a form, where the data goes, and how to handle it on the server.
### Client/Server Architecture
The web uses a basic client/server architecture:
- **Client** (web browser) sends an HTTP request to a **server**.
- **Server** (Apache, Nginx, IIS, Tomcat) responds using the same protocol.
- HTML forms are a user-friendly way to configure HTTP requests for sending data.
### On the Client Side: Defining How to Send Data
The `<form>` element controls how data is sent. Two critical attributes are `action` and `method`.
#### The `action` Attribute
The `action` attribute defines where form data gets sent. It must be a valid relative or absolute URL.
**Absolute URL:**
```html
<form action="https://www.example.com">...</form>
```
**Relative URL (same origin):**
```html
<form action="/somewhere_else">...</form>
```
**Same page (no attributes or empty action):**
```html
<form>...</form>
```
**Security Note:** Use HTTPS (secure HTTP) to encrypt data. If a secure form posts to an insecure HTTP URL, browsers display a security warning.
#### The `method` Attribute
Two main HTTP methods transmit form data: **GET** and **POST**.
### GET Method
- Used by browsers to ask the server to send back a resource.
- Data is appended to the URL as query parameters.
- Browser sends an empty body.
**Example Form:**
```html
<form action="https://www.example.com/greet" method="GET">
<div>
<label for="say">What greeting do you want to say?</label>
<input name="say" id="say" value="Hi" />
</div>
<div>
<label for="to">Who do you want to say it to?</label>
<input name="to" id="to" value="Mom" />
</div>
<div>
<button>Send my greetings</button>
</div>
</form>
```
**Result URL:** `https://www.example.com/greet?say=Hi&to=Mom`
**HTTP Request:**
```http
GET /?say=Hi&to=Mom HTTP/2.0
Host: example.com
```
**When to use:** Reading data, non-sensitive information.
### POST Method
- Used to send data that the server should process.
- Data is included in the HTTP request body, not the URL.
- More secure for sensitive data (passwords, etc.).
**Example Form:**
```html
<form action="https://www.example.com/greet" method="POST">
<div>
<label for="say">What greeting do you want to say?</label>
<input name="say" id="say" value="Hi" />
</div>
<div>
<label for="to">Who do you want to say it to?</label>
<input name="to" id="to" value="Mom" />
</div>
<div>
<button>Send my greetings</button>
</div>
</form>
```
**HTTP Request:**
```http
POST / HTTP/2.0
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
say=Hi&to=Mom
```
**When to use:** Sensitive data, large amounts of data, modifying server state.
### Viewing HTTP Requests in Browser DevTools
1. Open Developer Tools (F12).
2. Select the "Network" tab.
3. Select "All" to see all requests.
4. Click the request in the "Name" tab.
5. View "Request" (Firefox) or "Payload" (Chrome/Edge).
### On the Server Side: Retrieving the Data
The server receives data as a string parsed into key/value pairs. Access method depends on the server platform.
#### Example: Raw PHP
```php
<?php
// Access POST data
$say = htmlspecialchars($_POST["say"]);
$to = htmlspecialchars($_POST["to"]);
// Access GET data
// $say = htmlspecialchars($_GET["say"]);
echo $say, " ", $to;
?>
```
**Output:** `Hi Mom`
#### Example: Python with Flask
```python
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def form():
return render_template('form.html')
@app.route('/hello', methods=['GET', 'POST'])
def hello():
return render_template('greeting.html',
say=request.form['say'],
to=request.form['to'])
if __name__ == "__main__":
app.run()
```
#### Other Server-Side Frameworks
| Language | Frameworks |
|------------|-----------------------------------------|
| Python | Django, Flask, web2py, py4web |
| Node.js | Express, Next.js, Nuxt, Remix |
| PHP | Laravel, Laminas, Symfony |
| Ruby | Ruby On Rails |
| Java | Spring Boot |
### A Special Case: Sending Files
Files are binary data and require special handling. Three steps are needed:
#### The `enctype` Attribute
This specifies the `Content-Type` HTTP header.
- **Default:** `application/x-www-form-urlencoded`
- **For files:** `multipart/form-data`
**File Upload Example:**
```html
<form
method="post"
action="https://example.com/upload"
enctype="multipart/form-data">
<div>
<label for="file">Choose a file</label>
<input type="file" id="file" name="myFile" />
</div>
<div>
<button>Send the file</button>
</div>
</form>
```
**Requirements:**
- Set `method` to `POST` (file content cannot go in a URL).
- Set `enctype` to `multipart/form-data`.
- Include one or more `<input type="file">` controls.
**Note:** Servers can limit file and request sizes to prevent abuse.
### Security Considerations
#### Be Paranoid: Never Trust Your Users
All incoming data must be checked and sanitized:
1. **Escape Dangerous Characters** -- Watch for executable code patterns (JavaScript, SQL commands). Use server-side escaping functions. Different contexts require different escaping.
2. **Limit Incoming Data** -- Only accept what is necessary. Set maximum sizes for requests.
3. **Sandbox Uploaded Files** -- Store on a different server. Serve through a different subdomain or domain. Never execute uploaded files directly.
**Key Rule:** Never trust client-side validation alone -- always validate on the server. Client-side validation can be bypassed; the server has no way to verify what truly happened on the client.
### Quick Reference: GET vs POST
| Aspect | GET | POST |
|--------------------|--------------------------------------|----------------------------------------|
| Data location | Visible in URL as query parameters | Hidden in request body |
| Data size | Limited by URL length | No inherent limit |
| Security | Not suitable for sensitive data | Better for sensitive/large data |
| Caching | Can be cached | Not cached |
| Use case | Reading/retrieving data | Modifying server state, sending files |
### Important Notes
- **Form data format:** Name/value pairs joined with ampersands (`name=value&name2=value2`).
- **URL encoding:** Special characters are URL-encoded in query parameters.
- **Default form target:** Without `action`, data submits to the current page.
- **Secure protocol:** Always use HTTPS for sensitive data.
---
## Section 2: Form Validation
**Source:** https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Form_validation
### Overview
Client-side form validation helps ensure data entered matches requirements set by form controls. While important for **user experience**, it must **always** be paired with server-side validation since client-side validation is easily bypassed by malicious users.
### Built-In HTML Validation Attributes
#### `required`
Specifies that a form field must be filled before submission.
```html
<input id="choose" name="i-like" required />
```
- Input matches `:required` and `:invalid` pseudo-classes when empty.
- For radio buttons, one button in a same-named group must be checked.
#### `minlength` and `maxlength`
Constrains character length for text fields and textareas.
```html
<input type="text" minlength="6" maxlength="6" />
<textarea maxlength="140"></textarea>
```
#### `min`, `max`, and `step`
Constrains numeric values and their increments.
```html
<input type="number" min="1" max="10" step="1" />
<input type="date" min="2024-01-01" max="2024-12-31" />
```
#### `type`
Validates against specific formats (email, URL, number, date, etc.).
```html
<input type="email" />
<input type="url" />
<input type="number" />
```
#### `pattern`
Validates against a regular expression.
```html
<input
type="text"
pattern="[Bb]anana|[Cc]herry"
required
/>
```
**Pattern Examples:**
| Pattern | Matches |
|----------|------------------------------------------|
| `a` | Single character 'a' |
| `abc` | 'a' followed by 'b' followed by 'c' |
| `ab?c` | 'ac' or 'abc' |
| `ab*c` | 'ac', 'abc', 'abbbbbc', etc. |
| `a\|b` | 'a' or 'b' |
### CSS Pseudo-Classes for Validation States
#### Valid States
```css
input:valid {
border: 2px solid black;
}
input:user-valid {
/* Matches after user interaction */
}
input:in-range {
/* For inputs with min/max */
}
```
#### Invalid States
```css
input:invalid {
border: 2px dashed red;
}
input:user-invalid {
/* Matches after user interaction */
}
input:out-of-range {
/* For inputs with min/max */
}
input:required {
/* Matches required fields */
}
```
### Complete Built-In Validation Example
```html
<form>
<p>Please complete all required (*) fields.</p>
<fieldset>
<legend>Do you have a driver's license? *</legend>
<input type="radio" required name="driver" id="r1" value="yes" />
<label for="r1">Yes</label>
<input type="radio" required name="driver" id="r2" value="no" />
<label for="r2">No</label>
</fieldset>
<p>
<label for="age">How old are you?</label>
<input type="number" min="12" max="120" step="1" id="age" name="age" />
</p>
<p>
<label for="fruit">What's your favorite fruit? *</label>
<input
type="text"
id="fruit"
name="fruit"
list="fruits"
required
pattern="[Bb]anana|[Cc]herry|[Aa]pple"
/>
<datalist id="fruits">
<option>Banana</option>
<option>Cherry</option>
<option>Apple</option>
</datalist>
</p>
<p>
<label for="email">Email address:</label>
<input type="email" id="email" name="email" />
</p>
<button>Submit</button>
</form>
```
### Constraint Validation API
The Constraint Validation API provides methods and properties for custom validation logic.
#### Key Properties
**`validationMessage`** -- Returns localized validation error message.
**`validity`** -- Returns a `ValidityState` object with these properties:
| Property | Description |
|-------------------|-----------------------------------------------------|
| `valid` | `true` if element meets all constraints |
| `valueMissing` | `true` if required but empty |
| `typeMismatch` | `true` if value does not match type (e.g., email) |
| `patternMismatch` | `true` if pattern does not match |
| `tooLong` | `true` if exceeds `maxlength` |
| `tooShort` | `true` if below `minlength` |
| `rangeOverflow` | `true` if exceeds `max` |
| `rangeUnderflow` | `true` if below `min` |
| `customError` | `true` if custom error set via `setCustomValidity()` |
**`willValidate`** -- Boolean, `true` if element will be validated on form submission.
#### Key Methods
```javascript
// Check validity without submitting
element.checkValidity() // Returns boolean
// Report validity to user
element.reportValidity() // Shows browser's error message
// Set custom error message
element.setCustomValidity("Custom error text")
// Clear custom error
element.setCustomValidity("")
```
### JavaScript Custom Validation Examples
#### Basic Custom Error Message
```javascript
const email = document.getElementById("mail");
email.addEventListener("input", (event) => {
if (email.validity.typeMismatch) {
email.setCustomValidity("I am expecting an email address!");
} else {
email.setCustomValidity("");
}
});
```
#### Extending Built-In Validation
```javascript
const email = document.getElementById("mail");
email.addEventListener("input", (event) => {
// Reset custom validity
email.setCustomValidity("");
// Check built-in constraints first
if (!email.validity.valid) {
return;
}
// Add custom constraint
if (!email.value.endsWith("@example.com")) {
email.setCustomValidity("Please enter an email with @example.com domain");
}
});
```
#### Complex Form Validation with Custom Messages
```javascript
const form = document.querySelector("form");
const email = document.getElementById("mail");
const emailError = document.querySelector("#mail + span.error");
email.addEventListener("input", (event) => {
if (email.validity.valid) {
emailError.textContent = "";
emailError.className = "error";
} else {
showError();
}
});
form.addEventListener("submit", (event) => {
if (!email.validity.valid) {
showError();
event.preventDefault();
}
});
function showError() {
if (email.validity.valueMissing) {
emailError.textContent = "You need to enter an email address.";
} else if (email.validity.typeMismatch) {
emailError.textContent = "Entered value needs to be an email address.";
} else if (email.validity.tooShort) {
emailError.textContent =
`Email should be at least ${email.minLength} characters; you entered ${email.value.length}.`;
}
emailError.className = "error active";
}
```
#### Disabling Built-In Validation with `novalidate`
Use `novalidate` on the form to disable the browser's automatic validation while retaining CSS pseudo-classes:
```html
<form novalidate>
<input type="email" id="mail" required minlength="8" />
<span class="error" aria-live="polite"></span>
</form>
```
### Manual Validation Without the Constraint API
For custom form controls or complete control over validation:
```javascript
const form = document.querySelector("form");
const email = document.getElementById("mail");
const error = document.getElementById("error");
const emailRegExp = /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z\d-]+(?:\.[a-z\d-]+)*$/i;
const isValidEmail = () => {
return email.value.length !== 0 && emailRegExp.test(email.value);
};
const setEmailClass = (isValid) => {
email.className = isValid ? "valid" : "invalid";
};
const updateError = (isValid) => {
if (isValid) {
error.textContent = "";
error.removeAttribute("class");
} else {
error.textContent = "Please enter a valid email address.";
error.setAttribute("class", "active");
}
};
email.addEventListener("input", () => {
const validity = isValidEmail();
setEmailClass(validity);
updateError(validity);
});
form.addEventListener("submit", (event) => {
event.preventDefault();
const validity = isValidEmail();
setEmailClass(validity);
updateError(validity);
});
```
### Styling Error Messages
```css
/* Invalid field styling */
input:invalid {
border-color: #990000;
background-color: #ffdddd;
}
input:focus:invalid {
outline: none;
}
/* Error message container */
.error {
width: 100%;
padding: 0;
font-size: 80%;
color: white;
background-color: #990000;
border-radius: 0 0 5px 5px;
}
.error.active {
padding: 0.3em;
}
```
### Accessibility Best Practices
1. **Mark required fields** with an asterisk in the label:
```html
<label for="name">Name *</label>
```
2. **Use `aria-live`** for dynamic error messages:
```html
<span class="error" aria-live="polite"></span>
```
3. **Provide clear, helpful messages** that explain what is expected and how to fix errors.
4. **Avoid relying on color alone** to indicate errors.
### Validation Summary
| Approach | Pros | Cons |
|------------------------|---------------------------------------------|----------------------------------------|
| HTML built-in | No JavaScript needed, fast | Limited customization |
| Constraint Validation API | Modern, integrates with built-in features | Requires JavaScript |
| Fully manual (JS) | Complete control over UI and logic | More code, must handle everything |
- **HTML validation** is faster and does not require JavaScript.
- **JavaScript validation** provides more customization and control.
- **Always validate server-side** -- client-side validation is not secure.
- **Use the Constraint Validation API** for modern, built-in functionality.
- **Provide clear error messages** and guidance to users.
- **Style validation states** with `:valid` and `:invalid` pseudo-classes.