diff --git a/samples/MinimalHtmxorApp/Components/Pages/Weather.razor b/samples/MinimalHtmxorApp/Components/Pages/Weather.razor
index c9eddf2..778884e 100644
--- a/samples/MinimalHtmxorApp/Components/Pages/Weather.razor
+++ b/samples/MinimalHtmxorApp/Components/Pages/Weather.razor
@@ -1,9 +1,17 @@
@page "/weather"
-Weather - loading data
+@inherits ConditionalComponentBase
+Weather
Weather
-This component demonstrates showing data.
-
+This component demonstrates showing lazy loaded data.
+
+
+
+ Weather - Loading data, please wait ...
+ Loading data . . .
+
+
+
+
+
diff --git a/samples/MinimalHtmxorApp/Components/Pages/WeatherData.razor b/samples/MinimalHtmxorApp/Components/Pages/WeatherData.razor
index 03dd913..88d589e 100644
--- a/samples/MinimalHtmxorApp/Components/Pages/WeatherData.razor
+++ b/samples/MinimalHtmxorApp/Components/Pages/WeatherData.razor
@@ -1,34 +1,30 @@
-@page "/weather/data"
-@* This shows how the page title can be updated during an htmx request. *@
Weather - data loaded
-
-
+
+
+ Date |
+ Temp. (C) |
+ Temp. (F) |
+ Summary |
+
+
+
+ @foreach (var forecast in forecasts)
+ {
- Date |
- Temp. (C) |
- Temp. (F) |
- Summary |
+ @forecast.Date.ToShortDateString() |
+ @forecast.TemperatureC |
+ @forecast.TemperatureF |
+ @forecast.Summary |
-
-
- @foreach (var forecast in forecasts)
- {
-
- @forecast.Date.ToShortDateString() |
- @forecast.TemperatureC |
- @forecast.TemperatureF |
- @forecast.Summary |
-
- }
-
-
+ }
+
@code {
private WeatherForecast[] forecasts = [];
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
- await Task.Delay(500);
+ await Task.Delay(2000);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering",
diff --git a/src/Htmxor/Components/HtmxLazy.cs b/src/Htmxor/Components/HtmxLazy.cs
new file mode 100644
index 0000000..e9c34a1
--- /dev/null
+++ b/src/Htmxor/Components/HtmxLazy.cs
@@ -0,0 +1,91 @@
+using Htmxor.Http;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Rendering;
+
+namespace Htmxor.Components;
+
+public sealed class HtmxLazy : ComponentBase, IConditionalOutputComponent
+{
+ [Inject]
+ private HtmxContext HtmxContext { get; set; } = default!;
+
+ [SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "False positive. This is a parameter.")]
+ [Parameter(CaptureUnmatchedValues = true)]
+ public IDictionary? AdditionalAttributes { get; set; }
+
+ ///
+ /// The content to render out when the htmx load request arrives.
+ ///
+ [Parameter, EditorRequired]
+ public required RenderFragment ChildContent { get; set; }
+
+ ///
+ /// The content to should be rendered while the htmx load request is in progress.
+ ///
+ [Parameter]
+ public RenderFragment? Loading { get; set; }
+
+ ///
+ /// The ID of the element that should render it's content inside.
+ /// This is also the ID that htmx will use to target the element to replace it's content.
+ ///
+ [Parameter, EditorRequired]
+ public required string Id { get; set; }
+
+ ///
+ /// The element type that should render
+ /// and into.
+ ///
+ /// Default is a div element.
+ [Parameter]
+ public string Element { get; set; } = "div";
+
+ protected override void OnParametersSet()
+ {
+ Element = string.IsNullOrWhiteSpace(Element) ? "div" : Element.Trim();
+ Id = string.IsNullOrWhiteSpace(Id) ? Id : Id.Trim();
+
+ // If the user manually added some of these attributes they are removed
+ // to avoid inconsistent behavior.
+ // The user should not set these attributes manually.
+ if (AdditionalAttributes is not null)
+ {
+ AdditionalAttributes.Remove(HtmxConstants.Attributes.Id);
+ AdditionalAttributes.Remove(HtmxConstants.Attributes.HxGet);
+ AdditionalAttributes.Remove(HtmxConstants.Attributes.HxTrigger);
+ AdditionalAttributes.Remove(HtmxConstants.Attributes.HxTarget);
+ AdditionalAttributes.Remove(HtmxConstants.Attributes.HxSwap);
+ }
+ }
+
+ protected override void BuildRenderTree([NotNull] RenderTreeBuilder builder)
+ {
+ var request = HtmxContext.Request;
+ if (request.IsFullPageRequest)
+ {
+ builder.OpenElement(1, Element);
+ builder.AddAttribute(2, HtmxConstants.Attributes.Id, Id);
+ builder.AddAttribute(3, HtmxConstants.Attributes.HxGet, HtmxContext.Request.Path);
+ builder.AddAttribute(4, HtmxConstants.Attributes.HxTrigger, HtmxConstants.Triggers.Load);
+ builder.AddAttribute(5, HtmxConstants.Attributes.HxTarget, $"#{Id}");
+ builder.AddAttribute(6, HtmxConstants.Attributes.HxSwap, HtmxConstants.SwapStyles.InnerHTML);
+ if (AdditionalAttributes is not null)
+ {
+ builder.AddMultipleAttributes(7, AdditionalAttributes);
+ }
+ if (Loading is not null)
+ {
+ builder.AddContent(8, Loading);
+ }
+ builder.CloseElement();
+ }
+ else if (request.Target == Id && request.Trigger == Id)
+ {
+ builder.AddContent(9, ChildContent);
+ }
+ }
+
+ bool IConditionalOutputComponent.ShouldOutput([NotNull] HtmxContext context, int directConditionalChildren, int conditionalChildren)
+ => context.Request.IsFullPageRequest
+ || (context.Request.Target == Id && context.Request.Trigger == Id);
+}