PageComponentsOverviewPage.vala 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. using Spry;
  2. using Inversion;
  3. /**
  4. * PageComponentsOverviewPage - Introduction to PageComponent
  5. *
  6. * This page explains what PageComponents are, how they differ from regular
  7. * Components, and when to use them.
  8. */
  9. public class PageComponentsOverviewPage : PageComponent {
  10. public const string ROUTE = "/page-components/overview";
  11. private ComponentFactory factory = inject<ComponentFactory>();
  12. public override string markup { get {
  13. return """
  14. <div class="doc-content">
  15. <div class="doc-hero">
  16. <h1>Page Components</h1>
  17. <p class="doc-subtitle">Combine Component and Endpoint into a single powerful abstraction</p>
  18. </div>
  19. <section class="doc-section">
  20. <h2>What are Page Components?</h2>
  21. <p>
  22. A <code>PageComponent</code> is a special type of component that acts as both a
  23. <strong>Component</strong> AND an <strong>Endpoint</strong>. This means it can render
  24. HTML templates <em>and</em> handle HTTP requests at a specific route.
  25. </p>
  26. <p>
  27. In traditional web frameworks, you often need separate classes for routes/endpoints
  28. and for rendering views. PageComponent eliminates this duplication by combining
  29. both responsibilities into a single cohesive class.
  30. </p>
  31. </section>
  32. <section class="doc-section">
  33. <h2>PageComponent vs Component</h2>
  34. <p>
  35. Understanding when to use <code>PageComponent</code> vs <code>Component</code> is essential:
  36. </p>
  37. <table class="doc-table">
  38. <thead>
  39. <tr>
  40. <th>Feature</th>
  41. <th>Component</th>
  42. <th>PageComponent</th>
  43. </tr>
  44. </thead>
  45. <tbody>
  46. <tr>
  47. <td>Has markup</td>
  48. <td>✓ Yes</td>
  49. <td>✓ Yes</td>
  50. </tr>
  51. <tr>
  52. <td>Handles HTTP routes</td>
  53. <td>✗ No</td>
  54. <td>✓ Yes</td>
  55. </tr>
  56. <tr>
  57. <td>Can be nested in outlets</td>
  58. <td>✓ Yes</td>
  59. <td>✗ No (top-level only)</td>
  60. </tr>
  61. <tr>
  62. <td>Implements Endpoint</td>
  63. <td>✗ No</td>
  64. <td>✓ Yes</td>
  65. </tr>
  66. <tr>
  67. <td>Wrapped by templates</td>
  68. <td>✗ No</td>
  69. <td>✓ Yes</td>
  70. </tr>
  71. </tbody>
  72. </table>
  73. </section>
  74. <section class="doc-section">
  75. <h2>When to Use Each</h2>
  76. <div class="feature-grid">
  77. <div class="feature-card">
  78. <h3>Use PageComponent when:</h3>
  79. <ul>
  80. <li>Creating top-level pages (routes)</li>
  81. <li>Building documentation pages</li>
  82. <li>Rendering full HTML documents</li>
  83. <li>The component needs its own URL</li>
  84. </ul>
  85. </div>
  86. <div class="feature-card">
  87. <h3>Use Component when:</h3>
  88. <ul>
  89. <li>Building reusable UI elements</li>
  90. <li>Creating widgets or cards</li>
  91. <li>Nesting inside other components</li>
  92. <li>Rendering partial content</li>
  93. </ul>
  94. </div>
  95. </div>
  96. </section>
  97. <section class="doc-section">
  98. <h2>Basic PageComponent Example</h2>
  99. <p>
  100. Here's how to create a simple PageComponent:
  101. </p>
  102. <spry-component name="CodeBlockComponent" sid="basic-example"/>
  103. </section>
  104. <section class="doc-section">
  105. <h2>Route Registration</h2>
  106. <p>
  107. PageComponents are registered as endpoints in your application's Main.vala.
  108. You register both the component type and its route:
  109. </p>
  110. <spry-component name="CodeBlockComponent" sid="route-registration"/>
  111. </section>
  112. <section class="doc-section">
  113. <h2>Dependency Injection in Pages</h2>
  114. <p>
  115. PageComponents support the same dependency injection patterns as regular components.
  116. Use <code>inject<T>()</code> to access services, stores, and factories:
  117. </p>
  118. <spry-component name="CodeBlockComponent" sid="di-example"/>
  119. </section>
  120. <section class="doc-section">
  121. <h2>Template Wrapping</h2>
  122. <p>
  123. One of the most powerful features of PageComponents is automatic template wrapping.
  124. When a PageComponent renders, it is automatically wrapped by matching
  125. <code>PageTemplate</code> instances.
  126. </p>
  127. <p>
  128. This means your PageComponent markup only needs to contain the <em>content</em>
  129. of the page, not the full HTML document structure. The template provides the
  130. <code><html></code>, <code><head></code>, <code><body></code>,
  131. navigation, and footer.
  132. </p>
  133. <div class="info-box">
  134. <p>
  135. <strong>💡 Tip:</strong> Learn more about templates in the
  136. <a href="/page-components/templates">Page Templates</a> documentation.
  137. </p>
  138. </div>
  139. </section>
  140. <section class="doc-section">
  141. <h2>How handle_request() Works</h2>
  142. <p>
  143. The <code>PageComponent</code> base class implements the <code>Endpoint</code>
  144. interface's <code>handle_request()</code> method. This method:
  145. </p>
  146. <ol class="doc-list">
  147. <li>Collects all matching <code>PageTemplate</code> instances sorted by prefix depth</li>
  148. <li>Renders each template in order</li>
  149. <li>Finds <code>&lt;spry-template-outlet/&gt;</code> elements in each template</li>
  150. <li>Nests the content into the outlet, building the complete document</li>
  151. <li>Returns the final HTML as an <code>HttpResult</code></li>
  152. </ol>
  153. <p>
  154. You typically don't need to override <code>handle_request()</code> - the default
  155. implementation handles everything for you.
  156. </p>
  157. </section>
  158. <section class="doc-section">
  159. <h2>Next Steps</h2>
  160. <div class="nav-cards">
  161. <a href="/page-components/templates" class="nav-card">
  162. <h3>Page Templates →</h3>
  163. <p>Learn how to create layout templates that wrap your pages</p>
  164. </a>
  165. <a href="/components/overview" class="nav-card">
  166. <h3>Components Overview →</h3>
  167. <p>Review the basics of Spry components</p>
  168. </a>
  169. </div>
  170. </section>
  171. </div>
  172. """;
  173. }}
  174. public override async void prepare() throws Error {
  175. // Basic PageComponent example
  176. var basic_example = get_component_child<CodeBlockComponent>("basic-example");
  177. basic_example.language = "vala";
  178. basic_example.code = "using Spry;\n" +
  179. "using Inversion;\n\n" +
  180. "public class DocumentationPage : PageComponent {\n" +
  181. " \n" +
  182. " // Dependency injection works the same as Component\n" +
  183. " private ComponentFactory factory = inject<ComponentFactory>();\n" +
  184. " \n" +
  185. " public override string markup { get {\n" +
  186. " return \"\"\"\n" +
  187. " <div class=\"page-content\">\n" +
  188. " <h1>My Documentation Page</h1>\n" +
  189. " <p>This page handles its own route.</p>\n" +
  190. " </div>\n" +
  191. " \"\"\";\n" +
  192. " }}\n" +
  193. " \n" +
  194. " public override async void prepare() throws Error {\n" +
  195. " // Set up page content\n" +
  196. " }\n" +
  197. "}";
  198. // Route registration example
  199. var route_reg = get_component_child<CodeBlockComponent>("route-registration");
  200. route_reg.language = "vala";
  201. route_reg.code = "// In Main.vala:\n\n" +
  202. "// Register the page component with the dependency container\n" +
  203. "application.add_transient<DocumentationPage>();\n\n" +
  204. "// Register the page as an endpoint at a specific route\n" +
  205. "application.add_endpoint<DocumentationPage>(\n" +
  206. " new EndpointRoute(\"/docs/page\")\n" +
  207. ");\n\n" +
  208. "// Now visiting /docs/page will render DocumentationPage\n" +
  209. "// automatically wrapped by any matching PageTemplate";
  210. // Dependency injection example
  211. var di_example = get_component_child<CodeBlockComponent>("di-example");
  212. di_example.language = "vala";
  213. di_example.code = "public class UserProfilePage : PageComponent {\n" +
  214. " \n" +
  215. " // Inject services you need\n" +
  216. " private ComponentFactory factory = inject<ComponentFactory>();\n" +
  217. " private UserStore user_store = inject<UserStore>();\n" +
  218. " private HttpContext http_context = inject<HttpContext>();\n" +
  219. " \n" +
  220. " public override string markup { get {\n" +
  221. " return \"\"\"\n" +
  222. " <div class=\"profile-page\">\n" +
  223. " <h2 sid=\"username\"></h2>\n" +
  224. " <p sid=\"email\"></p>\n" +
  225. " </div>\n" +
  226. " \"\"\";\n" +
  227. " }}\n" +
  228. " \n" +
  229. " public override async void prepare() throws Error {\n" +
  230. " // Access route parameters from http_context\n" +
  231. " var user_id = http_context.request.query_params\n" +
  232. " .get_any_or_default(\"id\", \"guest\");\n" +
  233. " \n" +
  234. " // Use injected store to fetch data\n" +
  235. " var user = yield user_store.get_user(user_id);\n" +
  236. " \n" +
  237. " // Populate template\n" +
  238. " this[\"username\"].text_content = user.name;\n" +
  239. " this[\"email\"].text_content = user.email;\n" +
  240. " }\n" +
  241. "}";
  242. }
  243. }