using Spry; /** * ComponentsTemplateSyntaxPage - Documentation for Spry template syntax * * This page covers all the special HTML attributes and elements used in * Spry component templates. */ public class ComponentsTemplateSyntaxPage : PageComponent { public const string ROUTE = "/components/template-syntax"; public override string markup { get { return """

Template Syntax

Spry extends HTML with special attributes that enable reactive behavior, component composition, and dynamic content. This page covers all the template syntax available in Spry components.

Special Attributes

sid - Spry ID

The sid attribute assigns a unique identifier to an element within a component. Use it to select and manipulate elements in your Vala code.

spry-action - HTMX Action Binding

The spry-action attribute binds an element to trigger a component action when interacted with. The action is sent to the server via HTMX.

See the Actions page for more details.

spry-target - Scoped Targeting

The spry-target attribute specifies which element should be replaced when an action completes. It targets elements by their sid within the same component.

spry-global - Out-of-Band Swaps

The spry-global attribute marks an element for out-of-band updates. When included in a response, HTMX will swap this element anywhere on the page that has a matching id.

*-expr - Expression Attributes

Expression attributes dynamically set HTML attributes based on component properties. Use content-expr for text content, class-*-expr for conditional classes, and any-attr-expr for any other attribute.

spry-if / spry-else-if / spry-else - Conditionals

Use these attributes for conditional rendering. Only elements with truthy conditions will be included in the output.

spry-per-* - Loop Rendering

Use spry-per-{varname}="expression" to iterate over collections. The variable name becomes available in nested expressions.

Special Elements

<spry-outlet> - Child Component Outlets

Outlets are placeholders where child components can be inserted dynamically. Use set_outlet_children() to populate outlets.

See the Outlets page for more details.

<spry-component> - Declarative Child Components

Use <spry-component> to declare child components directly in your template. Access them with get_component_child<T>().

<spry-context> - Context Property Preservation

The <spry-context> tag marks a property to be preserved across action requests. When a component action is triggered, properties marked with this tag are encrypted and included in the action URL, then restored when the action is handled.

How It Works

  • Properties marked with <spry-context property="name"/> are tracked
  • When an action is triggered, these properties are encrypted using CryptographyProvider
  • The encrypted context is included in the action URL
  • The server decrypts and restores the properties before calling handle_action()

⚠️ Security Warning: Replay Attacks

The encrypted context can be captured and replayed by malicious actors. While the data cannot be tampered with (it's cryptographically signed), an attacker could capture a valid request and replay it later.

  • Do not use context for sensitive data that could be exploited if replayed
  • Do not use context for authentication or authorization decisions
  • Consider adding timestamps or expiration for time-sensitive operations

Example attack: If context contains admin privileges, an attacker could capture a request from an admin session and replay it to gain unauthorized access.

Best Practices

  • Use context for non-sensitive data like IDs, flags, or UI configuration
  • Validate context data on the server before using it
  • Keep context data minimal - only include what's needed

spry-res - Static Resources

Use spry-res to reference Spry's built-in static resources. This resolves to /_spry/res/{resource-name} automatically.

Next Steps

"""; }} public override async void prepare() throws Error { // sid examples var sid_html = get_component_child("sid-html"); sid_html.language = "HTML"; sid_html.code = """
Hello
"""; var sid_vala = get_component_child("sid-vala"); sid_vala.language = "Vala"; sid_vala.code = """// Access elements by sid in prepare() or handle_action() this["title"].text_content = "Updated Title"; this["submit-btn"].set_attribute("disabled", "true");"""; // spry-action example var action_example = get_component_child("action-example"); action_example.language = "HTML"; action_example.code = """ """; // spry-target example var target_example = get_component_child("target-example"); target_example.language = "HTML"; target_example.code = """
Item Title
"""; // spry-global examples var global_html = get_component_child("global-html"); global_html.language = "HTML"; global_html.code = """ """; var global_vala = get_component_child("global-vala"); global_vala.language = "Vala"; global_vala.code = """// In another component's handle_action() add_globals_from(header); // Includes header in response for OOB swap"""; // Expression attributes example var expr_example = get_component_child("expr-example"); expr_example.language = "HTML"; expr_example.code = """ 0%
Task
"""; // Conditional example var conditional_example = get_component_child("conditional-example"); conditional_example.language = "HTML"; conditional_example.code = """
Admin Panel
Moderator Panel
User Panel
"""; // Loop example var loop_example = get_component_child("loop-example"); loop_example.language = "HTML"; loop_example.code = """
"""; // Outlet example var outlet_example = get_component_child("outlet-example"); outlet_example.language = "HTML"; outlet_example.code = """
"""; // Component examples var component_html = get_component_child("component-html"); component_html.language = "HTML"; component_html.code = """
"""; var component_vala = get_component_child("component-vala"); component_vala.language = "Vala"; component_vala.code = """public override async void prepare() throws Error { var header = get_component_child("header"); header.title = "My App"; }"""; // Context example var context_example = get_component_child("context-example"); context_example.language = "HTML"; context_example.code = """
Demo
"""; // Static resources example var res_example = get_component_child("res-example"); res_example.language = "HTML"; res_example.code = """ """; } }