Radzen Tree checkboxes: can't change checkbox state

Hi, I'm investigating Radzen Tree checkboxes in order to apply to on my project but I'm having trouble with one thing. Actually I've searched this forum about my issue and found this https://forum.radzen.com/t/radzentree-with-checkboxes/8897/2 topic, but there is no answer and I decided to open new topic, may be someone help me on it.

So, my intention is that I want to save filtering value on current page and after walking on other pages when I come back to current page, I'm assigning saved value to CheckedValues and, in background CheckedValues has a data but checkboxes state doesn't change, I mean checkbox don't checked.

For example, I'll explain this issue over the demo: Let's assume we have the Categories tree.

    IEnumerable<Category> categories;
    IEnumerable<object> checkedValues;

    IEnumerable<object> CheckedValues
    {
        get => checkedValues;
        set
        {
            checkedValues = value;
        }
    }

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();

        categories = dbContext.Categories.Include(c => c.Products);
    }

And I've the saved tree value after filtering, like this:

IEnumerable<object> SavedValues {get; set;}
void SaveFilter()
{
   this.SavedValues = this.CheckedValues;
}

And I'm navigating to other pages and coming back to my current page, and waiting for to assign my saved value to CheckedValues, after that I get the value in the background but checkboxes doesn't checked.

void LoadFilter()
{
this.CheckedValues = this.SavedValues;
}

Maybe I'm doing that on the wrong way, if yes so please correct me.

Note: I'm saving value on my browser Session Storage

Thanks.

1 Like

Hola
Te paso este código, el componente Roles llama al componente FormularioRol que contiene un árbol de permisos, el árbol de permisos implementa checkboxes, hay mucho código extra que puedes ignorar, espero que te sirva de algo...

La magia reside en que el atributo CheckedValues del árbol debe ser un subconjunto del atributo Data.

Si necesitas más ayuda, no dudes en comentar, Saludos

@using Apps.Siga.Components
<PageTitle>@pageTitle</PageTitle>

@attribute [Authorize]
@implements IDisposable
@namespace Apps.Siga.Pages
@inherits ProviderInterceptor
@page "/siga/administracion/roles"

