Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions docs/specs/examples/AlertComponent.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!--
AlertComponent template demonstrating complex fragment-based rendering.

Each alert type has:
- Different CSS classes
- Different icons
- Different content structures (some have expandable details)
-->

<div view:context-root xmlns:view="https://github.com/tschuehly/spring-view-component">

<!-- Info Alert Fragment -->
<div view:context="InfoAlert" class="alert alert-info" role="alert">
<div class="alert-header">
<svg class="alert-icon" aria-hidden="true">
<use href="#icon-info"></use>
</svg>
<span class="alert-message" th:text="${infoAlert.message}">
Information message
</span>
</div>
</div>

<!-- Warning Alert Fragment -->
<div view:context="WarningAlert" class="alert alert-warning" role="alert">
<div class="alert-header">
<svg class="alert-icon" aria-hidden="true">
<use href="#icon-warning"></use>
</svg>
<span class="alert-message" th:text="${warningAlert.message}">
Warning message
</span>
</div>
<details class="alert-details">
<summary>Show Details</summary>
<pre class="alert-details-content" th:text="${warningAlert.details}">
Warning details
</pre>
</details>
</div>

<!-- Error Alert Fragment -->
<div view:context="ErrorAlert" class="alert alert-error" role="alert">
<div class="alert-header">
<svg class="alert-icon" aria-hidden="true">
<use href="#icon-error"></use>
</svg>
<span class="alert-message" th:text="${errorAlert.message}">
Error message
</span>
</div>
<details class="alert-details">
<summary>Show Stack Trace</summary>
<pre class="alert-details-content alert-stacktrace" th:text="${errorAlert.stackTrace}">
Stack trace
</pre>
</details>
</div>

<!-- Success Alert Fragment -->
<div view:context="SuccessAlert" class="alert alert-success" role="alert">
<div class="alert-header">
<svg class="alert-icon" aria-hidden="true">
<use href="#icon-success"></use>
</svg>
<span class="alert-message" th:text="${successAlert.message}">
Success message
</span>
</div>
</div>

</div>

<!--
SVG Icons (normally in a separate file)
-->
<svg style="display: none;">
<symbol id="icon-info" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="16" x2="12" y2="12"/>
<line x1="12" y1="8" x2="12.01" y2="8"/>
</symbol>
<symbol id="icon-warning" viewBox="0 0 24 24">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
<line x1="12" y1="9" x2="12" y2="13"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</symbol>
<symbol id="icon-error" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<line x1="9" y1="9" x2="15" y2="15"/>
</symbol>
<symbol id="icon-success" viewBox="0 0 24 24">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22 4 12 14.01 9 11.01"/>
</symbol>
</svg>
75 changes: 75 additions & 0 deletions docs/specs/examples/AlertComponent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package de.tschuehly.example.fragments;

import de.tschuehly.spring.viewcomponent.core.component.ViewComponent;
import de.tschuehly.spring.viewcomponent.thymeleaf.ViewContext;

/**
* Advanced example showing fragment rendering for alert messages.
*
* Demonstrates:
* - Different data requirements for different contexts (e.g., ErrorAlert has stackTrace)
* - Compile-time safety (can't create ErrorAlert without stackTrace)
* - Single template with multiple presentation variants
*/
@ViewComponent
public class AlertComponent {

/**
* Informational alert - simple message display
*/
public record InfoAlert(String message) implements ViewContext {}

/**
* Warning alert - message with additional details
*/
public record WarningAlert(String message, String details) implements ViewContext {}

/**
* Error alert - message with stack trace
*/
public record ErrorAlert(String message, String stackTrace) implements ViewContext {}

/**
* Success alert - simple message for successful operations
*/
public record SuccessAlert(String message) implements ViewContext {}

public InfoAlert info(String message) {
return new InfoAlert(message);
}

public WarningAlert warning(String message, String details) {
return new WarningAlert(message, details);
}

public ErrorAlert error(String message, String stackTrace) {
return new ErrorAlert(message, stackTrace);
}

public SuccessAlert success(String message) {
return new SuccessAlert(message);
}

/**
* Convenience method to create error alerts from exceptions
*/
public ErrorAlert error(String message, Exception exception) {
var stackTrace = buildStackTrace(exception);
return new ErrorAlert(message, stackTrace);
}

private String buildStackTrace(Exception exception) {
var sb = new StringBuilder();
sb.append(exception.getClass().getName()).append(": ").append(exception.getMessage()).append("\n");

for (var element : exception.getStackTrace()) {
sb.append(" at ").append(element.toString()).append("\n");
}

if (exception.getCause() != null) {
sb.append("Caused by: ").append(buildStackTrace((Exception) exception.getCause()));
}

return sb.toString();
}
}
35 changes: 35 additions & 0 deletions docs/specs/examples/ButtonComponent.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!--
ButtonComponent template with fragment-based conditional rendering.

Each fragment corresponds to a specific ViewContext type.
Only the fragment matching the current ViewContext type will be rendered.
-->

<div view:context-root xmlns:view="https://github.com/tschuehly/spring-view-component">

