| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- using Spry;
- using Inversion;
- /**
- * PageTemplatesPage - Documentation for Page Templates
- *
- * This page explains how PageTemplates work, how to create them,
- * and how they wrap PageComponents to provide site-wide layouts.
- */
- public class PageTemplatesPage : PageComponent {
-
- public const string ROUTE = "/page-components/templates";
-
- private ComponentFactory factory = inject<ComponentFactory>();
-
- public override string markup { get {
- return """
- <div class="doc-content">
- <div class="doc-hero">
- <h1>Page Templates</h1>
- <p class="doc-subtitle">Create reusable layouts that wrap your page components</p>
- </div>
-
- <section class="doc-section">
- <h2>What are Page Templates?</h2>
- <p>
- A <code>PageTemplate</code> is a special component that provides the outer HTML
- structure for your pages. Templates define the <code><html></code>,
- <code><head></code>, <code><body></code>, and common elements like
- navigation headers and footers.
- </p>
- <p>
- The key feature of a PageTemplate is the <code><spry-template-outlet/></code>
- element, which marks where page content will be inserted. When a PageComponent
- renders, it automatically gets nested inside matching templates.
- </p>
- </section>
-
- <section class="doc-section">
- <h2>The Template Outlet</h2>
- <p>
- The <code><spry-template-outlet/></code> element is the placeholder where
- page content gets inserted. Every PageTemplate must include at least one outlet.
- </p>
-
- <spry-component name="CodeBlockComponent" sid="outlet-example"/>
-
- <div class="info-box">
- <p>
- <strong>💡 How it works:</strong> When rendering, Spry finds all templates
- matching the current route, renders them in order of specificity, and nests
- each one's content into the previous template's outlet.
- </p>
- </div>
- </section>
-
- <section class="doc-section">
- <h2>Creating a MainTemplate</h2>
- <p>
- Here's a complete example of a site-wide template that provides the HTML
- document structure, head elements, navigation, and footer:
- </p>
-
- <spry-component name="CodeBlockComponent" sid="main-template"/>
- </section>
-
- <section class="doc-section">
- <h2>Template Registration</h2>
- <p>
- Templates are registered with a <strong>prefix</strong> that determines which
- routes they apply to. The prefix is passed via metadata during registration:
- </p>
-
- <spry-component name="CodeBlockComponent" sid="template-registration"/>
-
- <p>
- In this example, the <code>TemplateRoutePrefix("")</code> with an empty string
- matches <strong>all routes</strong>, making it the default site-wide template.
- </p>
- </section>
-
- <section class="doc-section">
- <h2>Route Prefix Matching</h2>
- <p>
- Templates can target specific sections of your site using route prefixes.
- The prefix determines which routes the template will wrap:
- </p>
-
- <table class="doc-table">
- <thead>
- <tr>
- <th>Prefix</th>
- <th>Matches Routes</th>
- <th>Use Case</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td><code>""</code> (empty)</td>
- <td>All routes</td>
- <td>Site-wide default template</td>
- </tr>
- <tr>
- <td><code>"/admin"</code></td>
- <td>/admin/*, /admin/settings, etc.</td>
- <td>Admin section layout</td>
- </tr>
- <tr>
- <td><code>"/docs"</code></td>
- <td>/docs/*, /docs/getting-started, etc.</td>
- <td>Documentation section</td>
- </tr>
- <tr>
- <td><code>"/api"</code></td>
- <td>/api/* routes</td>
- <td>API documentation or explorer</td>
- </tr>
- </tbody>
- </table>
-
- <h3>How Matching Works</h3>
- <p>
- The <code>TemplateRoutePrefix.matches_route()</code> method compares the template's
- prefix segments against the route segments. A template matches if its prefix
- segments are a prefix of the route segments.
- </p>
-
- <spry-component name="CodeBlockComponent" sid="matching-logic"/>
- </section>
-
- <section class="doc-section">
- <h2>Multiple Templates</h2>
- <p>
- You can have multiple templates for different sections of your site.
- Templates are sorted by <strong>rank</strong> (prefix depth) and applied
- from lowest to highest rank:
- </p>
-
- <spry-component name="CodeBlockComponent" sid="multiple-templates"/>
-
- <h3>Template Nesting Order</h3>
- <p>
- For a route like <code>/admin/users</code>, templates would be applied in order:
- </p>
-
- <ol class="doc-list">
- <li><strong>MainTemplate</strong> (prefix: "") - rank 0</li>
- <li><strong>AdminTemplate</strong> (prefix: "/admin") - rank 1</li>
- <li><strong>PageComponent</strong> - The actual page content</li>
- </ol>
-
- <p>
- Each template's outlet receives the content from the next item in the chain,
- creating nested layouts.
- </p>
- </section>
-
- <section class="doc-section">
- <h2>Section-Specific Template Example</h2>
- <p>
- Here's an example of a template specifically for the admin section that
- adds an admin sidebar:
- </p>
-
- <spry-component name="CodeBlockComponent" sid="admin-template"/>
- </section>
-
- <section class="doc-section">
- <h2>Head Content Merging</h2>
- <p>
- When templates wrap pages, the <code><head></code> elements are automatically
- merged. If a PageComponent or nested template has <code><head></code> content,
- those elements are appended to the outer template's head.
- </p>
-
- <p>
- This allows pages to add their own stylesheets, scripts, or meta tags while
- still benefiting from the template's common head elements.
- </p>
-
- <div class="warning-box">
- <p>
- <strong>⚠️ Note:</strong> Head merging only works when templates render
- actual <code><head></code> elements. Make sure your templates include
- a proper HTML structure with head and body sections.
- </p>
- </div>
- </section>
-
- <section class="doc-section">
- <h2>Template vs Component</h2>
-
- <table class="doc-table">
- <thead>
- <tr>
- <th>Feature</th>
- <th>PageTemplate</th>
- <th>Component</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>Base class</td>
- <td><code>Component</code></td>
- <td><code>Component</code></td>
- </tr>
- <tr>
- <td>Has markup</td>
- <td>✓ Yes</td>
- <td>✓ Yes</td>
- </tr>
- <tr>
- <td>Contains outlet</td>
- <td>✓ Required</td>
- <td>✗ Optional</td>
- </tr>
- <tr>
- <td>Route matching</td>
- <td>By prefix</td>
- <td>N/A</td>
- </tr>
- <tr>
- <td>Wraps pages</td>
- <td>✓ Yes</td>
- <td>✗ No</td>
- </tr>
- </tbody>
- </table>
- </section>
-
- <section class="doc-section">
- <h2>Best Practices</h2>
-
- <ul class="doc-list">
- <li><strong>Keep templates focused:</strong> Each template should handle one level of layout (site-wide, section-specific)</li>
- <li><strong>Use semantic HTML:</strong> Include proper <code><header></code>, <code><main></code>, <code><footer></code> elements</li>
- <li><strong>Include common resources:</strong> Add shared stylesheets and scripts in your main template</li>
- <li><strong>Plan your prefix hierarchy:</strong> Design your URL structure to work with template prefixes</li>
- <li><strong>Don't duplicate content:</strong> Let templates handle repeated elements like navigation</li>
- </ul>
- </section>
-
- <section class="doc-section">
- <h2>Next Steps</h2>
- <div class="nav-cards">
- <a href="/page-components/overview" class="nav-card">
- <h3>← Page Components</h3>
- <p>Learn about PageComponent basics</p>
- </a>
- <a href="/components/outlets" class="nav-card">
- <h3>Component Outlets →</h3>
- <p>Use outlets for nested components</p>
- </a>
- </div>
- </section>
- </div>
- """;
- }}
-
- public override async void prepare() throws Error {
- // Outlet example
- var outlet_example = get_component_child<CodeBlockComponent>("outlet-example");
- outlet_example.language = "xml";
- outlet_example.code = "<!DOCTYPE html>\n" +
- "<html lang=\"en\">\n" +
- "<head>\n" +
- " <title>My Site</title>\n" +
- " <link rel=\"stylesheet\" href=\"/styles/main.css\">\n" +
- "</head>\n" +
- "<body>\n" +
- " <header>\n" +
- " <nav><!-- Site navigation --></nav>\n" +
- " </header>\n" +
- " \n" +
- " <main>\n" +
- " <!-- Page content is inserted here -->\n" +
- " <spry-template-outlet />\n" +
- " </main>\n" +
- " \n" +
- " <footer>\n" +
- " <p>© 2024 My Site</p>\n" +
- " </footer>\n" +
- "</body>\n" +
- "</html>";
-
- // Main template example
- var main_template = get_component_child<CodeBlockComponent>("main-template");
- main_template.language = "vala";
- main_template.code = "using Spry;\n\n" +
- "/**\n" +
- " * MainTemplate - Site-wide layout template\n" +
- " * \n" +
- " * Wraps all pages with common HTML structure,\n" +
- " * navigation, and footer.\n" +
- " */\n" +
- "public class MainTemplate : PageTemplate {\n" +
- " \n" +
- " public override string markup { get {\n" +
- " return \"\"\"\n" +
- " <!DOCTYPE html>\n" +
- " <html lang=\"en\">\n" +
- " <head>\n" +
- " <meta charset=\"UTF-8\">\n" +
- " <meta name=\"viewport\" \n" +
- " content=\"width=device-width, initial-scale=1.0\">\n" +
- " <title>My Spry Application</title>\n" +
- " <link rel=\"stylesheet\" href=\"/styles/main.css\">\n" +
- " <script spry-res=\"htmx.js\"></script>\n" +
- " </head>\n" +
- " <body>\n" +
- " <header class=\"site-header\">\n" +
- " <nav>\n" +
- " <a href=\"/\" class=\"logo\">My App</a>\n" +
- " <ul>\n" +
- " <li><a href=\"/docs\">Docs</a></li>\n" +
- " <li><a href=\"/about\">About</a></li>\n" +
- " </ul>\n" +
- " </nav>\n" +
- " </header>\n" +
- " \n" +
- " <main>\n" +
- " <spry-template-outlet />\n" +
- " </main>\n" +
- " \n" +
- " <footer class=\"site-footer\">\n" +
- " <p>© 2024 My App. Built with Spry.</p>\n" +
- " </footer>\n" +
- " </body>\n" +
- " </html>\n" +
- " \"\"\";\n" +
- " }}\n" +
- "}";
-
- // Template registration
- var template_reg = get_component_child<CodeBlockComponent>("template-registration");
- template_reg.language = "vala";
- template_reg.code = "// In Main.vala:\n\n" +
- "var spry_cfg = application.configure_with<SpryConfigurator>();\n\n" +
- "// Register template with empty prefix (matches all routes)\n" +
- "spry_cfg.add_template<MainTemplate>(\"\");\n\n" +
- "// The TemplateRoutePrefix is created internally from the string\n" +
- "// and used for route matching";
-
- // Matching logic
- var matching_logic = get_component_child<CodeBlockComponent>("matching-logic");
- matching_logic.language = "vala";
- matching_logic.code = "// TemplateRoutePrefix matching logic (simplified)\n\n" +
- "public class TemplateRoutePrefix : Object {\n" +
- " public uint rank { get; private set; }\n" +
- " public string prefix { get; private set; }\n" +
- " \n" +
- " public TemplateRoutePrefix(string prefix) {\n" +
- " this.prefix = prefix;\n" +
- " // Rank is the number of path segments\n" +
- " rank = prefix.split(\"/\").length - 1;\n" +
- " }\n" +
- " \n" +
- " public bool matches_route(RouteContext context) {\n" +
- " // Returns true if prefix segments match\n" +
- " // the beginning of the route segments\n" +
- " return context.matched_route.route_segments\n" +
- " .starts_with(this.prefix_segments);\n" +
- " }\n" +
- "}\n\n" +
- "// Example: prefix \"/admin\" matches:\n" +
- "// /admin ✓ (exact match)\n" +
- "// /admin/users ✓ (prefix match)\n" +
- "// /admin/settings ✓ (prefix match)\n" +
- "// /user/admin ✗ (not a prefix match)";
-
- // Multiple templates
- var multiple_templates = get_component_child<CodeBlockComponent>("multiple-templates");
- multiple_templates.language = "vala";
- multiple_templates.code = "// In Main.vala:\n\n" +
- "var spry_cfg = application.configure_with<SpryConfigurator>();\n\n" +
- "// Site-wide template (rank 0, matches everything)\n" +
- "spry_cfg.add_template<MainTemplate>(\"\");\n\n" +
- "// Admin section template (rank 1, matches /admin/*)\n" +
- "spry_cfg.add_template<AdminTemplate>(\"/admin\");\n\n" +
- "// Documentation template (rank 1, matches /docs/*)\n" +
- "spry_cfg.add_template<DocsTemplate>(\"/docs\");\n\n" +
- "// API docs template (rank 2, matches /docs/api/*)\n" +
- "spry_cfg.add_template<ApiDocsTemplate>(\"/docs/api\");";
-
- // Admin template example
- var admin_template = get_component_child<CodeBlockComponent>("admin-template");
- admin_template.language = "vala";
- admin_template.code = "using Spry;\n\n" +
- "/**\n" +
- " * AdminTemplate - Layout for admin section\n" +
- " * \n" +
- " * Adds an admin sidebar to all /admin/* pages.\n" +
- " */\n" +
- "public class AdminTemplate : PageTemplate {\n" +
- " \n" +
- " public override string markup { get {\n" +
- " return \"\"\"\n" +
- " <div class=\"admin-layout\">\n" +
- " <aside class=\"admin-sidebar\">\n" +
- " <h3>Admin</h3>\n" +
- " <nav>\n" +
- " <a href=\"/admin\">Dashboard</a>\n" +
- " <a href=\"/admin/users\">Users</a>\n" +
- " <a href=\"/admin/settings\">Settings</a>\n" +
- " </nav>\n" +
- " </aside>\n" +
- " \n" +
- " <div class=\"admin-content\">\n" +
- " <!-- Page content inserted here -->\n" +
- " <spry-template-outlet />\n" +
- " </div>\n" +
- " </div>\n" +
- " \"\"\";\n" +
- " }}\n" +
- "}\n\n" +
- "// Register with /admin prefix\n" +
- "// spry_cfg.add_template<AdminTemplate>(\"/admin\");";
- }
- }
|