ComponentsOverviewPage.vala 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. using Spry;
  2. using Inversion;
  3. /**
  4. * ComponentsOverviewPage - Introduction to Spry Components
  5. *
  6. * This page provides a comprehensive overview of what Spry components are,
  7. * their lifecycle, key methods, and basic usage patterns.
  8. */
  9. public class ComponentsOverviewPage : PageComponent {
  10. public const string ROUTE = "/components/overview";
  11. private ComponentFactory factory = inject<ComponentFactory>();
  12. public override string markup { get {
  13. return """
  14. <div sid="page" class="doc-content">
  15. <h1>Components Overview</h1>
  16. <section class="doc-section">
  17. <h2>What are Spry Components?</h2>
  18. <p>
  19. Spry components are the building blocks of your web application. They combine
  20. HTML templates with behavior logic in a single, cohesive Vala class. Each component
  21. is self-contained with its own markup, state management, and action handling.
  22. </p>
  23. <p>
  24. Unlike traditional web frameworks where templates and logic are separated,
  25. Spry components embrace a component-based architecture where everything related
  26. to a UI element lives in one place.
  27. </p>
  28. </section>
  29. <section class="doc-section">
  30. <h2>Component Lifecycle</h2>
  31. <p>
  32. Understanding the component lifecycle is crucial for building effective Spry applications.
  33. The template DOM is <strong>fresh on every request</strong>, meaning state is not
  34. persisted between requests.
  35. </p>
  36. <div class="lifecycle-diagram">
  37. <div class="lifecycle-step">
  38. <div class="step-number">1</div>
  39. <div class="step-content">
  40. <h4>Component Creation</h4>
  41. <p>Component is instantiated via ComponentFactory</p>
  42. </div>
  43. </div>
  44. <div class="lifecycle-arrow">↓</div>
  45. <div class="lifecycle-step">
  46. <div class="step-number">2</div>
  47. <div class="step-content">
  48. <h4>prepare_once()</h4>
  49. <p>One-time initialization (called only before first prepare)</p>
  50. </div>
  51. </div>
  52. <div class="lifecycle-arrow">↓</div>
  53. <div class="lifecycle-step">
  54. <div class="step-number">3</div>
  55. <div class="step-content">
  56. <h4>prepare()</h4>
  57. <p>Fetch data from stores, populate template elements</p>
  58. </div>
  59. </div>
  60. <div class="lifecycle-arrow">↓</div>
  61. <div class="lifecycle-step">
  62. <div class="step-number">4</div>
  63. <div class="step-content">
  64. <h4>handle_action()</h4>
  65. <p>Handle user interactions (if action triggered)</p>
  66. </div>
  67. </div>
  68. <div class="lifecycle-arrow">↓</div>
  69. <div class="lifecycle-step">
  70. <div class="step-number">5</div>
  71. <div class="step-content">
  72. <h4>Serialization</h4>
  73. <p>Template is rendered to HTML and sent to client</p>
  74. </div>
  75. </div>
  76. </div>
  77. </section>
  78. <section class="doc-section">
  79. <h2>Key Methods</h2>
  80. <p>
  81. Every component inherits from the base <code>Component</code> class and can
  82. override these key methods:
  83. </p>
  84. <table class="doc-table">
  85. <thead>
  86. <tr>
  87. <th>Method</th>
  88. <th>Description</th>
  89. </tr>
  90. </thead>
  91. <tbody>
  92. <tr>
  93. <td><code>markup</code></td>
  94. <td>Property returning the HTML template string with Spry attributes</td>
  95. </tr>
  96. <tr>
  97. <td><code>prepare()</code></td>
  98. <td>Called before serialization. Fetch data and populate template here.</td>
  99. </tr>
  100. <tr>
  101. <td><code>prepare_once()</code></td>
  102. <td>Called only once before the first prepare(). For one-time initialization.</td>
  103. </tr>
  104. <tr>
  105. <td><code>handle_action()</code></td>
  106. <td>Called when HTMX triggers an action. Modify state here.</td>
  107. </tr>
  108. <tr>
  109. <td><code>continuation()</code></td>
  110. <td>For real-time updates via Server-Sent Events (SSE).</td>
  111. </tr>
  112. </tbody>
  113. </table>
  114. </section>
  115. <section class="doc-section">
  116. <h2>Basic Component Example</h2>
  117. <p>
  118. Here's a simple counter component demonstrating the core concepts:
  119. </p>
  120. <spry-component name="CodeBlockComponent" sid="example-code"/>
  121. </section>
  122. <section class="doc-section">
  123. <h2>Important: Template State is Not Persisted</h2>
  124. <div class="warning-box">
  125. <p>
  126. <strong>⚠️ Critical Concept:</strong> The template DOM is fresh on every request.
  127. Any state set via setters (like <code>title</code>, <code>completed</code>) is
  128. NOT available in <code>handle_action()</code>.
  129. </p>
  130. <p>
  131. Always use <code>prepare()</code> to fetch data from stores and set up the template.
  132. To pass data to actions, use one of these approaches:
  133. </p>
  134. </div>
  135. <div class="info-box">
  136. <h4>Option 1: spry-context (Recommended for Properties)</h4>
  137. <p>
  138. Use <code>&lt;spry-context property="name"/&gt;</code> to automatically preserve
  139. property values across requests.
  140. </p>
  141. <ul>
  142. <li>Properties are encrypted and included in action URLs automatically</li>
  143. <li>Values are restored to properties before <code>handle_action()</code> is called</li>
  144. <li>Example: <code>&lt;spry-context property="item_id"/&gt;</code></li>
  145. </ul>
  146. </div>
  147. <div class="info-box">
  148. <h4>Option 2: hx-vals (Manual Query Parameters)</h4>
  149. <p>
  150. Use the <code>hx-vals</code> attribute to pass data as query parameters.
  151. </p>
  152. <ul>
  153. <li>Values are included in the request as query parameters</li>
  154. <li>Must be read manually via <code>http_context.request.query_params.get_any_or_default("key")</code></li>
  155. <li>Example: <code>hx-vals='{"id": "123"}'</code></li>
  156. </ul>
  157. </div>
  158. </section>
  159. <section class="doc-section">
  160. <h2>Next Steps</h2>
  161. <div class="nav-cards">
  162. <a href="/components/template-syntax" class="nav-card">
  163. <h3>Template Syntax →</h3>
  164. <p>Learn about Spry's special HTML attributes</p>
  165. </a>
  166. <a href="/components/actions" class="nav-card">
  167. <h3>Actions →</h3>
  168. <p>Handle user interactions with actions</p>
  169. </a>
  170. <a href="/components/outlets" class="nav-card">
  171. <h3>Outlets →</h3>
  172. <p>Compose components with outlets</p>
  173. </a>
  174. </div>
  175. </section>
  176. </div>
  177. """;
  178. }}
  179. public override async void prepare() throws Error {
  180. var code = get_component_child<CodeBlockComponent>("example-code");
  181. code.language = "Vala";
  182. code.code = "using Spry;\n\n" +
  183. "public class CounterComponent : Component {\n" +
  184. " private CounterStore store = inject<CounterStore>();\n\n" +
  185. " public override string markup { get {\n" +
  186. " return \"\"\"\n" +
  187. " <div sid=\"counter\" class=\"counter\">\n" +
  188. " <span sid=\"count\"></span>\n" +
  189. " <button sid=\"inc\" spry-action=\":Increment\"\n" +
  190. " spry-target=\"counter\">+</button>\n" +
  191. " <button sid=\"dec\" spry-action=\":Decrement\"\n" +
  192. " spry-target=\"counter\">-</button>\n" +
  193. " </div>\n" +
  194. " \"\"\";\n" +
  195. " }}\n\n" +
  196. " public override void prepare() throws Error {\n" +
  197. " this[\"count\"].text_content = store.count.to_string();\n" +
  198. " }\n\n" +
  199. " public async override void handle_action(string action) throws Error {\n" +
  200. " switch (action) {\n" +
  201. " case \"Increment\":\n" +
  202. " store.count++;\n" +
  203. " break;\n" +
  204. " case \"Decrement\":\n" +
  205. " store.count--;\n" +
  206. " break;\n" +
  207. " }\n" +
  208. " // prepare() is called automatically after handle_action()\n" +
  209. " }\n" +
  210. "}";
  211. }
  212. }