using Astralis;
using Invercargill;
using Invercargill.DataStructures;
/**
* DocumentBuilderTemplate Example
*
* Demonstrates loading an existing HTML template and modifying elements
* using the DocumentModel classes. Shows how to:
* - Load HTML from a string template
* - Use XPath selectors to find specific elements
* - Modify element content, attributes, and classes
* - Add new elements dynamically
* - Handle form POST to update the document state
*
* This example complements DocumentBuilder.vala by showing template-based
* document manipulation rather than building from scratch.
*
* Usage: document-builder-template [port]
*/
// Application state - a simple counter
class AppState : Object {
public int counter { get; set; }
public int total_changes { get; set; }
public string last_action { get; set; }
public DateTime last_update { get; set; }
public AppState() {
counter = 0;
total_changes = 0;
last_action = "Initialized";
last_update = new DateTime.now_local();
}
public void increment() {
counter++;
total_changes++;
last_action = "Incremented";
last_update = new DateTime.now_local();
}
public void decrement() {
counter--;
total_changes++;
last_action = "Decremented";
last_update = new DateTime.now_local();
}
public void reset() {
counter = 0;
total_changes++;
last_action = "Reset";
last_update = new DateTime.now_local();
}
}
// Global app state
AppState app_state;
// HTML template loaded as a constant
private const string HTML_TEMPLATE = """
📄 Template Document Builder
This page demonstrates loading an HTML template and modifying it dynamically.
How It Works
The server loads the HTML template and uses XPath selectors to find and modify elements:
// Load the template
var doc = new MarkupDocument.from_string(HTML_TEMPLATE);
// Find and modify elements using XPath
var counter_el = doc.select_one("//div[@id='counter-value']");
counter_el.text_content = counter.to_string();
// Add/remove classes based on state
if (counter > 0) {
counter_el.add_class("status-positive");
}
DocumentModel Features Used:
MarkupDocument.from_string() - Load HTML from template
doc.select_one(xpath) - Find single element by XPath
doc.select(xpath) - Find multiple elements
element.text_content - Get/set text content
element.add_class()/remove_class() - Modify CSS classes
element.set_attribute() - Set element attributes
doc.to_result() - Return as HttpResult
""";
// Main page endpoint - loads template and modifies it
class HomePageEndpoint : Object, Endpoint {
public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
// Load the HTML template
var doc = new MarkupDocument.from_string(HTML_TEMPLATE);
// Update the counter value
var counter_el = doc.select_one("//div[@id='counter-value']");
if (counter_el != null) {
counter_el.text_content = app_state.counter.to_string();
// Update status class based on counter value
counter_el.remove_class("status-positive");
counter_el.remove_class("status-negative");
counter_el.remove_class("status-zero");
if (app_state.counter > 0) {
counter_el.add_class("status-positive");
} else if (app_state.counter < 0) {
counter_el.add_class("status-negative");
} else {
counter_el.add_class("status-zero");
}
}
// Update status text
var status_el = doc.select_one("//div[@id='status-value']");
if (status_el != null) {
if (app_state.counter > 0) {
status_el.text_content = "Positive";
status_el.remove_class("status-negative");
status_el.remove_class("status-zero");
status_el.add_class("status-positive");
} else if (app_state.counter < 0) {
status_el.text_content = "Negative";
status_el.remove_class("status-positive");
status_el.remove_class("status-zero");
status_el.add_class("status-negative");
} else {
status_el.text_content = "Zero";
status_el.remove_class("status-positive");
status_el.remove_class("status-negative");
status_el.add_class("status-zero");
}
}
// Update last action
var action_el = doc.select_one("//div[@id='action-value']");
if (action_el != null) {
action_el.text_content = app_state.last_action;
}
// Update timestamp
var time_el = doc.select_one("//div[@id='time-value']");
if (time_el != null) {
time_el.text_content = app_state.last_update.format("%H:%M:%S");
}
// Update total changes count
var changes_el = doc.select_one("//div[@id='changes-value']");
if (changes_el != null) {
changes_el.text_content = app_state.total_changes.to_string();
}
return doc.to_result();
}
}
// Increment endpoint
class IncrementEndpoint : Object, Endpoint {
public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
app_state.increment();
return new HttpStringResult("", (StatusCode)302)
.set_header("Location", "/");
}
}
// Decrement endpoint
class DecrementEndpoint : Object, Endpoint {
public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
app_state.decrement();
return new HttpStringResult("", (StatusCode)302)
.set_header("Location", "/");
}
}
// Reset endpoint
class ResetEndpoint : Object, Endpoint {
public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
app_state.reset();
return new HttpStringResult("", (StatusCode)302)
.set_header("Location", "/");
}
}
// Raw HTML endpoint - shows the unmodified template
class RawHtmlEndpoint : Object, Endpoint {
public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
// Load template without modifications
var doc = new MarkupDocument.from_string(HTML_TEMPLATE);
// Add a notice at the top
var body = doc.body;
if (body != null) {
var notice = body.append_child_element("div");
notice.set_attribute("style", "background: #fff3cd; color: #856404; padding: 15px; margin: 10px 0; border-radius: 8px; border: 1px solid #ffc107;");
notice.append_text("⚠️ This is the raw template without dynamic modifications. ");
var link = notice.append_child_element("a");
link.set_attribute("href", "/");
link.append_text("← Back to dynamic version");
}
return doc.to_result();
}
}
// API endpoint that demonstrates modifying multiple elements at once
class BulkUpdateEndpoint : Object, Endpoint {
public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
var doc = new MarkupDocument.from_string(HTML_TEMPLATE);
// Demonstrate selecting multiple elements
var all_info_values = doc.select("//div[contains(@class, 'info-value')]");
// Update all info values to show they were bulk-modified
all_info_values.set_text_all("BULK UPDATED");
all_info_values.add_class_all("status-positive");
// Also demonstrate setting attributes on multiple elements
var all_cards = doc.select("//div[contains(@class, 'card')]");
all_cards.set_attribute_all("data-bulk-update", "true");
// Update title to indicate bulk operation
var title = doc.select_one("//h1[@id='page-title']");
if (title != null) {
title.text_content = "📄 Bulk Update Demo";
}
// Add explanation
var desc = doc.select_one("//p[@id='description']");
if (desc != null) {
desc.text_content = "This demonstrates MarkupNodeList operations: set_text_all(), add_class_all(), and set_attribute_all().";
}
return doc.to_result();
}
}
void main(string[] args) {
int port = args.length > 1 ? int.parse(args[1]) : 8080;
// Initialize app state
app_state = new AppState();
print("╔══════════════════════════════════════════════════════════════╗\n");
print("║ Astralis DocumentBuilderTemplate Example ║\n");
print("╠══════════════════════════════════════════════════════════════╣\n");
print(@"║ Port: $port");
for (int i = 0; i < 50 - port.to_string().length - 7; i++) print(" ");
print(" ║\n");
print("╠══════════════════════════════════════════════════════════════╣\n");
print("║ Endpoints: ║\n");
print("║ / - Counter page (template with modifications) ║\n");
print("║ /increment - Increase counter (POST) ║\n");
print("║ /decrement - Decrease counter (POST) ║\n");
print("║ /reset - Reset counter (POST) ║\n");
print("║ /raw - Raw template (no modifications) ║\n");
print("║ /bulk - Bulk update demo ║\n");
print("╚══════════════════════════════════════════════════════════════╝\n");
print("\nPress Ctrl+C to stop the server\n\n");
try {
var application = new WebApplication(port);
// Register compression components
application.container.register_singleton