<RadzenCard Style="min-height: 720px">
	<div class="row">
		<div class="col-md-5">
			<ArbolRoles Items="@items" OnChangeActionAsync="@GetSingleRolAsync" />
		</div>
		<div class="col-md-7">
			<div class="row">
				<div class="col-12 text-end">
					<RadzenButton Click=@(args => LimpiarForm()) Text="Nuevo" Icon="add" ButtonStyle="ButtonStyle.Primary" />
				</div>
				<div class="col-12">

					<RadzenTemplateForm TItem="VmAddOrUpdateRol" Data=@model Submit="@OnSubmitFormAsync" Visible="@showForm">
						<RadzenFieldset Text="Detalles">
							<div class="row">
								<div class="col-md-9">
									<RadzenLabel Text="Nombre" Component="Nombre" />
									<RadzenTextBox Name="Nombre" @bind-Value=@model.Nombre Style="width: 100%"
																 MouseEnter="@(args => ShowTooltip(args, "Nombre con el cual será identificado el rol."))" />
									<RadzenRequiredValidator Component="Nombre" Text="Debes ingresar el nombre del rol" />
								</div>
								<div class="col-md-3">
									<RadzenLabel Text="Padre" Component="PadreId" />
									<RadzenNumeric TValue="int?" Name="PadreId" @bind-Value=@model.PadreId Style="width: 100%"
																 MouseEnter="@(args => ShowTooltip(args, "Id del identificador contenedor padre del permiso"))" />
								</div>
							</div>

							<div class="row">
								<div class="col-md-12">
									<RadzenLabel Text="Descripción" Component="Descripcion" />
									<RadzenTextArea Name="Descripcion" @bind-Value=@model.Descripcion Style="width: 100%" Rows="2"
																	MouseEnter="@(args => ShowTooltip(args, "Breve descripción del rol y/o sus funciones."))" />
									<RadzenRequiredValidator Component="Descripcion" Text="Debes ingresar la descripción del rol" />
								</div>
							</div>

							<div class="row">
								<div class="col-md-6">
									<div class="row">
										<div class="col-md-6">
											<RadzenLabel Text="Fecha Inicial" Component="FechaInicial" />
											<RadzenDatePicker Name="FechaInicial" @bind-Value=@model.FechaInicial Style="width: 100%" DateFormat="dd-MMM-yyyy"
																				MouseEnter="@(args => ShowTooltip(args, "Inicio de operaciones, vacío para omitir restricciones de tiempo"))" />
										</div>
										<div class="col-md-6">
											<RadzenLabel Text="Fecha Final" Component="FechaFinal" />
											<RadzenDatePicker Name="FechaFinal" @bind-Value=@model.FechaFinal Style="width: 100%" DateFormat="dd-MMM-yyyy"
																				MouseEnter="@(args => ShowTooltip(args, "Fin de operaciones, vacío para omitir restricciones de tiempo"))" />
										</div>
										<div class="col-md-12 mt-3">
											<RadzenSwitch @bind-Value=@model.AddOrUpdateCategorias
																		Change="@((args) => {model.CategoriasSeleccionadas = categoriasIniciales;})" />
											<span class="form-text">Activa este switch si deseas actualizar las categorías del rol</span>
											<RadzenListBox class="w-100"
																		 Multiple="true"
																		 Data=@categorias
																		 ValueProperty="Id"
																		 Style="height:200px"
																		 AllowFiltering="true"
																		 TextProperty="Nombre"
																		 Name="ComboCategorias"
																		 Disabled=@(!model.AddOrUpdateCategorias)
																		 @bind-Value=@model.CategoriasSeleccionadas
																		 Placeholder="Categorias"
																		 FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" />
										</div>
									</div>
								</div>

								<div class="col-md-6">
									<div class="row">
										<div class="col-md-12 mt-4 mb-2">
											<RadzenSwitch @bind-Value=@model.IsInDevelopedMode />
											<span class="form-text">Activa el Modo Desarrollador para que este rol no este disponible en etapa de producción</span>
										</div>
										<div class="col-md-12 mt-3">
											<RadzenSwitch @bind-Value=@model.AddOrUpdatePermisos
																		Change=@ResetCheckedPermisos />
											<span class="form-text">Activa este switch si deseas actualizar los permisos del rol</span>
										</div>
										<div class="col-md-12">
											<RadzenTree Expand=@OnExpand
																	AllowCheckBoxes="true"
																	AllowCheckParents="false"
																	AllowCheckChildren="false"
																	Data=@permisosPrincipales
																	CheckedValues="@checkedValues"
																	CheckedValuesChanged="@(value => OnChangeCheckedValues(value))">
												<RadzenTreeLevel Text=@GetTextForNode
																				 Template="@PermisoTemplate"
																				 Expanded="@(value => (value as VmHojaPermiso).HasChildren)" />
											</RadzenTree>
										</div>
									</div>
								</div>
							</div>

							<div class="row mt-3">
								<div class="col-md-12 text-end">
									<RadzenButton ButtonType="ButtonType.Reset" Text="Cancelar" Icon="cancel"
																Click="@(() => {showForm = false;})"
																ButtonStyle="ButtonStyle.Light"></RadzenButton>
									<RadzenButton Text="Eliminar"
																Icon="delete_outline"
																Visible="@(idRol != 0)"
																Click=@ShowDeleteDialogAsync
																ButtonStyle="ButtonStyle.Secondary" />
									<RadzenButton ButtonType="ButtonType.Submit" Text="Guardar" Icon="save" ButtonStyle="ButtonStyle.Primary"></RadzenButton>
								</div>
							</div>
						</RadzenFieldset>
					</RadzenTemplateForm>

				</div>
			</div>
		</div>
	</div>
</RadzenCard>

