Jelajahi Sumber

refactor(spry): introduce SpryConfigurator for template registration

Add SpryConfigurator class to provide a cleaner API for registering
templates with route prefixes. Update TemplateExample to demonstrate
the new configuration pattern with simplified add_template method.

Includes visual refresh with flatter CSS design, improved flexbox
layout for sticky footer behavior, and updated HTMX target handling
to use outerHTML swap for proper card replacement on user additions.
Billy Barrow 1 Minggu lalu
induk
melakukan
0616127d8b
2 mengubah file dengan 56 tambahan dan 48 penghapusan
  1. 44 47
      examples/TemplateExample.vala
  2. 12 1
      src/Spry.vala

+ 44 - 47
examples/TemplateExample.vala

@@ -27,25 +27,29 @@ using Spry;
  * Main CSS - Base styles for all pages
  */
 private const string MAIN_CSS = """
-/* Base Reset & Typography */
+/* Base Reset & Layout */
 * { box-sizing: border-box; margin: 0; padding: 0; }
+html, body {
+    height: 100%;
+}
 body {
     font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
     line-height: 1.6;
-    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    background: #f5f7fa;
+    display: flex;
+    flex-direction: column;
     min-height: 100vh;
 }
 
 /* Header */
 header {
-    background: rgba(44, 62, 80, 0.95);
+    background: #2c3e50;
     color: white;
     padding: 1rem 2rem;
     display: flex;
     justify-content: space-between;
     align-items: center;
-    backdrop-filter: blur(10px);
-    box-shadow: 0 2px 20px rgba(0,0,0,0.2);
+    flex-shrink: 0;
 }
 header nav a {
     color: white;
@@ -61,11 +65,13 @@ header .visit-info {
     opacity: 0.8;
 }
 
-/* Main Content */
+/* Main Content - grows to fill space */
 main.container {
+    flex: 1;
     max-width: 1200px;
-    margin: 2rem auto;
-    padding: 0 1rem;
+    width: 100%;
+    margin: 0 auto;
+    padding: 2rem 1rem;
 }
 
 /* Cards */
@@ -74,7 +80,7 @@ main.container {
     border-radius: 12px;
     padding: 2rem;
     margin-bottom: 1.5rem;
-    box-shadow: 0 10px 40px rgba(0,0,0,0.15);
+    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
 }
 
 /* Typography */
@@ -86,8 +92,8 @@ ul, ol { margin-left: 1.5rem; margin-bottom: 1rem; }
 li { margin-bottom: 0.5rem; color: #555; }
 
 /* Links */
-a { color: #667eea; text-decoration: none; transition: color 0.2s; }
-a:hover { color: #764ba2; text-decoration: underline; }
+a { color: #3498db; text-decoration: none; transition: color 0.2s; }
+a:hover { color: #2980b9; text-decoration: underline; }
 
 /* Code */
 code {
@@ -107,19 +113,18 @@ pre {
     margin: 1rem 0;
 }
 
-/* Footer */
+/* Footer - at bottom */
 footer {
-    background: rgba(44, 62, 80, 0.9);
+    background: #2c3e50;
     color: rgba(255,255,255,0.7);
     padding: 1.5rem;
     text-align: center;
-    margin-top: 2rem;
-    backdrop-filter: blur(10px);
+    flex-shrink: 0;
 }
 
 /* Buttons & Forms */
 button {
-    background: #667eea;
+    background: #3498db;
     color: white;
     border: none;
     padding: 0.75rem 1.5rem;
@@ -130,9 +135,7 @@ button {
     transition: all 0.2s;
 }
 button:hover {
-    background: #5a6fd6;
-    transform: translateY(-1px);
-    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+    background: #2980b9;
 }
 input[type="text"] {
     padding: 0.75rem;
@@ -143,7 +146,7 @@ input[type="text"] {
 }
 input[type="text"]:focus {
     outline: none;
-    border-color: #667eea;
+    border-color: #3498db;
 }
 """;
 
@@ -155,17 +158,15 @@ private const string ADMIN_CSS = """
 .admin-section {
     display: flex;
     gap: 2rem;
-    margin-top: 1rem;
 }
 
 /* Admin Sidebar */
 .admin-sidebar {
     width: 220px;
-    background: rgba(52, 73, 94, 0.95);
+    background: #34495e;
     padding: 1.5rem;
     border-radius: 12px;
-    box-shadow: 0 4px 20px rgba(0,0,0,0.15);
-    backdrop-filter: blur(10px);
+    flex-shrink: 0;
 }
 .admin-sidebar h3 {
     color: white;
@@ -179,7 +180,7 @@ private const string ADMIN_CSS = """
     margin: 0;
     padding: 0;
 }
