| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- using Spry;
- using Inversion;
- /**
- * ComponentsOutletsPage - Documentation for Spry outlets
- *
- * This page covers what outlets are, how to use them for component
- * composition, and the parent/child component pattern.
- */
- public class ComponentsOutletsPage : PageComponent {
-
- public const string ROUTE = "/components/outlets";
-
- private ComponentFactory factory = inject<ComponentFactory>();
-
- public override string markup { get {
- return """
- <div sid="page" class="doc-content">
- <h1>Outlets</h1>
-
- <section class="doc-section">
- <p>
- Outlets are placeholders in your component templates where child components
- can be dynamically inserted. They enable powerful component composition
- patterns, especially for lists and dynamic content.
- </p>
- </section>
-
- <section class="doc-section">
- <h2>What are Outlets?</h2>
- <p>
- An outlet is a special element in your markup that acts as a insertion point
- for child components. Think of it like a socket where you can plug in
- different components at runtime.
- </p>
-
- <spry-component name="CodeBlockComponent" sid="outlet-syntax"/>
- </section>
-
- <section class="doc-section">
- <h2>Using <code>set_outlet_children()</code></h2>
- <p>
- The <code>set_outlet_children(sid, children)</code> method populates an outlet
- with child components. Call it in your <code>prepare()</code> method after
- creating child components with the ComponentFactory.
- </p>
-
- <spry-component name="CodeBlockComponent" sid="set-outlet-vala"/>
- </section>
-
- <section class="doc-section">
- <h2>Parent/Child Component Pattern</h2>
- <p>
- The typical pattern for outlets involves:
- </p>
- <ol>
- <li>A <strong>parent component</strong> with an outlet and a method to set children</li>
- <li>A <strong>child component</strong> that displays individual items</li>
- <li>A <strong>store</strong> that holds the data</li>
- </ol>
-
- <h3>Parent Component</h3>
- <spry-component name="CodeBlockComponent" sid="parent-component"/>
-
- <h3>Child Component</h3>
- <spry-component name="CodeBlockComponent" sid="child-component"/>
- </section>
-
- <section class="doc-section">
- <h2>Creating Lists with Outlets</h2>
- <p>
- Outlets are perfect for rendering dynamic lists. Here's the complete pattern:
- </p>
-
- <spry-component name="CodeBlockComponent" sid="list-pattern"/>
- </section>
-
- <section class="doc-section">
- <h2>Outlets vs <code><spry-component></code></h2>
- <p>
- When should you use outlets vs declarative child components?
- </p>
-
- <table class="doc-table">
- <thead>
- <tr>
- <th>Feature</th>
- <th><code><spry-outlet></code></th>
- <th><code><spry-component></code></th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>Use Case</td>
- <td>Dynamic lists, multiple items</td>
- <td>Single, known child components</td>
- </tr>
- <tr>
- <td>Population</td>
- <td><code>set_outlet_children()</code> in prepare()</td>
- <td>Automatic, configured via properties</td>
- </tr>
- <tr>
- <td>Access</td>
- <td>Not directly accessible</td>
- <td><code>get_component_child<T>()</code></td>
- </tr>
- <tr>
- <td>Examples</td>
- <td>Todo lists, data tables, feeds</td>
- <td>Headers, sidebars, fixed sections</td>
- </tr>
- </tbody>
- </table>
- </section>
-
- <section class="doc-section">
- <h2>Live Demo: Todo List</h2>
- <p>
- This interactive demo shows outlets in action. The todo list uses an outlet
- to render individual todo items. Try adding, toggling, and deleting items!
- </p>
-
- <spry-component name="DemoHostComponent" sid="todo-demo"/>
- </section>
-
- <section class="doc-section">
- <h2>Next Steps</h2>
- <div class="nav-cards">
- <a href="/components/continuations" class="nav-card">
- <h3>Continuations →</h3>
- <p>Real-time updates with SSE</p>
- </a>
- <a href="/components/actions" class="nav-card">
- <h3>Actions ←</h3>
- <p>Handle user interactions</p>
- </a>
- <a href="/components/template-syntax" class="nav-card">
- <h3>Template Syntax ←</h3>
- <p>Review template attributes</p>
- </a>
- </div>
- </section>
- </div>
- """;
- }}
-
- public override async void prepare() throws Error {
- // Outlet syntax example
- var outlet_syntax = get_component_child<CodeBlockComponent>("outlet-syntax");
- outlet_syntax.language = "HTML";
- outlet_syntax.code = "<div sid=\"list\">\n" +
- " <!-- This outlet will be filled with child components -->\n" +
- " <spry-outlet sid=\"items\"/>\n" +
- "</div>";
-
- // set_outlet_children example
- var set_outlet_vala = get_component_child<CodeBlockComponent>("set-outlet-vala");
- set_outlet_vala.language = "Vala";
- set_outlet_vala.code = "public override async void prepare() throws Error {\n" +
- " var factory = inject<ComponentFactory>();\n" +
- " var store = inject<TodoStore>();\n\n" +
- " // Create child components for each item\n" +
- " var children = new Series<Renderable>();\n" +
- " foreach (var item in store.items) {\n" +
- " var component = factory.create<TodoItemComponent>();\n" +
- " component.item_id = item.id; // Pass data to child\n" +
- " children.add(component);\n" +
- " }\n\n" +
- " // Populate the outlet\n" +
- " set_outlet_children(\"items\", children);\n" +
- "}";
-
- // Parent component example
- var parent_component = get_component_child<CodeBlockComponent>("parent-component");
- parent_component.language = "Vala";
- parent_component.code = "public class TodoListComponent : Component {\n" +
- " private TodoStore store = inject<TodoStore>();\n" +
- " private ComponentFactory factory = inject<ComponentFactory>();\n\n" +
- " public override string markup { get {\n" +
- " return \"\"\"\n" +
- " <div sid=\"list\" class=\"todo-list\">\n" +
- " <ul sid=\"items\">\n" +
- " <spry-outlet sid=\"items-outlet\"/>\n" +
- " </ul>\n" +
- " </div>\n" +
- " \"\"\";\n" +
- " }}\n\n" +
- " public override async void prepare() throws Error {\n" +
- " var children = new Series<Renderable>();\n" +
- " foreach (var item in store.items) {\n" +
- " var component = factory.create<TodoItemComponent>();\n" +
- " component.item_id = item.id;\n" +
- " children.add(component);\n" +
- " }\n" +
- " set_outlet_children(\"items-outlet\", children);\n" +
- " }\n" +
- "}";
-
- // Child component example
- var child_component = get_component_child<CodeBlockComponent>("child-component");
- child_component.language = "Vala";
- child_component.code = "public class TodoItemComponent : Component {\n" +
- " private TodoStore store = inject<TodoStore>();\n" +
- " private HttpContext http_context = inject<HttpContext>();\n\n" +
- " public int item_id { get; set; } // Set by parent\n\n" +
- " public override string markup { get {\n" +
- " return \"\"\"\n" +
- " <li sid=\"item\" class=\"todo-item\">\n" +
- " <span sid=\"title\"></span>\n" +
- " <button spry-action=\":Toggle\" spry-target=\"item\">Toggle</button>\n" +
- " </li>\n" +
- " \"\"\";\n" +
- " }}\n\n" +
- " public override void prepare() throws Error {\n" +
- " var item = store.get_by_id(item_id);\n" +
- " this[\"title\"].text_content = item.title;\n" +
- " // Pass item_id via hx-vals for handle_action\n" +
- " this[\"item\"].set_attribute(\"hx-vals\", @\"{\\\"id\\\":$item_id}\");\n" +
- " }\n\n" +
- " public async override void handle_action(string action) throws Error {\n" +
- " var id = int.parse(http_context.request.query_params.get_any_or_default(\"id\"));\n" +
- " if (action == \"Toggle\") {\n" +
- " store.toggle(id);\n" +
- " }\n" +
- " }\n" +
- "}";
-
- // List pattern example
- var list_pattern = get_component_child<CodeBlockComponent>("list-pattern");
- list_pattern.language = "Vala";
- list_pattern.code = "// 1. Define a store to hold data\n" +
- "public class TodoStore : Object {\n" +
- " public Series<TodoItem> items { get; default = new Series<TodoItem>(); }\n\n" +
- " public void add(string title) { /* ... */ }\n" +
- " public void toggle(int id) { /* ... */ }\n" +
- " public void remove(int id) { /* ... */ }\n" +
- "}\n\n" +
- "// 2. Register as singleton\n" +
- "application.add_singleton<TodoStore>();\n\n" +
- "// 3. Parent component uses outlet\n" +
- "public class TodoListComponent : Component {\n" +
- " private TodoStore store = inject<TodoStore>();\n\n" +
- " public override string markup { get {\n" +
- " return \"<ul><spry-outlet sid=\"items\"/></ul>\";\n" +
- " }}\n\n" +
- " public override async void prepare() throws Error {\n" +
- " var children = new Series<Renderable>();\n" +
- " foreach (var item in store.items) {\n" +
- " var child = factory.create<TodoItemComponent>();\n" +
- " child.item_id = item.id;\n" +
- " children.add(child);\n" +
- " }\n" +
- " set_outlet_children(\"items\", children);\n" +
- " }\n" +
- "}\n\n" +
- "// 4. Child component handles individual items\n" +
- "public class TodoItemComponent : Component {\n" +
- " public int item_id { get; set; }\n" +
- " // ... markup and methods\n" +
- "}";
-
- // Set up the demo host
- var demo = get_component_child<DemoHostComponent>("todo-demo");
- demo.demo_component_name = "TodoListDemo";
- demo.source_file = "demo/DemoComponents/TodoListDemo.vala";
- }
- }
|