Si en Blazor estamos trabajando en crear un componente como plantilla, para poder ser utilizado en cualcuier parte de la aplicación, o sencillamente tenemos un coponente que tiene que recibir un fragmento de código HTML, pero nos gustaría que por defecto, si el programador decide no enviar nada, mostrara algún contenido, aqui te traigo la solución.
¿Qué es RenderFragment?
RenderFragment es un delegado el cual nos permite mostrar código HTML que se recibe como parámetro de un componente a otro. O por lo menos, esta sería la definición más simple que se me ocurre por la experiencia que tengo hasta la fecha, recordar que apenas estoy empezando en Blazor con sólo 3 o 4 meses investigando esta nueva tecnología de Microsoft a la cual le veo mucho futuro, por lo menos en lo que a mí me corresponde.
Exponiendo un ejemplo
Sin una necesidad es difícil de entender muchas veces lo que se presenta, por lo que vamos a exponer un ejemplo de uso. Imaginemos un componente para mostrar los datos de una tabla, en el que podemos tener 3 posibles estados:
1. Cargando
2. Sin datos
3. Mostrar los datos
Crearemos un componente el cual va a recibir como parámetros esos 3 estados con su correspondiente HTML. Pero el programador, que es un poco vago, Sólo pasa al componente el fragmento de código (RenderFragment). Nuestro componente genérico llamado TemplateList podría tener un aspecto como este:
@typeparam TItem
@if (Items is null)
{
@Loading
}
else if (Items.Any())
{
@foreach (TItem item in Items)
{
@Item(item)
}
}
else
{
@Empty
}
@code{
[Parameter]
public RenderFragment Loading { get; set; }
[Parameter]
public RenderFragment Empty { get; set; }
[Parameter]
public RenderFragment Item { get; set; }
List Items;
}
Como puedes objervar el componente va a recibir 3 fragmentos de código para cada uno de los estados. Es un ejemplo muy básico, pero espero que quede claro que es lo que estamos tratando de presentar. Para utilizarlo inicialmente sería algo como cargar una lista con los elementos y mostrarlos utilizando el componente. Y tenemos que pasar los fragmentos de código con los tres estados y la estructura de cómo vamos a mostrarlos datos. Quedaría algo como esto:
@page "/milist"
Cargando...
No hay nada que ver
< Volver!
@Item.CreatedTime.ToLongDateString()
@code{
[Inject]
public OrdersClient Client { get; set; }
List Items;
protected override async Task OnInitializedAsync()
{
Items = await Client.GetOrders();
}
}
Esta sería la forma complete de utilización de nuestro componente, pero que pasa si el programador es un poco vago y sólo quiero hacer algo como esto:
@page "/milist"
@Item.CreatedTime.ToLongDateString()
@code{
[Inject]
public OrdersClient Client { get; set; }
List Items;
protected override async Task OnInitializedAsync()
{
Items = await Client.GetOrders();
}
}
Entonces, para ser un "buen" programador, lo lógico sería que siempre tuvieramos un contenido por defecto. Y aquí es dónde nos vamos a enfocar.
Lo importante en nuestro componente
Un pequeño cambio que tenemos uqe hacer en nuestro componente es agregar la lógica que se encargará de controlar si tenemos inicializado el RenderFragment y entonces si nos viene nulo asignarle por defecto algo a mostrar. Estas líneas en nuestro caso sería algo como lo que sigue:
protected override void OnParametersSet()
{
if (Loading is null)
{
Loading = builder =>
{
builder.OpenElement(1, "p");
builder.AddContent(1, "Cargando...");
builder.CloseElement();
};
}
if (Empty is null)
{
Empty = builder =>
{
builder.OpenElement(0, "p");
builder.AddContent(0, "Nada a mostrar!");
builder.CloseElement();
builder.OpenElement(1, "a");
builder.AddAttribute(1, "href", "");
builder.AddContent(1, "< Volver");
builder.CloseElement();
};
}
}
Para que nos entendamos
Por hacer una comparación a cómo se hace desde JAVASCRIPT la creación de elementos en el DOM dinámicamente voy a poner un poco de código en JAVASCRIPT. Comparemos el fragmento de código del Loading:
var esperando = document.createElement('p');
esperando.innerText = 'Cargando...';
document.body.appendChild(esperando);
Por lo tanto, desde C#, tenemos que hacer algo parecido:
1. Crear el elemento con .OpenElement
2. Asignar el contenido con .AddContent
3. Agregar el documento con .CloseElement
Aclaraciones
OpenElement necesita dos parámetros, un identificador, en este caso 0, y el elemento HTML que vamos a agregar, en este caso P. Como se puede ver en el ejemplo, para el fragmento de Empty tiene dos elementos, por lo que para identificar cada elemento tenemos que asignar un nuevo número. Inicialmente se puede indicar cualquier número, pero lo más lógico es seguir una incrementación desde cero.
Siempre debes de poner los atributos antes que el contenido, como se muestra también en el ejemplo.
Para agregar contenido usamos AddContent esto es el equivalente a innetText en javascript, pero para utilizar, algo parecido a innerHtml y mostrar directamente contenido utilizaremos AddMarkupContent.
Podemos utilizar un Dictionary junto con AddMultipleAttribute para poder crear directamente todos los atributos en una sola llamada.
Destacar, que en mis pruebas, el mejor momento para poder controlar e insertar este tipo de código es en los eventos OnInitiliza o OnParametersSet, en su forma no asíncrona. Ya que si utilizas su versión asíncrona muchas veces no se llega a ver el contenido.
Podrías tener incluso un método que agrtege contenido HTML directamente en la página, devolviendo un objeto RenderFragment y teniendo una propiedad que fuera tambien RenderFragment, evidentemente.
Happy codding