@code {
	[Inject] protected DialogService DialogService { get; set; }
	[Inject] protected IRolesProvider RolesProvider { get; set; }
	[Inject] protected TooltipService tooltipService { get; set; }
	[Inject] protected AppStateContainer StateContainer { get; set; }
	[Inject] protected IPermisosProvider PermisosProvider { get; set; }
	[Inject] protected ICategoriasProvider CategoriasProvider { get; set; }
	[Inject] protected NotificationService NotificationService { get; set; }

	private int idRol = 0;
	private bool showForm = false;
	private string pageTitle = "Administración de Roles";
	private VmAddOrUpdateRol model { get; set; } = new();
	private IEnumerable<VmHojaRol> items = new Collection<VmHojaRol>();
	private IEnumerable<object> checkedValues = new Collection<object>();
	private IEnumerable<int> categoriasIniciales = new Collection<int>();
	private IEnumerable<VmHojaPermiso> permisos = new Collection<VmHojaPermiso>();
	private IEnumerable<VmComboCategoria> categorias = new Collection<VmComboCategoria>();
	private IEnumerable<VmHojaPermiso> permisosIniciales = new Collection<VmHojaPermiso>();
	private IEnumerable<VmHojaPermiso> permisosPrincipales = new Collection<VmHojaPermiso>();

	protected override void OnInitialized() {
		StateContainer.UpdatePageTitle(pageTitle);
		base.OnInitialized();
	}

	protected override async Task OnInitializedAsync() {
		try {
			items = await RolesProvider.GetAllAsync();
			permisos = await PermisosProvider.GetAllAsync();
			categorias = await CategoriasProvider.GetComboAsync();

			permisosPrincipales = permisos?
				.Where(x => x.PadreId is null)
				.ToList();
		} catch (Exception e) {
			NotificationService.Notify(new NotificationMessage {
						Severity = NotificationSeverity.Error,
						Summary = "Error",
						Detail = e.Message,
						Duration = 4000
					});
		}
		await base.OnInitializedAsync();
	}

	#region Funcionalidad del árbol de roles
	public async Task GetSingleRolAsync(int id) {
		try {
			idRol = id;
			showForm = true;
			model = Mapper.Map(await RolesProvider.GetAsync(idRol))
				.ToANew<VmAddOrUpdateRol>();
			model.PermisosSeleccionados = model.PermisosSeleccionados ?? new Collection<int>();
			model.CategoriasSeleccionadas = model.CategoriasSeleccionadas ?? new Collection<int>();

			permisosIniciales = permisos
				.Where(x => model.PermisosSeleccionados.Contains(x.Id))
				.ToList()
				.Clone();
			categoriasIniciales = model.CategoriasSeleccionadas.ToArray();

			checkedValues = permisos?
			.Where(x => model.PermisosSeleccionados.Contains(x.Id));
		} catch (Exception e) {
			NotificationService.Notify(new NotificationMessage {
						Severity = NotificationSeverity.Error,
						Summary = "Error",
						Detail = e.Message,
						Duration = 4000
					});
		}
	}
	#endregion

	#region Funcionalidad del formulario de rol
	void ShowTooltip(ElementReference elementReference, string text = null) =>
		tooltipService.Open(elementReference, text);

	private void LimpiarForm() {
		idRol = 0;
		showForm = true;
		model = new VmAddOrUpdateRol();
		model.PermisosSeleccionados = new Collection<int>();
		model.CategoriasSeleccionadas = new Collection<int>();

		permisosIniciales = permisos
		.Where(x => model.PermisosSeleccionados.Contains(x.Id))
		.ToList()
		.Clone();
		categoriasIniciales = model.CategoriasSeleccionadas.ToArray();

		checkedValues = permisos.Where(x => model.PermisosSeleccionados.Contains(x.Id));
	}

	public async Task OnSubmitFormAsync() {
		try {
			if (idRol == 0)
				await RolesProvider.AddAsync(model);
			else
				await RolesProvider.UpdateAsync(model, idRol);
			items = await RolesProvider.GetAllAsync();

			NotificationService.Notify(new NotificationMessage {
						Severity = NotificationSeverity.Success,
						Summary = "Éxito",
						Detail = "Registro actualizado",
						Duration = 4000
					});
		} catch (Exception e) {
			NotificationService.Notify(new NotificationMessage {
						Severity = NotificationSeverity.Error,
						Summary = "Error",
						Detail = e.Message,
						Duration = 4000
					});
		}
	}

	async Task ShowDeleteDialogAsync() {
		var result = await DialogService.OpenAsync("Confirmación", ds =>
		@<div>
			<p class="mb-4">Estás a punto de borrar el elemento seleccionado ¿Deseas continuar?</p>
			<div class="row">
				<div class="col text-end">
					<RadzenButton ButtonStyle="ButtonStyle.Light" Text="No" Click="() => ds.Close(false)" />
					<RadzenButton ButtonStyle="ButtonStyle.Secondary" Text="Si" Click="() => OnDeleteAsync()" />
				</div>
			</div>
		</div>
	);
	}

	public async Task OnDeleteAsync() {
		try {
			if (idRol == 0)
				throw new ArgumentException("No se a seleccionado un registro");
			else
				await RolesProvider.DeleteAsync(idRol);
			items = await RolesProvider.GetAllAsync();
			showForm = false;
			DialogService.Close();

			NotificationService.Notify(new NotificationMessage {
						Severity = NotificationSeverity.Success,
						Summary = "Éxito",
						Detail = "Registro eliminado con éxito",
						Duration = 4000
					});
		} catch (Exception e) {
			NotificationService.Notify(new NotificationMessage {
						Severity = NotificationSeverity.Error,
						Summary = "Error",
						Detail = e.Message,
						Duration = 4000
					});
		}
	}
	#endregion

	#region Funcionalidad del árbol de permisos
	void OnExpand(TreeExpandEventArgs args) {
		var permiso = args.Value as VmHojaPermiso;
		var children = GetChildren(permiso.Id);
		args.Children.Data = children;
		args.Children.Text = GetTextForNode;
		args.Children.HasChildren = (data) => ((VmHojaPermiso)data).HasChildren;
		args.Children.Template = PermisoTemplate;
	}

	RenderFragment<RadzenTreeItem> PermisoTemplate = (context) => builder => {
		bool isParent = context.HasChildren;
		builder.OpenComponent<RadzenIcon>(0);
		builder.AddAttribute(1, "Icon", isParent ? "account_tree" : "label");
		builder.CloseComponent();
		builder.AddContent(2, context.Text);
	};

	string GetTextForNode(object data) {
		VmHojaPermiso _data = (VmHojaPermiso)data;
		return _data.Descripcion;
	}

	public IEnumerable<VmHojaPermiso> GetChildren(int idPadre) =>
		permisos
			.Where(x => x.PadreId == idPadre)
			.OrderBy(x => x.IndiceDeOrdenamiento)
			.ToList();

	private void OnChangeCheckedValues(IEnumerable<object> value) {
		IEnumerable<int> checkeds = ((IEnumerable)value).Cast<VmHojaPermiso>()
		.Select(x => x.Id)
		.ToList();
		model.PermisosSeleccionados = checkeds;
	}

	private void ResetCheckedPermisos() {
		IEnumerable<int> idsPermisos = ((IEnumerable)permisosIniciales).Cast<VmHojaPermiso>()
		.Select(x => x.Id)
		.ToList();
		model.PermisosSeleccionados = idsPermisos;
	}
	#endregion

}
@namespace Apps.Siga.Components

