ComponentsTemplateSyntaxPage.vala 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. using Spry;
  2. /**
  3. * ComponentsTemplateSyntaxPage - Documentation for Spry template syntax
  4. *
  5. * This page covers all the special HTML attributes and elements used in
  6. * Spry component templates.
  7. */
  8. public class ComponentsTemplateSyntaxPage : PageComponent {
  9. public const string ROUTE = "/components/template-syntax";
  10. public override string markup { get {
  11. return """
  12. <div sid="page" class="doc-content">
  13. <h1>Template Syntax</h1>
  14. <section class="doc-section">
  15. <p>
  16. Spry extends HTML with special attributes that enable reactive behavior,
  17. component composition, and dynamic content. This page covers all the
  18. template syntax available in Spry components.
  19. </p>
  20. </section>
  21. <section class="doc-section">
  22. <h2>Special Attributes</h2>
  23. <h3><code>sid</code> - Spry ID</h3>
  24. <p>
  25. The <code>sid</code> attribute assigns a unique identifier to an element within
  26. a component. Use it to select and manipulate elements in your Vala code.
  27. </p>
  28. <spry-component name="CodeBlockComponent" sid="sid-html"/>
  29. <spry-component name="CodeBlockComponent" sid="sid-vala"/>
  30. </section>
  31. <section class="doc-section">
  32. <h3><code>spry-action</code> - HTMX Action Binding</h3>
  33. <p>
  34. The <code>spry-action</code> attribute binds an element to trigger a component action
  35. when interacted with. The action is sent to the server via HTMX.
  36. </p>
  37. <spry-component name="CodeBlockComponent" sid="action-example"/>
  38. <p>
  39. See the <a href="/components/actions">Actions</a> page for more details.
  40. </p>
  41. </section>
  42. <section class="doc-section">
  43. <h3><code>spry-target</code> - Scoped Targeting</h3>
  44. <p>
  45. The <code>spry-target</code> attribute specifies which element should be replaced
  46. when an action completes. It targets elements by their <code>sid</code> within the
  47. same component.
  48. </p>
  49. <spry-component name="CodeBlockComponent" sid="target-example"/>
  50. </section>
  51. <section class="doc-section">
  52. <h3><code>spry-global</code> - Out-of-Band Swaps</h3>
  53. <p>
  54. The <code>spry-global</code> attribute marks an element for out-of-band updates.
  55. When included in a response, HTMX will swap this element anywhere on the page
  56. that has a matching <code>id</code>.
  57. </p>
  58. <spry-component name="CodeBlockComponent" sid="global-html"/>
  59. <spry-component name="CodeBlockComponent" sid="global-vala"/>
  60. </section>
  61. <section class="doc-section">
  62. <h3><code>*-expr</code> - Expression Attributes</h3>
  63. <p>
  64. Expression attributes dynamically set HTML attributes based on component properties.
  65. Use <code>content-expr</code> for text content, <code>class-*-expr</code> for
  66. conditional classes, and <code>any-attr-expr</code> for any other attribute.
  67. </p>
  68. <spry-component name="CodeBlockComponent" sid="expr-example"/>
  69. </section>
  70. <section class="doc-section">
  71. <h3><code>spry-if</code> / <code>spry-else-if</code> / <code>spry-else</code> - Conditionals</h3>
  72. <p>
  73. Use these attributes for conditional rendering. Only elements with truthy
  74. conditions will be included in the output.
  75. </p>
  76. <spry-component name="CodeBlockComponent" sid="conditional-example"/>
  77. </section>
  78. <section class="doc-section">
  79. <h3><code>spry-per-*</code> - Loop Rendering</h3>
  80. <p>
  81. Use <code>spry-per-{varname}="expression"</code> to iterate over collections.
  82. The variable name becomes available in nested expressions.
  83. </p>
  84. <spry-component name="CodeBlockComponent" sid="loop-example"/>
  85. </section>
  86. <section class="doc-section">
  87. <h2>Special Elements</h2>
  88. <h3><code>&lt;spry-outlet&gt;</code> - Child Component Outlets</h3>
  89. <p>
  90. Outlets are placeholders where child components can be inserted dynamically.
  91. Use <code>set_outlet_children()</code> to populate outlets.
  92. </p>
  93. <spry-component name="CodeBlockComponent" sid="outlet-example"/>
  94. <p>
  95. See the <a href="/components/outlets">Outlets</a> page for more details.
  96. </p>
  97. </section>
  98. <section class="doc-section">
  99. <h3><code>&lt;spry-component&gt;</code> - Declarative Child Components</h3>
  100. <p>
  101. Use <code>&lt;spry-component&gt;</code> to declare child components directly in
  102. your template. Access them with <code>get_component_child&lt;T&gt;()</code>.
  103. </p>
  104. <spry-component name="CodeBlockComponent" sid="component-html"/>
  105. <spry-component name="CodeBlockComponent" sid="component-vala"/>
  106. </section>
  107. <section class="doc-section">
  108. <h3><code>&lt;spry-context&gt;</code> - Context Property Preservation</h3>
  109. <p>
  110. The <code>&lt;spry-context&gt;</code> tag marks a property to be preserved across
  111. action requests. When a component action is triggered, properties marked with this
  112. tag are encrypted and included in the action URL, then restored when the action
  113. is handled.
  114. </p>
  115. <spry-component name="CodeBlockComponent" sid="context-example"/>
  116. <h4>How It Works</h4>
  117. <ul>
  118. <li>Properties marked with <code>&lt;spry-context property="name"/&gt;</code> are tracked</li>
  119. <li>When an action is triggered, these properties are encrypted using <code>CryptographyProvider</code></li>
  120. <li>The encrypted context is included in the action URL</li>
  121. <li>The server decrypts and restores the properties before calling <code>handle_action()</code></li>
  122. </ul>
  123. <div class="warning-box">
  124. <h4>⚠️ Security Warning: Replay Attacks</h4>
  125. <p>
  126. The encrypted context can be captured and replayed by malicious actors. While the
  127. data cannot be tampered with (it's cryptographically signed), an attacker could
  128. capture a valid request and replay it later.
  129. </p>
  130. <ul>
  131. <li><strong>Do not</strong> use context for sensitive data that could be exploited if replayed</li>
  132. <li><strong>Do not</strong> use context for authentication or authorization decisions</li>
  133. <li><strong>Consider</strong> adding timestamps or expiration for time-sensitive operations</li>
  134. </ul>
  135. <p>
  136. <strong>Example attack:</strong> If context contains admin privileges, an attacker
  137. could capture a request from an admin session and replay it to gain unauthorized access.
  138. </p>
  139. </div>
  140. <h4>Best Practices</h4>
  141. <ul>
  142. <li>Use context for non-sensitive data like IDs, flags, or UI configuration</li>
  143. <li>Validate context data on the server before using it</li>
  144. <li>Keep context data minimal - only include what's needed</li>
  145. </ul>
  146. </section>
  147. <section class="doc-section">
  148. <h3><code>spry-res</code> - Static Resources</h3>
  149. <p>
  150. Use <code>spry-res</code> to reference Spry's built-in static resources.
  151. This resolves to <code>/_spry/res/{resource-name}</code> automatically.
  152. </p>
  153. <spry-component name="CodeBlockComponent" sid="res-example"/>
  154. </section>
  155. <section class="doc-section">
  156. <h2>Next Steps</h2>
  157. <div class="nav-cards">
  158. <a href="/components/actions" class="nav-card">
  159. <h3>Actions →</h3>
  160. <p>Handle user interactions with actions</p>
  161. </a>
  162. <a href="/components/outlets" class="nav-card">
  163. <h3>Outlets →</h3>
  164. <p>Compose components with outlets</p>
  165. </a>
  166. <a href="/components/continuations" class="nav-card">
  167. <h3>Continuations →</h3>
  168. <p>Real-time updates with SSE</p>
  169. </a>
  170. </div>
  171. </section>
  172. </div>
  173. """;
  174. }}
  175. public override async void prepare() throws Error {
  176. // sid examples
  177. var sid_html = get_component_child<CodeBlockComponent>("sid-html");
  178. sid_html.language = "HTML";
  179. sid_html.code = """<div sid="container">
  180. <span sid="title">Hello</span>
  181. <button sid="submit-btn">Submit</button>
  182. </div>""";
  183. var sid_vala = get_component_child<CodeBlockComponent>("sid-vala");
  184. sid_vala.language = "Vala";
  185. sid_vala.code = """// Access elements by sid in prepare() or handle_action()
  186. this["title"].text_content = "Updated Title";
  187. this["submit-btn"].set_attribute("disabled", "true");""";
  188. // spry-action example
  189. var action_example = get_component_child<CodeBlockComponent>("action-example");
  190. action_example.language = "HTML";
  191. action_example.code = """<!-- Same-component action -->
  192. <button spry-action=":Toggle">Toggle</button>
  193. <!-- Cross-component action -->
  194. <button spry-action="ListComponent:Add">Add Item</button>""";
  195. // spry-target example
  196. var target_example = get_component_child<CodeBlockComponent>("target-example");
  197. target_example.language = "HTML";
  198. target_example.code = """<div sid="item" class="item">
  199. <span sid="title">Item Title</span>
  200. <button spry-action=":Delete" spry-target="item">Delete</button>
  201. </div>""";
  202. // spry-global examples
  203. var global_html = get_component_child<CodeBlockComponent>("global-html");
  204. global_html.language = "HTML";
  205. global_html.code = """<!-- In HeaderComponent -->
  206. <div id="header" spry-global="header">
  207. <h1>Todo App</h1>
  208. <span sid="count">0 items</span>
  209. </div>""";
  210. var global_vala = get_component_child<CodeBlockComponent>("global-vala");
  211. global_vala.language = "Vala";
  212. global_vala.code = """// In another component's handle_action()
  213. add_globals_from(header); // Includes header in response for OOB swap""";
  214. // Expression attributes example
  215. var expr_example = get_component_child<CodeBlockComponent>("expr-example");
  216. expr_example.language = "HTML";
  217. expr_example.code = """<!-- Content expression -->
  218. <span content-expr='format("%i%%", this.percent)'>0%</span>
  219. <!-- Style expression -->
  220. <div style-width-expr='format("%i%%", this.percent)'></div>
  221. <!-- Conditional class -->
  222. <div class-completed-expr="this.is_completed">Task</div>
  223. <!-- Any attribute expression -->
  224. <input disabled-expr="this.is_readonly">""";
  225. // Conditional example
  226. var conditional_example = get_component_child<CodeBlockComponent>("conditional-example");
  227. conditional_example.language = "HTML";
  228. conditional_example.code = """<div spry-if="this.is_admin">Admin Panel</div>
  229. <div spry-else-if="this.is_moderator">Moderator Panel</div>
  230. <div spry-else>User Panel</div>""";
  231. // Loop example
  232. var loop_example = get_component_child<CodeBlockComponent>("loop-example");
  233. loop_example.language = "HTML";
  234. loop_example.code = """<ul>
  235. <li spry-per-task="this.tasks">
  236. <span content-expr="task.name"></span>
  237. <span content-expr="task.status"></span>
  238. </li>
  239. </ul>""";
  240. // Outlet example
  241. var outlet_example = get_component_child<CodeBlockComponent>("outlet-example");
  242. outlet_example.language = "HTML";
  243. outlet_example.code = """<div sid="list">
  244. <spry-outlet sid="items"/>
  245. </div>""";
  246. // Component examples
  247. var component_html = get_component_child<CodeBlockComponent>("component-html");
  248. component_html.language = "HTML";
  249. component_html.code = """<div>
  250. <spry-component name="HeaderComponent" sid="header"/>
  251. <spry-component name="TodoListComponent" sid="todo-list"/>
  252. <spry-component name="FooterComponent" sid="footer"/>
  253. </div>""";
  254. var component_vala = get_component_child<CodeBlockComponent>("component-vala");
  255. component_vala.language = "Vala";
  256. component_vala.code = """public override async void prepare() throws Error {
  257. var header = get_component_child<HeaderComponent>("header");
  258. header.title = "My App";
  259. }""";
  260. // Context example
  261. var context_example = get_component_child<CodeBlockComponent>("context-example");
  262. context_example.language = "HTML";
  263. context_example.code = """<!-- Mark properties to be preserved across actions -->
  264. <spry-context property="demo_component_name"/>
  265. <spry-context property="source_file"/>
  266. <div sid="host">
  267. <span content-expr="this.demo_component_name">Demo</span>
  268. <button spry-action=":ShowSource" spry-target="host">Show Source</button>
  269. </div>""";
  270. // Static resources example
  271. var res_example = get_component_child<CodeBlockComponent>("res-example");
  272. res_example.language = "HTML";
  273. res_example.code = """<script spry-res="htmx.js"></script>
  274. <script spry-res="htmx-sse.js"></script>""";
  275. }
  276. }