PageComponentsOverviewPage.vala 12 KB

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