<!-- Fragment for PrimaryButton context -->
<button view:context="PrimaryButton"
class="btn btn-primary"
type="button"
th:attr="data-action=${primaryButton.action}">
<span th:text="${primaryButton.label}">Primary Action</span>
</button>

<!-- Fragment for SecondaryButton context -->
<button view:context="SecondaryButton"
class="btn btn-secondary"
type="button"
th:attr="data-action=${secondaryButton.action}">
<span th:text="${secondaryButton.label}">Secondary Action</span>
</button>

<!-- Fragment for DangerButton context -->
<button view:context="DangerButton"
class="btn btn-danger"
type="button"
th:attr="data-action=${dangerButton.action}"
th:onclick="|return confirm('${dangerButton.confirmMessage}')|">
<span th:text="${dangerButton.label}">Danger Action</span>
</button>

</div>
61 changes: 61 additions & 0 deletions docs/specs/examples/ButtonComponent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package de.tschuehly.example.fragments;

import de.tschuehly.spring.viewcomponent.core.component.ViewComponent;
import de.tschuehly.spring.viewcomponent.thymeleaf.ViewContext;

/**
* Example ViewComponent demonstrating fragment rendering with multiple ViewContext types.
*
* This single component can render different button variants (primary, secondary, danger)
* based on the ViewContext type returned from the render methods.
*/
@ViewComponent
public class ButtonComponent {

/**
* ViewContext for primary action buttons.
*/
public record PrimaryButton(
String label,
String action
) implements ViewContext {}

/**
* ViewContext for secondary/alternative action buttons.
*/
public record SecondaryButton(
String label,
String action
) implements ViewContext {}

/**
* ViewContext for dangerous/destructive action buttons.
* Includes a confirmation message that will be shown before execution.
*/
public record DangerButton(
String label,
String action,
String confirmMessage
) implements ViewContext {}

/**
* Renders a primary button for main actions.
*/
public PrimaryButton primary(String label, String action) {
return new PrimaryButton(label, action);
}

/**
* Renders a secondary button for alternative actions.
*/
public SecondaryButton secondary(String label, String action) {
return new SecondaryButton(label, action);
}

/**
* Renders a danger button for destructive actions.
*/
public DangerButton danger(String label, String action, String confirmMessage) {
return new DangerButton(label, action, confirmMessage);
}
}
32 changes: 32 additions & 0 deletions docs/specs/examples/ButtonComponent.jte
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@import de.tschuehly.example.fragments.ButtonComponent.*

<%--
ButtonComponent JTE template with type-based conditional rendering.

JTE natively supports instanceof checks with pattern matching,
providing compile-time type safety.
--%>

@if(model instanceof PrimaryButton primaryButton)
<button class="btn btn-primary"
type="button"
data-action="${primaryButton.action()}">
${primaryButton.label()}
</button>
@elseif(model instanceof SecondaryButton secondaryButton)
<button class="btn btn-secondary"
type="button"
data-action="${secondaryButton.action()}">
${secondaryButton.label()}
</button>
@elseif(model instanceof DangerButton dangerButton)
<button class="btn btn-danger"
type="button"
data-action="${dangerButton.action()}"
onclick="return confirm('${dangerButton.confirmMessage()}')">
${dangerButton.label()}
</button>
@else
<%-- Fallback for unknown context types --%>
<div class="error">Unknown button type: ${model.getClass().getSimpleName()}</div>
@endif
68 changes: 68 additions & 0 deletions docs/specs/examples/ExampleController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package de.tschuehly.example.fragments;

import de.tschuehly.spring.viewcomponent.core.IViewContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

/**
* Example controller demonstrating fragment rendering usage.
*
* Different endpoints return different ViewContext types from the same ViewComponent,
* causing different fragments to be rendered.
*/
@Controller
public class ExampleController {

private final ButtonComponent buttonComponent;

public ExampleController(ButtonComponent buttonComponent) {
this.buttonComponent = buttonComponent;
}

/**
* Returns a primary button - renders the PrimaryButton fragment
*/
@GetMapping("/button/submit")
IViewContext submitButton() {
return buttonComponent.primary("Submit Form", "/api/submit");
}

/**
* Returns a secondary button - renders the SecondaryButton fragment
*/
@GetMapping("/button/cancel")
IViewContext cancelButton() {
return buttonComponent.secondary("Cancel", "/api/cancel");
}

/**
* Returns a danger button - renders the DangerButton fragment
*/
@GetMapping("/button/delete")
IViewContext deleteButton() {
return buttonComponent.danger(
"Delete Account",
"/api/delete-account",
"Are you sure you want to delete your account? This action cannot be undone."
);
}

/**
* Example showing nested component rendering with fragments.
*
* The page component can receive different button variants
* as child components.
*/
@GetMapping("/page/submit-form")
IViewContext submitFormPage() {
var submitButton = buttonComponent.primary("Submit", "/submit");
var cancelButton = buttonComponent.secondary("Cancel", "/cancel");

// In a real application, you might have a PageComponent that accepts
// button components as parameters
// return pageComponent.render(submitButton, cancelButton);

return submitButton; // Simplified for this example
}
}
Loading
Loading