ComponentsOutletsPage.vala 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. using Spry;
  2. using Inversion;
  3. /**
  4. * ComponentsOutletsPage - Documentation for Spry outlets
  5. *
  6. * This page covers what outlets are, how to use them for component
  7. * composition, and the parent/child component pattern.
  8. */
  9. public class ComponentsOutletsPage : PageComponent {
  10. public const string ROUTE = "/components/outlets";
  11. private ComponentFactory factory = inject<ComponentFactory>();
  12. public override string markup { get {
  13. return """
  14. <div sid="page" class="doc-content">
  15. <h1>Outlets</h1>
  16. <section class="doc-section">
  17. <p>
  18. Outlets are placeholders in your component templates where child components
  19. can be dynamically inserted. They enable powerful component composition
  20. patterns, especially for lists and dynamic content.
  21. </p>
  22. </section>
  23. <section class="doc-section">
  24. <h2>What are Outlets?</h2>
  25. <p>
  26. An outlet is a special element in your markup that acts as a insertion point
  27. for child components. Think of it like a socket where you can plug in
  28. different components at runtime.
  29. </p>
  30. <spry-component name="CodeBlockComponent" sid="outlet-syntax"/>
  31. </section>
  32. <section class="doc-section">
  33. <h2>Using <code>set_outlet_children()</code></h2>
  34. <p>
  35. The <code>set_outlet_children(sid, children)</code> method populates an outlet
  36. with child components. Call it in your <code>prepare()</code> method after
  37. creating child components with the ComponentFactory.
  38. </p>
  39. <spry-component name="CodeBlockComponent" sid="set-outlet-vala"/>
  40. </section>
  41. <section class="doc-section">
  42. <h2>Parent/Child Component Pattern</h2>
  43. <p>
  44. The typical pattern for outlets involves:
  45. </p>
  46. <ol>
  47. <li>A <strong>parent component</strong> with an outlet and a method to set children</li>
  48. <li>A <strong>child component</strong> that displays individual items</li>
  49. <li>A <strong>store</strong> that holds the data</li>
  50. </ol>
  51. <h3>Parent Component</h3>
  52. <spry-component name="CodeBlockComponent" sid="parent-component"/>
  53. <h3>Child Component</h3>
  54. <spry-component name="CodeBlockComponent" sid="child-component"/>
  55. </section>
  56. <section class="doc-section">
  57. <h2>Creating Lists with Outlets</h2>
  58. <p>
  59. Outlets are perfect for rendering dynamic lists. Here's the complete pattern:
  60. </p>
  61. <spry-component name="CodeBlockComponent" sid="list-pattern"/>
  62. </section>
  63. <section class="doc-section">
  64. <h2>Outlets vs <code>&lt;spry-component&gt;</code></h2>
  65. <p>
  66. When should you use outlets vs declarative child components?
  67. </p>
  68. <table class="doc-table">
  69. <thead>
  70. <tr>
  71. <th>Feature</th>
  72. <th><code>&lt;spry-outlet&gt;</code></th>
  73. <th><code>&lt;spry-component&gt;</code></th>
  74. </tr>
  75. </thead>
  76. <tbody>
  77. <tr>
  78. <td>Use Case</td>
  79. <td>Dynamic lists, multiple items</td>
  80. <td>Single, known child components</td>
  81. </tr>
  82. <tr>
  83. <td>Population</td>
  84. <td><code>set_outlet_children()</code> in prepare()</td>
  85. <td>Automatic, configured via properties</td>
  86. </tr>
  87. <tr>
  88. <td>Access</td>
  89. <td>Not directly accessible</td>
  90. <td><code>get_component_child<T>()</code></td>
  91. </tr>
  92. <tr>
  93. <td>Examples</td>
  94. <td>Todo lists, data tables, feeds</td>
  95. <td>Headers, sidebars, fixed sections</td>
  96. </tr>
  97. </tbody>
  98. </table>
  99. </section>
  100. <section class="doc-section">
  101. <h2>Live Demo: Todo List</h2>
  102. <p>
  103. This interactive demo shows outlets in action. The todo list uses an outlet
  104. to render individual todo items. Try adding, toggling, and deleting items!
  105. </p>
  106. <spry-component name="DemoHostComponent" sid="todo-demo"/>
  107. </section>
  108. <section class="doc-section">
  109. <h2>Next Steps</h2>
  110. <div class="nav-cards">
  111. <a href="/components/continuations" class="nav-card">
  112. <h3>Continuations →</h3>
  113. <p>Real-time updates with SSE</p>
  114. </a>
  115. <a href="/components/actions" class="nav-card">
  116. <h3>Actions ←</h3>
  117. <p>Handle user interactions</p>
  118. </a>
  119. <a href="/components/template-syntax" class="nav-card">
  120. <h3>Template Syntax ←</h3>
  121. <p>Review template attributes</p>
  122. </a>
  123. </div>
  124. </section>
  125. </div>
  126. """;
  127. }}
  128. public override async void prepare() throws Error {
  129. // Outlet syntax example
  130. var outlet_syntax = get_component_child<CodeBlockComponent>("outlet-syntax");
  131. outlet_syntax.language = "HTML";
  132. outlet_syntax.code = "<div sid=\"list\">\n" +
  133. " <!-- This outlet will be filled with child components -->\n" +
  134. " <spry-outlet sid=\"items\"/>\n" +
  135. "</div>";
  136. // set_outlet_children example
  137. var set_outlet_vala = get_component_child<CodeBlockComponent>("set-outlet-vala");
  138. set_outlet_vala.language = "Vala";
  139. set_outlet_vala.code = "public override async void prepare() throws Error {\n" +
  140. " var factory = inject<ComponentFactory>();\n" +
  141. " var store = inject<TodoStore>();\n\n" +
  142. " // Create child components for each item\n" +
  143. " var children = new Series<Renderable>();\n" +
  144. " foreach (var item in store.items) {\n" +
  145. " var component = factory.create<TodoItemComponent>();\n" +
  146. " component.item_id = item.id; // Pass data to child\n" +
  147. " children.add(component);\n" +
  148. " }\n\n" +
  149. " // Populate the outlet\n" +
  150. " set_outlet_children(\"items\", children);\n" +
  151. "}";
  152. // Parent component example
  153. var parent_component = get_component_child<CodeBlockComponent>("parent-component");
  154. parent_component.language = "Vala";
  155. parent_component.code = "public class TodoListComponent : Component {\n" +
  156. " private TodoStore store = inject<TodoStore>();\n" +
  157. " private ComponentFactory factory = inject<ComponentFactory>();\n\n" +
  158. " public override string markup { get {\n" +
  159. " return \"\"\"\n" +
  160. " <div sid=\"list\" class=\"todo-list\">\n" +
  161. " <ul sid=\"items\">\n" +
  162. " <spry-outlet sid=\"items-outlet\"/>\n" +
  163. " </ul>\n" +
  164. " </div>\n" +
  165. " \"\"\";\n" +
  166. " }}\n\n" +
  167. " public override async void prepare() throws Error {\n" +
  168. " var children = new Series<Renderable>();\n" +
  169. " foreach (var item in store.items) {\n" +
  170. " var component = factory.create<TodoItemComponent>();\n" +
  171. " component.item_id = item.id;\n" +
  172. " children.add(component);\n" +
  173. " }\n" +
  174. " set_outlet_children(\"items-outlet\", children);\n" +
  175. " }\n" +
  176. "}";
  177. // Child component example
  178. var child_component = get_component_child<CodeBlockComponent>("child-component");
  179. child_component.language = "Vala";
  180. child_component.code = "public class TodoItemComponent : Component {\n" +
  181. " private TodoStore store = inject<TodoStore>();\n" +
  182. " private HttpContext http_context = inject<HttpContext>();\n\n" +
  183. " public int item_id { get; set; } // Set by parent\n\n" +
  184. " public override string markup { get {\n" +
  185. " return \"\"\"\n" +
  186. " <li sid=\"item\" class=\"todo-item\">\n" +
  187. " <span sid=\"title\"></span>\n" +
  188. " <button spry-action=\":Toggle\" spry-target=\"item\">Toggle</button>\n" +
  189. " </li>\n" +
  190. " \"\"\";\n" +
  191. " }}\n\n" +
  192. " public override void prepare() throws Error {\n" +
  193. " var item = store.get_by_id(item_id);\n" +
  194. " this[\"title\"].text_content = item.title;\n" +
  195. " // Pass item_id via hx-vals for handle_action\n" +
  196. " this[\"item\"].set_attribute(\"hx-vals\", @\"{\\\"id\\\":$item_id}\");\n" +
  197. " }\n\n" +
  198. " public async override void handle_action(string action) throws Error {\n" +
  199. " var id = int.parse(http_context.request.query_params.get_any_or_default(\"id\"));\n" +
  200. " if (action == \"Toggle\") {\n" +
  201. " store.toggle(id);\n" +
  202. " }\n" +
  203. " }\n" +
  204. "}";
  205. // List pattern example
  206. var list_pattern = get_component_child<CodeBlockComponent>("list-pattern");
  207. list_pattern.language = "Vala";
  208. list_pattern.code = "// 1. Define a store to hold data\n" +
  209. "public class TodoStore : Object {\n" +
  210. " public Series<TodoItem> items { get; default = new Series<TodoItem>(); }\n\n" +
  211. " public void add(string title) { /* ... */ }\n" +
  212. " public void toggle(int id) { /* ... */ }\n" +
  213. " public void remove(int id) { /* ... */ }\n" +
  214. "}\n\n" +
  215. "// 2. Register as singleton\n" +
  216. "application.add_singleton<TodoStore>();\n\n" +
  217. "// 3. Parent component uses outlet\n" +
  218. "public class TodoListComponent : Component {\n" +
  219. " private TodoStore store = inject<TodoStore>();\n\n" +
  220. " public override string markup { get {\n" +
  221. " return \"<ul><spry-outlet sid=\"items\"/></ul>\";\n" +
  222. " }}\n\n" +
  223. " public override async void prepare() throws Error {\n" +
  224. " var children = new Series<Renderable>();\n" +
  225. " foreach (var item in store.items) {\n" +
  226. " var child = factory.create<TodoItemComponent>();\n" +
  227. " child.item_id = item.id;\n" +
  228. " children.add(child);\n" +
  229. " }\n" +
  230. " set_outlet_children(\"items\", children);\n" +
  231. " }\n" +
  232. "}\n\n" +
  233. "// 4. Child component handles individual items\n" +
  234. "public class TodoItemComponent : Component {\n" +
  235. " public int item_id { get; set; }\n" +
  236. " // ... markup and methods\n" +
  237. "}";
  238. // Set up the demo host
  239. var demo = get_component_child<DemoHostComponent>("todo-demo");
  240. demo.demo_component_name = "TodoListDemo";
  241. demo.source_file = "demo/DemoComponents/TodoListDemo.vala";
  242. }
  243. }