|
|
@@ -0,0 +1,554 @@
|
|
|
+using Astralis;
|
|
|
+using Invercargill;
|
|
|
+using Invercargill.DataStructures;
|
|
|
+
|
|
|
+/**
|
|
|
+ * FormData Example
|
|
|
+ *
|
|
|
+ * Demonstrates handling POST form data in Astralis using async HttpHandler.
|
|
|
+ * Shows both application/x-www-form-urlencoded and multipart/form-data handling.
|
|
|
+ */
|
|
|
+
|
|
|
+// HTML form page handler
|
|
|
+class FormPageHandler : Object, HttpHandler {
|
|
|
+ public async HttpResult handle(HttpContext context) throws Error {
|
|
|
+ var headers = new Catalogue<string, string>();
|
|
|
+ headers.add("Content-Type", "text/html");
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ """<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+<head>
|
|
|
+ <title>Form Data Example</title>
|
|
|
+ <style>
|
|
|
+ body { font-family: Arial, sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; }
|
|
|
+ h1 { color: #333; }
|
|
|
+ .form-section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
|
|
|
+ label { display: block; margin: 10px 0 5px; font-weight: bold; }
|
|
|
+ input, textarea, select { width: 100%; padding: 8px; margin: 5px 0; box-sizing: border-box; }
|
|
|
+ button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; }
|
|
|
+ button:hover { background: #0056b3; }
|
|
|
+ a { color: #007bff; text-decoration: none; }
|
|
|
+ a:hover { text-decoration: underline; }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <h1>Form Data Examples</h1>
|
|
|
+
|
|
|
+ <div class="form-section">
|
|
|
+ <h2>Simple Form (URL Encoded)</h2>
|
|
|
+ <form action="/submit-simple" method="POST">
|
|
|
+ <label for="name">Name:</label>
|
|
|
+ <input type="text" id="name" name="name" required>
|
|
|
+
|
|
|
+ <label for="email">Email:</label>
|
|
|
+ <input type="email" id="email" name="email" required>
|
|
|
+
|
|
|
+ <button type="submit">Submit</button>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-section">
|
|
|
+ <h2>Registration Form (URL Encoded)</h2>
|
|
|
+ <form action="/submit-register" method="POST">
|
|
|
+ <label for="username">Username:</label>
|
|
|
+ <input type="text" id="username" name="username" required>
|
|
|
+
|
|
|
+ <label for="password">Password:</label>
|
|
|
+ <input type="password" id="password" name="password" required>
|
|
|
+
|
|
|
+ <label for="age">Age:</label>
|
|
|
+ <input type="number" id="age" name="age" min="18" max="120">
|
|
|
+
|
|
|
+ <label for="country">Country:</label>
|
|
|
+ <select id="country" name="country">
|
|
|
+ <option value="us">United States</option>
|
|
|
+ <option value="uk">United Kingdom</option>
|
|
|
+ <option value="nz">New Zealand</option>
|
|
|
+ <option value="au">Australia</option>
|
|
|
+ </select>
|
|
|
+
|
|
|
+ <label for="bio">Bio:</label>
|
|
|
+ <textarea id="bio" name="bio" rows="4"></textarea>
|
|
|
+
|
|
|
+ <label>
|
|
|
+ <input type="checkbox" name="newsletter" value="yes"> Subscribe to newsletter
|
|
|
+ </label>
|
|
|
+
|
|
|
+ <button type="submit">Register</button>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-section">
|
|
|
+ <h2>Search Form (URL Encoded)</h2>
|
|
|
+ <form action="/submit-search" method="POST">
|
|
|
+ <label for="query">Search Query:</label>
|
|
|
+ <input type="text" id="query" name="query" required>
|
|
|
+
|
|
|
+ <label for="category">Category:</label>
|
|
|
+ <select id="category" name="category">
|
|
|
+ <option value="">All Categories</option>
|
|
|
+ <option value="books">Books</option>
|
|
|
+ <option value="electronics">Electronics</option>
|
|
|
+ <option value="clothing">Clothing</option>
|
|
|
+ </select>
|
|
|
+
|
|
|
+ <label for="min_price">Min Price:</label>
|
|
|
+ <input type="number" id="min_price" name="min_price" min="0" step="0.01">
|
|
|
+
|
|
|
+ <label for="max_price">Max Price:</label>
|
|
|
+ <input type="number" id="max_price" name="max_price" min="0" step="0.01">
|
|
|
+
|
|
|
+ <button type="submit">Search</button>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-section">
|
|
|
+ <h2>File Upload (Multipart)</h2>
|
|
|
+ <form action="/submit-file" method="POST" enctype="multipart/form-data">
|
|
|
+ <label for="description">Description:</label>
|
|
|
+ <input type="text" id="description" name="description">
|
|
|
+
|
|
|
+ <label for="file">File:</label>
|
|
|
+ <input type="file" id="file" name="file">
|
|
|
+
|
|
|
+ <button type="submit">Upload</button>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-section">
|
|
|
+ <h2>Links</h2>
|
|
|
+ <p><a href="/form-debug">Form Debug Tool</a></p>
|
|
|
+ </div>
|
|
|
+</body>
|
|
|
+</html>""",
|
|
|
+ StatusCode.OK,
|
|
|
+ headers
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Simple form submission handler
|
|
|
+class SimpleFormHandler : Object, HttpHandler {
|
|
|
+ public async HttpResult handle(HttpContext context) throws Error {
|
|
|
+ if (!context.request.is_post()) {
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ "Please use POST method",
|
|
|
+ StatusCode.METHOD_NOT_ALLOWED
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse form data asynchronously from the request body
|
|
|
+ FormData form_data = yield FormDataParser.parse(
|
|
|
+ context.request.request_body,
|
|
|
+ context.request.content_type
|
|
|
+ );
|
|
|
+
|
|
|
+ var name = form_data.get_field_or_default("name", "Anonymous");
|
|
|
+ var email = form_data.get_field_or_default("email", "no-email@example.com");
|
|
|
+
|
|
|
+ var parts = new Series<string>();
|
|
|
+ parts.add("Form Submission Received!\n");
|
|
|
+ parts.add("=========================\n\n");
|
|
|
+ parts.add(@"Name: $name\n");
|
|
|
+ parts.add(@"Email: $email\n");
|
|
|
+ parts.add("\nAll form data:\n");
|
|
|
+
|
|
|
+ form_data.fields.to_immutable_buffer()
|
|
|
+ .iterate((grouping) => {
|
|
|
+ grouping.iterate((value) => {
|
|
|
+ parts.add(@" $(grouping.key): $value\n");
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ var result = parts.to_immutable_buffer()
|
|
|
+ .aggregate<string>("", (acc, s) => acc + s);
|
|
|
+
|
|
|
+ return new BufferedHttpResult.from_string(result);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Registration form submission handler
|
|
|
+class RegisterFormHandler : Object, HttpHandler {
|
|
|
+ public async HttpResult handle(HttpContext context) throws Error {
|
|
|
+ if (!context.request.is_post()) {
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ "Please use POST method",
|
|
|
+ StatusCode.METHOD_NOT_ALLOWED
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ FormData form_data = yield FormDataParser.parse(
|
|
|
+ context.request.request_body,
|
|
|
+ context.request.content_type
|
|
|
+ );
|
|
|
+
|
|
|
+ var username = form_data.get_field("username");
|
|
|
+ var password = form_data.get_field("password");
|
|
|
+ var age_str = form_data.get_field_or_default("age", "0");
|
|
|
+ var country = form_data.get_field_or_default("country", "us");
|
|
|
+ var bio = form_data.get_field_or_default("bio", "");
|
|
|
+ var newsletter = form_data.get_field("newsletter");
|
|
|
+
|
|
|
+ // Validation
|
|
|
+ if (username == null || username == "") {
|
|
|
+ var headers = new Catalogue<string, string>();
|
|
|
+ headers.add("Content-Type", "application/json");
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ @"{ \"error\": \"Username is required\" }",
|
|
|
+ StatusCode.BAD_REQUEST,
|
|
|
+ headers
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (password == null || password == "") {
|
|
|
+ var headers = new Catalogue<string, string>();
|
|
|
+ headers.add("Content-Type", "application/json");
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ @"{ \"error\": \"Password is required\" }",
|
|
|
+ StatusCode.BAD_REQUEST,
|
|
|
+ headers
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ var age = int.parse(age_str);
|
|
|
+ if (age < 18) {
|
|
|
+ var headers = new Catalogue<string, string>();
|
|
|
+ headers.add("Content-Type", "application/json");
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ @"{ \"error\": \"You must be at least 18 years old\" }",
|
|
|
+ StatusCode.BAD_REQUEST,
|
|
|
+ headers
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build JSON response using Series
|
|
|
+ var json_parts = new Series<string>();
|
|
|
+ json_parts.add(@"{ \"success\": true, \"user\": {");
|
|
|
+ json_parts.add(@" \"username\": \"$username\",");
|
|
|
+ json_parts.add(@" \"age\": $age,");
|
|
|
+ json_parts.add(@" \"country\": \"$country\",");
|
|
|
+ json_parts.add(@" \"bio\": \"$(bio.replace("\"", "\\\""))\",");
|
|
|
+ json_parts.add(@" \"newsletter\": $(newsletter != null ? "true" : "false")");
|
|
|
+ json_parts.add(@"} }");
|
|
|
+
|
|
|
+ var json_string = json_parts.to_immutable_buffer()
|
|
|
+ .aggregate<string>("", (acc, s) => acc + s);
|
|
|
+
|
|
|
+ var headers = new Catalogue<string, string>();
|
|
|
+ headers.add("Content-Type", "application/json");
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ json_string,
|
|
|
+ StatusCode.OK,
|
|
|
+ headers
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Search form submission handler
|
|
|
+class SearchFormHandler : Object, HttpHandler {
|
|
|
+ public async HttpResult handle(HttpContext context) throws Error {
|
|
|
+ if (!context.request.is_post()) {
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ "Please use POST method",
|
|
|
+ StatusCode.METHOD_NOT_ALLOWED
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ FormData form_data = yield FormDataParser.parse(
|
|
|
+ context.request.request_body,
|
|
|
+ context.request.content_type
|
|
|
+ );
|
|
|
+
|
|
|
+ var query = form_data.get_field("query");
|
|
|
+ var category = form_data.get_field_or_default("category", "");
|
|
|
+ var min_price = double.parse(form_data.get_field_or_default("min_price", "0"));
|
|
|
+ var max_price = double.parse(form_data.get_field_or_default("max_price", "999999"));
|
|
|
+
|
|
|
+ if (query == null || query == "") {
|
|
|
+ var headers = new Catalogue<string, string>();
|
|
|
+ headers.add("Content-Type", "application/json");
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ @"{ \"error\": \"Search query is required\" }",
|
|
|
+ StatusCode.BAD_REQUEST,
|
|
|
+ headers
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Simulated search results using Enumerable operations
|
|
|
+ var all_products = new Series<Product>();
|
|
|
+ all_products.add(new Product(1, "Book A", "books", 15.99));
|
|
|
+ all_products.add(new Product(2, "Book B", "books", 24.99));
|
|
|
+ all_products.add(new Product(3, "Laptop", "electronics", 999.99));
|
|
|
+ all_products.add(new Product(4, "Phone", "electronics", 699.99));
|
|
|
+ all_products.add(new Product(5, "Shirt", "clothing", 29.99));
|
|
|
+ all_products.add(new Product(6, "Pants", "clothing", 49.99));
|
|
|
+
|
|
|
+ // Filter results using Enumerable operations
|
|
|
+ var results = all_products.to_immutable_buffer()
|
|
|
+ .where(p => {
|
|
|
+ var matches_query = p.name.down().contains(query.down());
|
|
|
+ var matches_category = category == "" || p.category == category;
|
|
|
+ var matches_price = p.price >= min_price && p.price <= max_price;
|
|
|
+ return matches_query && matches_category && matches_price;
|
|
|
+ });
|
|
|
+
|
|
|
+ var json_parts = new Series<string>();
|
|
|
+ json_parts.add(@"{ \"query\": \"$query\", \"category\": \"$category\", \"min_price\": $min_price, \"max_price\": $max_price, \"results\": [");
|
|
|
+
|
|
|
+ bool first = true;
|
|
|
+ results.iterate((product) => {
|
|
|
+ if (!first) json_parts.add(", ");
|
|
|
+ json_parts.add(product.to_json());
|
|
|
+ first = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ json_parts.add("] }");
|
|
|
+
|
|
|
+ var json_string = json_parts.to_immutable_buffer()
|
|
|
+ .aggregate<string>("", (acc, s) => acc + s);
|
|
|
+
|
|
|
+ var headers = new Catalogue<string, string>();
|
|
|
+ headers.add("Content-Type", "application/json");
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ json_string,
|
|
|
+ StatusCode.OK,
|
|
|
+ headers
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// File upload handler (multipart/form-data)
|
|
|
+class FileUploadHandler : Object, HttpHandler {
|
|
|
+ public async HttpResult handle(HttpContext context) throws Error {
|
|
|
+ if (!context.request.is_post()) {
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ "Please use POST method",
|
|
|
+ StatusCode.METHOD_NOT_ALLOWED
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ FormData form_data = yield FormDataParser.parse(
|
|
|
+ context.request.request_body,
|
|
|
+ context.request.content_type
|
|
|
+ );
|
|
|
+
|
|
|
+ var description = form_data.get_field_or_default("description", "");
|
|
|
+ var file = form_data.get_file("file");
|
|
|
+
|
|
|
+ var parts = new Series<string>();
|
|
|
+ parts.add("File Upload Result\n");
|
|
|
+ parts.add("==================\n\n");
|
|
|
+ parts.add(@"Description: $description\n");
|
|
|
+
|
|
|
+ if (file != null) {
|
|
|
+ parts.add(@"\nFile Information:\n");
|
|
|
+ parts.add(@" Field Name: $(file.field_name)\n");
|
|
|
+ parts.add(@" Filename: $(file.filename)\n");
|
|
|
+ parts.add(@" Content-Type: $(file.content_type)\n");
|
|
|
+ parts.add(@" Size: $(file.data.length) bytes\n");
|
|
|
+ } else {
|
|
|
+ parts.add("\nNo file uploaded.\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ var result = parts.to_immutable_buffer()
|
|
|
+ .aggregate<string>("", (acc, s) => acc + s);
|
|
|
+
|
|
|
+ return new BufferedHttpResult.from_string(result);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Form debug tool handler
|
|
|
+class FormDebugHandler : Object, HttpHandler {
|
|
|
+ public async HttpResult handle(HttpContext context) throws Error {
|
|
|
+ FormData? form_data = null;
|
|
|
+ string? body_text = null;
|
|
|
+
|
|
|
+ // Try to parse form data if this is a POST with form content
|
|
|
+ if (context.request.is_post() &&
|
|
|
+ (context.request.is_form_urlencoded() || context.request.is_multipart())) {
|
|
|
+ try {
|
|
|
+ form_data = yield FormDataParser.parse(
|
|
|
+ context.request.request_body,
|
|
|
+ context.request.content_type
|
|
|
+ );
|
|
|
+ } catch (Error e) {
|
|
|
+ body_text = @"Error parsing form data: $(e.message)";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get counts (uint type)
|
|
|
+ uint field_count = form_data != null ? form_data.field_count() : 0;
|
|
|
+ uint file_count = form_data != null ? form_data.file_count() : 0;
|
|
|
+
|
|
|
+ var parts = new Series<string>();
|
|
|
+ parts.add("""<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+<head>
|
|
|
+ <title>Form Debug Tool</title>
|
|
|
+ <style>
|
|
|
+ body { font-family: monospace; max-width: 800px; margin: 40px auto; padding: 20px; background: #f5f5f5; }
|
|
|
+ .section { background: white; padding: 20px; margin: 20px 0; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
|
|
|
+ h1 { color: #333; }
|
|
|
+ h2 { color: #666; border-bottom: 2px solid #007bff; padding-bottom: 10px; }
|
|
|
+ table { width: 100%; border-collapse: collapse; margin: 10px 0; }
|
|
|
+ th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
|
|
|
+ th { background: #007bff; color: white; }
|
|
|
+ tr:hover { background: #f9f9f9; }
|
|
|
+ .empty { color: #999; font-style: italic; }
|
|
|
+ a { color: #007bff; text-decoration: none; }
|
|
|
+ a:hover { text-decoration: underline; }
|
|
|
+ form { margin: 20px 0; }
|
|
|
+ input, textarea { width: 100%; padding: 8px; margin: 5px 0; box-sizing: border-box; }
|
|
|
+ button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <h1>Form Debug Tool</h1>
|
|
|
+
|
|
|
+ <div class="section">
|
|
|
+ <h2>Test Form</h2>
|
|
|
+ <form action="/form-debug" method="POST" enctype="multipart/form-data">
|
|
|
+ <label>Text Field: <input type="text" name="test_field" value="test value"></label>
|
|
|
+ <label>File: <input type="file" name="test_file"></label>
|
|
|
+ <button type="submit">Submit Test</button>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section">
|
|
|
+ <h2>Request Information</h2>
|
|
|
+ <table>
|
|
|
+ <tr><th>Method</th><td>$(context.request.method)</td></tr>
|
|
|
+ <tr><th>Path</th><td>$(context.request.raw_path)</td></tr>
|
|
|
+ <tr><th>Content Type</th><td>$(context.request.content_type)</td></tr>
|
|
|
+ <tr><th>Content Length</th><td>$(context.request.content_length)</td></tr>
|
|
|
+ <tr><th>Is Form URL Encoded</th><td>$(context.request.is_form_urlencoded())</td></tr>
|
|
|
+ <tr><th>Is Multipart</th><td>$(context.request.is_multipart())</td></tr>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+""");
|
|
|
+
|
|
|
+ if (form_data != null) {
|
|
|
+ parts.add(@" <div class=\"section\">\n");
|
|
|
+ parts.add(@" <h2>Form Fields ($field_count fields)</h2>\n");
|
|
|
+
|
|
|
+ if (field_count == 0) {
|
|
|
+ parts.add(@" <p class=\"empty\">No form fields received.</p>\n");
|
|
|
+ } else {
|
|
|
+ parts.add(@" <table>\n");
|
|
|
+ parts.add(@" <tr><th>Field Name</th><th>Value</th></tr>\n");
|
|
|
+
|
|
|
+ form_data.fields.to_immutable_buffer()
|
|
|
+ .iterate((grouping) => {
|
|
|
+ grouping.iterate((value) => {
|
|
|
+ parts.add(@" <tr><td>$(grouping.key)</td><td>$(value.escape(""))</td></tr>\n");
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ parts.add(@" </table>\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ parts.add(@" </div>\n");
|
|
|
+
|
|
|
+ parts.add(@" <div class=\"section\">\n");
|
|
|
+ parts.add(@" <h2>File Uploads ($file_count files)</h2>\n");
|
|
|
+
|
|
|
+ if (file_count == 0) {
|
|
|
+ parts.add(@" <p class=\"empty\">No files uploaded.</p>\n");
|
|
|
+ } else {
|
|
|
+ parts.add(@" <table>\n");
|
|
|
+ parts.add(@" <tr><th>Field Name</th><th>Filename</th><th>Content-Type</th><th>Size</th></tr>\n");
|
|
|
+
|
|
|
+ form_data.files.to_immutable_buffer()
|
|
|
+ .iterate((grouping) => {
|
|
|
+ grouping.iterate((file) => {
|
|
|
+ parts.add(@" <tr><td>$(file.field_name)</td><td>$(file.filename)</td><td>$(file.content_type)</td><td>$(file.data.length) bytes</td></tr>\n");
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ parts.add(@" </table>\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ parts.add(@" </div>\n");
|
|
|
+ } else if (body_text != null) {
|
|
|
+ parts.add(@" <div class=\"section\">\n");
|
|
|
+ parts.add(@" <h2>Error</h2>\n");
|
|
|
+ parts.add(@" <pre>$(body_text.escape(""))</pre>\n");
|
|
|
+ parts.add(@" </div>\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ parts.add(""" <div class="section">
|
|
|
+ <h2>Query Parameters</h2>
|
|
|
+""");
|
|
|
+
|
|
|
+ var query_count = context.request.query_params.to_immutable_buffer().count();
|
|
|
+ if (query_count == 0) {
|
|
|
+ parts.add(@" <p class=\"empty\">No query parameters.</p>\n");
|
|
|
+ } else {
|
|
|
+ parts.add(@" <table>\n");
|
|
|
+ parts.add(@" <tr><th>Parameter</th><th>Value</th></tr>\n");
|
|
|
+
|
|
|
+ context.request.query_params.to_immutable_buffer()
|
|
|
+ .iterate((grouping) => {
|
|
|
+ grouping.iterate((value) => {
|
|
|
+ parts.add(@" <tr><td>$(grouping.key)</td><td>$(value.escape(""))</td></tr>\n");
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ parts.add(@" </table>\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ parts.add(""" </div>
|
|
|
+
|
|
|
+ <p><a href="/">Back to Forms</a></p>
|
|
|
+</body>
|
|
|
+</html>""");
|
|
|
+
|
|
|
+ var result = parts.to_immutable_buffer()
|
|
|
+ .aggregate<string>("", (acc, s) => acc + s);
|
|
|
+
|
|
|
+ var headers = new Catalogue<string, string>();
|
|
|
+ headers.add("Content-Type", "text/html");
|
|
|
+ return new BufferedHttpResult.from_string(
|
|
|
+ result,
|
|
|
+ StatusCode.OK,
|
|
|
+ headers
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Helper class for product data
|
|
|
+class Product {
|
|
|
+ public int id { get; private set; }
|
|
|
+ public string name { get; private set; }
|
|
|
+ public string category { get; private set; }
|
|
|
+ public double price { get; private set; }
|
|
|
+
|
|
|
+ public Product(int id, string name, string category, double price) {
|
|
|
+ this.id = id;
|
|
|
+ this.name = name;
|
|
|
+ this.category = category;
|
|
|
+ this.price = price;
|
|
|
+ }
|
|
|
+
|
|
|
+ public string to_json() {
|
|
|
+ return @"{ \"id\": $id, \"name\": \"$name\", \"category\": \"$category\", \"price\": $price }";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void main() {
|
|
|
+ var router = new Router();
|
|
|
+ var server = new Server(8084, router);
|
|
|
+
|
|
|
+ // Register handlers
|
|
|
+ router.map("/", new FormPageHandler());
|
|
|
+ router.map("/submit-simple", new SimpleFormHandler());
|
|
|
+ router.map("/submit-register", new RegisterFormHandler());
|
|
|
+ router.map("/submit-search", new SearchFormHandler());
|
|
|
+ router.map("/submit-file", new FileUploadHandler());
|
|
|
+ router.map("/form-debug", new FormDebugHandler());
|
|
|
+
|
|
|
+ print("Form Data Example Server running on port 8084\n");
|
|
|
+ print("Open http://localhost:8084/ in your browser to try the forms\n");
|
|
|
+
|
|
|
+ server.run();
|
|
|
+}
|