<RadzenTemplateForm TItem="VmAddOrUpdateRol" Data=@Rol Submit="@InvokeOnSubmitAsync" Visible="@ShowForm">
	<RadzenFieldset Text="Detalles">
		<div class="row">
			<div class="col-md-9">
				<RadzenLabel Text="Nombre" Component="Nombre" />
				<RadzenTextBox Name="Nombre" @bind-Value=@Rol.Nombre Style="width: 100%"
											 MouseEnter="@(args => ShowTooltip(args, "Nombre con el cual será identificado el rol."))" />
				<RadzenRequiredValidator Component="Nombre" Text="Debes ingresar el nombre del rol" />
			</div>
			<div class="col-md-3">
				<RadzenLabel Text="Padre" Component="PadreId" />
				<RadzenNumeric TValue="int?" Name="PadreId" @bind-Value=@Rol.PadreId Style="width: 100%"
											 MouseEnter="@(args => ShowTooltip(args, "Id del identificador contenedor padre del permiso"))" />
			</div>
		</div>

		<div class="row">
			<div class="col-md-12">
				<RadzenLabel Text="Descripción" Component="Descripcion" />
				<RadzenTextArea Name="Descripcion" @bind-Value=@Rol.Descripcion Style="width: 100%" Rows="2"
												MouseEnter="@(args => ShowTooltip(args, "Breve descripción del rol y/o sus funciones."))" />
				<RadzenRequiredValidator Component="Descripcion" Text="Debes ingresar la descripción del rol" />
			</div>
		</div>

		<div class="row">
			<div class="col-md-6">
				<div class="row">
					<div class="col-md-6">
						<RadzenLabel Text="Fecha Inicial" Component="FechaInicial" />
						<RadzenDatePicker Name="FechaInicial" @bind-Value=@Rol.FechaInicial Style="width: 100%" DateFormat="dd-MMM-yyyy"
															MouseEnter="@(args => ShowTooltip(args, "Inicio de operaciones, vacío para omitir restricciones de tiempo"))" />
					</div>
					<div class="col-md-6">
						<RadzenLabel Text="Fecha Final" Component="FechaFinal" />
						<RadzenDatePicker Name="FechaFinal" @bind-Value=@Rol.FechaFinal Style="width: 100%" DateFormat="dd-MMM-yyyy"
															MouseEnter="@(args => ShowTooltip(args, "Fin de operaciones, vacío para omitir restricciones de tiempo"))" />
					</div>
					<div class="col-md-12 mt-3">
						<RadzenSwitch @bind-Value=@Rol.AddOrUpdateCategorias
													Change="@((args) => {Rol.CategoriasSeleccionadas = categoriasIniciales;})" />
						<span class="form-text">Activa este switch si deseas actualizar las categorías del rol</span>
						<RadzenListBox class="w-100"
													 Multiple="true"
													 Data=@Categorias
													 ValueProperty="Id"
													 Style="height:200px"
													 AllowFiltering="true"
													 TextProperty="Nombre"
													 Name="ComboCategorias"
													 Disabled=@(!Rol.AddOrUpdateCategorias)
													 @bind-Value=@Rol.CategoriasSeleccionadas
													 Placeholder="Categorias"
													 FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" />
					</div>
				</div>
			</div>

			<div class="col-md-6">
				<div class="row">
					<div class="col-md-12 mt-4 mb-2">
						<RadzenSwitch @bind-Value=@Rol.IsInDevelopedMode />
						<span class="form-text">Activa el Modo Desarrollador para que este rol no este disponible en etapa de producción</span>
					</div>
					<div class="col-md-12 mt-3">
						<RadzenSwitch @bind-Value=@Rol.AddOrUpdatePermisos
													Change="@((args) => {checkedValues = permisosIniciales;})" />
						<span class="form-text">Activa este switch si deseas actualizar los permisos del rol</span>
					</div>
					<div class="col-md-12">
						<RadzenTree Expand=@OnExpand
												AllowCheckBoxes="true"
												AllowCheckParents="false"
												AllowCheckChildren="false"
												Data=@PermisosPrincipales
												CheckedValues="@checkedValues"
												CheckedValuesChanged="@(value => InvokeOnChangeCheckedValues(value))">
							<RadzenTreeLevel Text=@GetTextForNode
															 Template="@PermisoTemplate"
															 Expanded="@(value => (value as VmHojaPermiso).HasChildren)" />
						</RadzenTree>
					</div>
				</div>
			</div>
		</div>

		<div class="row mt-3">
			<div class="col-md-12 text-end">
				<RadzenButton ButtonType="ButtonType.Reset" Text="Cancelar" Icon="cancel"
											Click="@(() => {ShowForm = false;})"
											ButtonStyle="ButtonStyle.Light"></RadzenButton>
				<RadzenButton Text="Eliminar"
											Icon="delete_outline"
											Visible="@ShowDeleteButton"
											Click=@InvokeOnDeleteAsync
											ButtonStyle="ButtonStyle.Secondary" />
				<RadzenButton ButtonType="ButtonType.Submit" Text="Guardar" Icon="save" ButtonStyle="ButtonStyle.Primary"></RadzenButton>
			</div>
		</div>
	</RadzenFieldset>
