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.

- - -
Loading 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 - - + + + + + + + + + + @foreach (var forecast in forecasts) + { - - - - + + + + - - - @foreach (var forecast in forecasts) - { - - - - - - - } - -
DateTemp. (C)Temp. (F)Summary
DateTemp. (C)Temp. (F)Summary@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
@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); +}