Reputation: 839
I am implementing an EventCallback
in a RenderFragment
parameter to enable an external close button to fire an event within a custom component:
public RenderFragment<IElementInfo>? ContentTemplate { get; set; }
Here is the interface:
public interface IElementInfo
{
EventCallback CloseCallback { get; }
}
And to use:
<ContentTemplate>
<button type="button"
@onclick="@(async () =>
await RaiseCallback(context.CloseCallback))">
Cancel
</button>
</ContentTemplate>
[edit] This is what the compiler generates for the above usage:
__builder.AddAttribute<ClosedEventArgs>(22, "Closed", RuntimeHelpers.TypeCheck<EventCallback<ClosedEventArgs>>(EventCallback.Factory.Create<ClosedEventArgs>((object) this, new Action(OnClosed))));
[end edit]
There is more code that is needed to wire it all up. As it is it works.
What I am trying to do is add a simple method for passing a title and a razor component to wire-up using the RenderTreeBuilder
manually. Here is a working version for a non-generic RenderFragment
:
[edit]
public void Show(string? title, Type contentType)
{
if (contentType.BaseType != typeof(ComponentBase))
throw new ArgumentException
($"{contentType.FullName} must be a Blazor Component");
RenderFragment content = x =>
{
x.OpenComponent(1, contentType);
x.CloseComponent();
};
Show(new Options
{
HeaderText = title,
ContentTemplate = content
});
}
public void Show(Options options)
{
Container container = new Container(options, _provider);
_contents.Add(container);
// notify new content
OnUpdated?.Invoke();
}
Sample usage:
Service.Show(
"Sample Title",
typeof(SampleRazorFile));
// where SampleRazorFile is a seperate razor file
[end edit]
Where I am stumped is with changing with the RenderTreeBuilder
for working with the generic RenderFragment<IElementInfo>
.
[edit]
The question is regarding RenderTreeBuilder
- can it return a Renderfragment<TValue>
type?
[end edit]
There is next to no information out there on this specific task. Has anyone done anything like this and have any suggestions?
Upvotes: 3
Views: 5269
Reputation: 839
I have concluded that you can not do it. I have added solutions to support both RenderFragment
and RenderFragment<TValue>
.
Actually, the solution is unexpectedly simple - cast the RenderFragment
to the generic type.
So, for my example above, the answer is as follows:
ContentTemplate = (RenderFragment<IElementInfo>)(_context =>
builder =>
{
builder.OpenElement(2, "div");
// trimmed...
builder.CloseElement();
}
);
How I found this solution was to enable emitting of Roslyn generated files. Add the following to the project file:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
Hope that this helps someone! 😊
Upvotes: 4
Reputation: 30177
I'm not absolutely sure of your context and if you have it right. You don't set the "context" externally, you set it within the component and pass it to the external RenderFragment block.
Here's some code for a component to display the WeatherForecast rows in FetchData
that demonstreates how to use the RenderFragment<>
in a RenderFragment block.
<h3>ListOfWeather</h3>
<table class="table">
<thead>
@this.HeaderTemplate
</thead>
@this.Rows
</table>
@code {
[Parameter] public List<WeatherForecast> ForecastList { get; set; } = new List<WeatherForecast>();
[Parameter] public RenderFragment<WeatherForecast>? ListRowTemplate { get; set; }
[Parameter] public RenderFragment<WeatherForecast>? HeaderTemplate { get; set; }
private RenderFragment Rows => (builder) =>
{
if (this.ListRowTemplate is null)
return;
builder.OpenElement(0, "tdbody");
foreach (var forecast in ForecastList)
builder.AddContent(2, this.ListRowTemplate(forecast));
builder.CloseElement();
};
}
For reference FetchData
looks like this:
@page "/fetchdata"
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
<ListOfWeather ForecastList="this.Forecasts">
<HeaderTemplate>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</HeaderTemplate>
<ListRowTemplate>
<tr>
<td>@context.Date.ToShortDateString()</td>
<td>@context.TemperatureC</td>
<td>@context.TemperatureF</td>
<td>@context.Summary</td>
<td></td>
</tr>
</ListRowTemplate>
</ListOfWeather>
@code {
private List<WeatherForecast> Forecasts = new List<WeatherForecast>();
[Inject] private WeatherForecastService? service { get; set; }
private WeatherForecastService Service => service!;
protected override async Task OnInitializedAsync()
{
this.Forecasts = await Service.GetForecastAsync(DateTime.Now);
}
}
Upvotes: 0