</RadzenTemplateForm>

@code {
	[Inject] TooltipService tooltipService { get; set; }

	[Parameter] public bool ShowForm { get; set; } = false;
	[Parameter] public EventCallback OnSubmitAsync { get; set; }
	[Parameter] public EventCallback OnDeleteAsync { get; set; }
	[Parameter] public VmAddOrUpdateRol Rol { get; set; } = new();
	[Parameter] public bool ShowDeleteButton { get; set; } = false;
	[Parameter] public IEnumerable<VmHojaPermiso> Permisos { get; set; }
	[Parameter] public IEnumerable<VmComboCategoria> Categorias { get; set; }
	[Parameter] public IEnumerable<VmHojaPermiso> PermisosPrincipales { get; set; }
	[Parameter] public EventCallback<IEnumerable<int>> OnChangeCheckedValues { get; set; }

	private IEnumerable<object> checkedValues = new Collection<object>();
	private IEnumerable<int> categoriasIniciales = new Collection<int>();
	private IEnumerable<VmHojaPermiso> permisosIniciales = new Collection<VmHojaPermiso>();

	protected override Task OnParametersSetAsync() {
		/*categoriasIniciales = Rol?.CategoriasSeleccionadas
		?? new Collection<int>();

		permisosIniciales = Permisos?
			.Where(x => Rol.PermisosSeleccionados.Contains(x.Id))
			?? new Collection<VmHojaPermiso>();*/

		checkedValues = Permisos?
			.Where(x => Rol.PermisosSeleccionados.Contains(x.Id));

		return base.OnParametersSetAsync();
	}

	private async Task InvokeOnDeleteAsync() =>
	await OnDeleteAsync.InvokeAsync();

	private async Task InvokeOnSubmitAsync() =>
		await OnSubmitAsync.InvokeAsync();

	void ShowTooltip(ElementReference elementReference, string text = null) =>
		tooltipService.Open(elementReference, text);

	#region Funcionalidad del árbol de permisos
	void OnExpand(TreeExpandEventArgs args) {
		var permiso = args.Value as VmHojaPermiso;
		var children = GetChildren(permiso.Id);
		args.Children.Data = children;
		args.Children.Text = GetTextForNode;
		args.Children.HasChildren = (data) => ((VmHojaPermiso)data).HasChildren;
		args.Children.Template = PermisoTemplate;
	}

	RenderFragment<RadzenTreeItem> PermisoTemplate = (context) => builder => {
		bool isParent = context.HasChildren;
		builder.OpenComponent<RadzenIcon>(0);
		builder.AddAttribute(1, "Icon", isParent ? "account_tree" : "label");
		builder.CloseComponent();
		builder.AddContent(2, context.Text);
	};

	string GetTextForNode(object data) {
		VmHojaPermiso _data = (VmHojaPermiso)data;
		return _data.Descripcion;
	}

	public IEnumerable<VmHojaPermiso> GetChildren(int idPadre) =>
		Permisos
			.Where(x => x.PadreId == idPadre)
			.OrderBy(x => x.IndiceDeOrdenamiento)
			.ToList();

	private void InvokeOnChangeCheckedValues(IEnumerable<object> value) {
		IEnumerable<int> checkeds = ((IEnumerable)value).Cast<VmHojaPermiso>()
		.Select(x => x.Id)
		.ToList();
		OnChangeCheckedValues.InvokeAsync(checkeds);
	}
	#endregion

}
1 Like

Thanks for your reply, but my issue is that when I'm trying to manual assign value to CheckedValues then checkboxes state don't change.

Bueno
Mira que me costó mucho trabajo trabajar con checkboxes en los árboles.
Pero, como te comento, para que te funcione la asignación manual del atributo CheckedValues, el valor que le asignas debe ser un subconjunto del valor ligado al atributo Data, si no es así, jamás podrás asignar valores a CheckedValues.

Ejemplo:

var customers = Customers.ToList();
var checkeds = customers.Where(x => x.Status == 'Active');

<RadzenTree Data=@customers CheckedValues="@checkeds">
...
</RadzenTree>

// Actualización manual de CheckedValues
var checkeds = customers.Where(x => x.Country == 'USA');

Revisa los métodos OnInitializedAsync, GetSingleRolAsync y LimpiarForm del primero componente del comentario anterior, mira que su valor siempre debe ser un subconjunto de la variable "permisos" que es la variable que está ligada a el atributo Data del árbol de permisos.

Algo a tomar en cuenta es el hecho de que la copia de colecciones se hace mediante referencia y no por valor, es por eso que en algunos casos se tienen que clonar los algunas colecciones.

Quedo atento de tus comentarios.