| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- using Spry;
- using Inversion;
- /**
- * PageComponentsOverviewPage - Introduction to PageComponent
- *
- * This page explains what PageComponents are, how they differ from regular
- * Components, and when to use them.
- */
- public class PageComponentsOverviewPage : PageComponent {
-
- public const string ROUTE = "/page-components/overview";
-
- private ComponentFactory factory = inject<ComponentFactory>();
-
- public override string markup { get {
- return """
- <div class="doc-content">
- <div class="doc-hero">
- <h1>Page Components</h1>
- <p class="doc-subtitle">Combine Component and Endpoint into a single powerful abstraction</p>
- </div>
-
- <section class="doc-section">
- <h2>What are Page Components?</h2>
- <p>
- A <code>PageComponent</code> is a special type of component that acts as both a
- <strong>Component</strong> AND an <strong>Endpoint</strong>. This means it can render
- HTML templates <em>and</em> handle HTTP requests at a specific route.
- </p>
- <p>
- In traditional web frameworks, you often need separate classes for routes/endpoints
- and for rendering views. PageComponent eliminates this duplication by combining
- both responsibilities into a single cohesive class.
- </p>
- </section>
-
- <section class="doc-section">
- <h2>PageComponent vs Component</h2>
- <p>
- Understanding when to use <code>PageComponent</code> vs <code>Component</code> is essential:
- </p>
-
- <table class="doc-table">
- <thead>
- <tr>
- <th>Feature</th>
- <th>Component</th>
- <th>PageComponent</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>Has markup</td>
- <td>✓ Yes</td>
- <td>✓ Yes</td>
- </tr>
- <tr>
- <td>Handles HTTP routes</td>
- <td>✗ No</td>
- <td>✓ Yes</td>
- </tr>
- <tr>
- <td>Can be nested in outlets</td>
- <td>✓ Yes</td>
- <td>✗ No (top-level only)</td>
- </tr>
- <tr>
- <td>Implements Endpoint</td>
- <td>✗ No</td>
- <td>✓ Yes</td>
- </tr>
- <tr>
- <td>Wrapped by templates</td>
- <td>✗ No</td>
- <td>✓ Yes</td>
- </tr>
- </tbody>
- </table>
- </section>
-
- <section class="doc-section">
- <h2>When to Use Each</h2>
-
- <div class="feature-grid">
- <div class="feature-card">
- <h3>Use PageComponent when:</h3>
- <ul>
- <li>Creating top-level pages (routes)</li>
- <li>Building documentation pages</li>
- <li>Rendering full HTML documents</li>
- <li>The component needs its own URL</li>
- </ul>
- </div>
- <div class="feature-card">
- <h3>Use Component when:</h3>
- <ul>
- <li>Building reusable UI elements</li>
- <li>Creating widgets or cards</li>
- <li>Nesting inside other components</li>
- <li>Rendering partial content</li>
- </ul>
- </div>
- </div>
- </section>
-
- <section class="doc-section">
- <h2>Basic PageComponent Example</h2>
- <p>
- Here's how to create a simple PageComponent:
- </p>
-
- <spry-component name="CodeBlockComponent" sid="basic-example"/>
- </section>
-
- <section class="doc-section">
- <h2>Route Registration</h2>
- <p>
- PageComponents are registered as endpoints in your application's Main.vala.
- You register both the component type and its route:
- </p>
-
- <spry-component name="CodeBlockComponent" sid="route-registration"/>
- </section>
-
- <section class="doc-section">
- <h2>Dependency Injection in Pages</h2>
- <p>
- PageComponents support the same dependency injection patterns as regular components.
- Use <code>inject<T>()</code> to access services, stores, and factories:
- </p>
-
- <spry-component name="CodeBlockComponent" sid="di-example"/>
- </section>
-
- <section class="doc-section">
- <h2>Template Wrapping</h2>
- <p>
- One of the most powerful features of PageComponents is automatic template wrapping.
- When a PageComponent renders, it is automatically wrapped by matching
- <code>PageTemplate</code> instances.
- </p>
- <p>
- This means your PageComponent markup only needs to contain the <em>content</em>
- of the page, not the full HTML document structure. The template provides the
- <code><html></code>, <code><head></code>, <code><body></code>,
- navigation, and footer.
- </p>
-
- <div class="info-box">
- <p>
- <strong>💡 Tip:</strong> Learn more about templates in the
- <a href="/page-components/templates">Page Templates</a> documentation.
- </p>
- </div>
- </section>
-
- <section class="doc-section">
- <h2>How handle_request() Works</h2>
- <p>
- The <code>PageComponent</code> base class implements the <code>Endpoint</code>
- interface's <code>handle_request()</code> method. This method:
- </p>
-
- <ol class="doc-list">
- <li>Collects all matching <code>PageTemplate</code> instances sorted by prefix depth</li>
- <li>Renders each template in order</li>
- <li>Finds <code><spry-template-outlet/></code> elements in each template</li>
- <li>Nests the content into the outlet, building the complete document</li>
- <li>Returns the final HTML as an <code>HttpResult</code></li>
- </ol>
-
- <p>
- You typically don't need to override <code>handle_request()</code> - the default
- implementation handles everything for you.
- </p>
- </section>
-
- <section class="doc-section">
- <h2>Next Steps</h2>
- <div class="nav-cards">
- <a href="/page-components/templates" class="nav-card">
- <h3>Page Templates →</h3>
- <p>Learn how to create layout templates that wrap your pages</p>
- </a>
- <a href="/components/overview" class="nav-card">
- <h3>Components Overview →</h3>
- <p>Review the basics of Spry components</p>
- </a>
- </div>
- </section>
- </div>
- """;
- }}
-
- public override async void prepare() throws Error {
- // Basic PageComponent example
- var basic_example = get_component_child<CodeBlockComponent>("basic-example");
- basic_example.language = "vala";
- basic_example.code = "using Spry;\n" +
- "using Inversion;\n\n" +
- "public class DocumentationPage : PageComponent {\n" +
- " \n" +
- " // Dependency injection works the same as Component\n" +
- " private ComponentFactory factory = inject<ComponentFactory>();\n" +
- " \n" +
- " public override string markup { get {\n" +
- " return \"\"\"\n" +
- " <div class=\"page-content\">\n" +
- " <h1>My Documentation Page</h1>\n" +
- " <p>This page handles its own route.</p>\n" +
- " </div>\n" +
- " \"\"\";\n" +
- " }}\n" +
- " \n" +
- " public override async void prepare() throws Error {\n" +
- " // Set up page content\n" +
- " }\n" +
- "}";
-
- // Route registration example
- var route_reg = get_component_child<CodeBlockComponent>("route-registration");
- route_reg.language = "vala";
- route_reg.code = "// In Main.vala:\n\n" +
- "// Register the page component with the dependency container\n" +
- "application.add_transient<DocumentationPage>();\n\n" +
- "// Register the page as an endpoint at a specific route\n" +
- "application.add_endpoint<DocumentationPage>(\n" +
- " new EndpointRoute(\"/docs/page\")\n" +
- ");\n\n" +
- "// Now visiting /docs/page will render DocumentationPage\n" +
- "// automatically wrapped by any matching PageTemplate";
-
- // Dependency injection example
- var di_example = get_component_child<CodeBlockComponent>("di-example");
- di_example.language = "vala";
- di_example.code = "public class UserProfilePage : PageComponent {\n" +
- " \n" +
- " // Inject services you need\n" +
- " private ComponentFactory factory = inject<ComponentFactory>();\n" +
- " private UserStore user_store = inject<UserStore>();\n" +
- " private HttpContext http_context = inject<HttpContext>();\n" +
- " \n" +
- " public override string markup { get {\n" +
- " return \"\"\"\n" +
- " <div class=\"profile-page\">\n" +
- " <h2 sid=\"username\"></h2>\n" +
- " <p sid=\"email\"></p>\n" +
- " </div>\n" +
- " \"\"\";\n" +
- " }}\n" +
- " \n" +
- " public override async void prepare() throws Error {\n" +
- " // Access route parameters from http_context\n" +
- " var user_id = http_context.request.query_params\n" +
- " .get_any_or_default(\"id\", \"guest\");\n" +
- " \n" +
- " // Use injected store to fetch data\n" +
- " var user = yield user_store.get_user(user_id);\n" +
- " \n" +
- " // Populate template\n" +
- " this[\"username\"].text_content = user.name;\n" +
- " this[\"email\"].text_content = user.email;\n" +
- " }\n" +
- "}";
- }
- }
|