-.admin-sidebar li { margin-bottom: 0.5rem; }
+.admin-sidebar li { margin-bottom: 0.5rem; margin-left: 0; }
 .admin-sidebar a {
     color: rgba(255,255,255,0.7);
     display: block;
@@ -206,12 +207,11 @@ private const string ADMIN_CSS = """
     flex-wrap: wrap;
 }
 .stat-card {
-    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    background: #3498db;
     color: white;
     padding: 1.5rem;
     border-radius: 12px;
     min-width: 180px;
-    box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
 }
 .stat-card h3 {
     color: rgba(255,255,255,0.9);
@@ -234,15 +234,13 @@ private const string ADMIN_CSS = """
     align-items: center;
     gap: 1rem;
     padding: 1rem 1.25rem;
-    background: white;
+    background: #f8f9fa;
     margin-bottom: 0.75rem;
     border-radius: 8px;
-    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
-    transition: transform 0.2s, box-shadow 0.2s;
+    transition: background 0.2s;
 }
 .user-item:hover {
-    transform: translateX(4px);
-    box-shadow: 0 4px 12px rgba(0,0,0,0.12);
+    background: #e9ecef;
 }
 .user-item .user-name {
     flex: 1;
@@ -270,10 +268,10 @@ private const string ADMIN_CSS = """
 .nav-link {
     display: inline-block;
     margin-top: 1.5rem;
-    color: #667eea;
+    color: #3498db;
     font-weight: 500;
 }
-.nav-link:hover { color: #764ba2; }
+.nav-link:hover { color: #2980b9; }
 """;
 
 // =============================================================================
@@ -424,7 +422,7 @@ class HomePage : PageComponent {
             <h2>Features</h2>
             <ul>
                 <li><strong>MainLayoutTemplate</strong> - Provides base HTML, header, footer</li>
-                <li><strong>AdminSectionTemplate</strong> - Adds sidebar for /admin/* routes</li>
+                <li><modulestrong>AdminSectionTemplate</strong> - Adds sidebar for /admin/* routes</li>
                 <li><strong>PageComponent</strong> - Pages automatically inherit templates</li>
             </ul>
             
@@ -517,7 +515,9 @@ class AdminDashboardPage : PageComponent {
 /**
  * AdminUsersPage - Admin page with interactive user list
  * 
- * Demonstrates HTMX interactions within a templated page
+ * Demonstrates HTMX interactions within a templated page.
+ * The form targets the card with outerHTML swap so the entire card
+ * is replaced with the updated content.
  */
 class AdminUsersPage : PageComponent {
     
@@ -529,14 +529,14 @@ class AdminUsersPage : PageComponent {
     
     public override string markup { get {
         return """
-        <div class="card">
+        <div class="card" sid="users-card">
             <h1>User Management</h1>
             
-            <div class="user-list" sid="user-list">
-                <!-- Users rendered here -->
+            <div class="user-list">
+                <spry-outlet sid="user-list"/>
             </div>
             
-            <form class="add-form" sid="add-form" spry-action=":AddUser" spry-target="user-list" hx-swap="innerHTML">
+            <form class="add-form" sid="add-form" spry-action=":AddUser" spry-target="users-card" hx-swap="outerHTML">
                 <input type="text" name="username" placeholder="Enter username" required>
                 <button type="submit">Add User</button>
             </form>
@@ -632,14 +632,11 @@ void main(string[] args) {
         
         // Register templates with route prefixes
         // MainLayoutTemplate applies to ALL routes (empty prefix)
-        application.add_transient<MainLayoutTemplate>()
-            .as<PageTemplate>()
-            .with_metadata(new TemplateRoutePrefix(""));
-        
+        var spry_cfg = application.configure_with<SpryConfigurator>();
+        spry_cfg.add_template<MainLayoutTemplate>("");
+
         // AdminSectionTemplate applies to /admin/* routes
-        application.add_transient<AdminSectionTemplate>()
-            .as<PageTemplate>()
-            .with_metadata(new TemplateRoutePrefix("/admin"));
+        spry_cfg.add_template<AdminSectionTemplate>("/admin");
         
         // Register page components as endpoints
         application.add_transient<HomePage>();

+ 12 - 1
src/Spry.vala

@@ -4,7 +4,6 @@ using Astralis;
 namespace Spry {
 
     public class SpryModule : Object, Module {
-
         public void register_components (Container container) throws Error {
             container.register_singleton<ComponentUriProvider>();
             container.register_scoped<ComponentFactory>();
@@ -15,4 +14,16 @@ namespace Spry {
 
     }
 
+    public class SpryConfigurator : Object {
+
+        private Container container = inject<Container>();
+
+        public void add_template<T>(string prefix) {
+            container.register_transient<T>()
+                .as<PageTemplate> ()
+                .with_metadata<TemplateRoutePrefix>(new TemplateRoutePrefix(prefix));
+        }
+
+    }
+
 }