Open borisdj opened 3 years ago
I do this type of dropdown using RenderCrudComponentAs<>
It displays an <input>
field where you enter a SearchTerm to populate the dropdown
searching for values contaings SearchTerm.
If you want I can submit my code here
Snipped with example would be nice, thx.
To use my component I insert in class ColumnCollections
something like this:
private static Func<BLibro, string?> exprIdTown = (s) => { return s.IdTownNavigation == null ? "" : s.IdTownNavigation?.Nome; };
(..)
c.Add(o => o.IdTown).Titled("TownRlst").RenderValueAs(o => o.IdTown == null ? "" : o.IdTownNavigation.Nome)
.RenderCrudComponentAs<RemoteDropDownComponent<BLibro, BTown>>(("IdTown", exprIdTown), false);
This is the component:
using GridBlazor;
using GridShared;
using GridShared.Columns;
using HayaiB;
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Bookstore.Pages.Components
{
public partial class RemoteDropDownComponent<T, V> : ICustomGridComponent<T> where V : class, IGenericModel
{
[Parameter]
public T Item { get; set; }
[Parameter]
public CGrid<T> Grid { get; set; }
[Parameter]
public object Object { get; set; }
[Inject]
private IGenericService<V> RlstService { get; set; }
public IEnumerable<SelectItem> SelectedItems;
public string selectedValue;
private string searchTerm;
public bool allowChange;
private string _RlstField;
private string _message;
private Func<T, string?> _expr;
private ModelExtension Model { get; set; }
public string SearchTerm
{
get { return searchTerm; }
set { searchTerm = value; OnSearchChange(); }
}
protected override void OnParametersSet()
{
if (Object.GetType() == typeof((string, Func<T, string?>)))
{
(_RlstField, _expr) = ((string, Func<T, string?>))Object;
try
{
Model = new ModelExtension(typeof(T), Item);
selectedValue = Model.GetValue($"{_RlstField}")?.ToString() ?? "";
SearchTerm = _expr(Item) ?? "";
}
catch (Exception e)
{
throw new Exception("ERROR RemoteDropDownComponent must have (string[], Func<T, string?>) parameters and GetSelectedItems");
}
}
string gridState = Grid.GetState();
allowChange = Grid.Mode == GridMode.Update || Grid.Mode == GridMode.Create;
if (!allowChange)
{
selectedValue = String.Concat(Model.GetValue($"{_RlstField}")?.ToString(), " - ", _expr(Item));
}
}
public void OnSearchChange()
{
SelectedItems = RlstService.GetSelectedItems(searchTerm);
_message = $"selezionare... [{SelectedItems.Count()}]";
}
private void ChangeValue(ChangeEventArgs e)
{
var value = e?.Value?.ToString();
Model.SetValue($"{_RlstField}", value);
}
}
}
I developed and use here a ModelExtension
class that is:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace HayaiB
{
public class ModelExtension
{
public Type ModelType { get; }
public object Item { get; set; }
public ModelExtension(Type type, object item)
{
ModelType = type;
Item = item;
}
public void SetValue(string columnName, object? value)
{
PropertyInfo[] properties = ModelType.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.Name == columnName)
{
if (Int32.TryParse((string?)value, out int ivalue))
{
property.SetValue(Item, ivalue);
}
else
{
property.SetValue(Item, value);
}
return;
}
}
}
public object? GetValue(string columnName)
{
PropertyInfo[] properties = ModelType.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.Name == columnName)
{
return property.GetValue(Item);
}
}
return null;
}
public List<string> GetKeyNames()
{
PropertyInfo[] properties = ModelType.GetProperties();
List<string> ret = new ();
foreach (PropertyInfo property in properties)
{
if (Attribute.GetCustomAttribute(property, typeof(KeyAttribute)) is KeyAttribute)
{
ret.Add(property.Name);
}
}
return ret;
}
public List<object?> GetKeyValues()
{
PropertyInfo[] properties = ModelType.GetProperties();
List<object?> ret = new ();
foreach (PropertyInfo property in properties)
{
if (Attribute.GetCustomAttribute(property, typeof(KeyAttribute)) is KeyAttribute)
{
ret.Add(GetValue(property.Name));
}
}
return ret;
}
}
}
I wasn't able to make this work, so now trying again.
I've made a Demo project, created new BlazorServer simple app, with entities Order and Customer and their pages with CrudGrid.
You can downloaded or fork it from here: GridBlazorDropDown
Before starting the app Db should be created from migration with command update-database
, and on first run Seed method will input data into Customer table.
I have also added your classes ModelExtension
and RemoteDropDownComponent
but I am not sure how to connected them properly. Should this component make single remote combo or does it work with one text field and another connected combo?
In Order.razor. there is:
c.Add(o => o.CustomerId, true).SetSelectField(true, o => o.Customer.Name, customerService.Get);
c.Add(o => o.Customer.Name).SetCrudHidden(true);
// and commented
//c.Add(o => o.CustomerId).Titled("Customer")
// .RenderValueAs(o => o.Customer.Name)
// .RenderCrudComponentAs<RemoteDropDownComponent<Customer, Customer>>(("CustomerId", exprIdValue), false);
Could you take a look and applied it to this example. Thx in advance.
sorry, indeed I forget this RemoteDropDownComponent.razor
@typeparam T
@typeparam V
<style>
.row > div.col-md-10 > div.card.panel.panel-default { border: none }
.row > div.col-md-10 > div.card.panel.panel-default > div.card-body.panel-body { padding: 0px; border: none }
</style>
@if (allowChange)
{
<div class="row col-md-5">Find item starting by: <input class="form-control " @bind="SearchTerm" /></div>
<div class="row col-md-5">
<select id="Select_@_RlstField" name="@_RlstField" class="form-control" value="@selectedValue" @onchange="(e) => ChangeValue(e)">
<option value="">@_message</option>
@foreach (var selectItem in SelectedItems)
{
if (selectItem.Value == selectedValue)
{
<option value="@selectItem.Value" selected="selected">@selectItem.Title</option>
}
else
{
<option value="@selectItem.Value">@selectItem.Title</option>
}
}
</select>
</div>
}
else
{
<input id="@_RlstField" name="@_RlstField" class="form-control" value="@selectedValue" disabled="disabled" />
}
and that is needed a new method in CustomerService.cs:
// used by RemoteDropDownComponent
public IEnumerable<SelectItem> GetSelectedItems(string searchTerm)
{
using var context = new ApplicationDbContext(ApplicationDbContext.GetOptions());
return context.Customers
.Where(o => o.Name.StartsWith(searchTerm)).Take(100)
.Select(r => new SelectItem(r.CustomerId.ToString(), r.Name))
.ToList();
}
Still struggling to connect this. Any change you could update working example in the linked Demo.
can't write on your repo see my fork updated here: https://github.com/faina09/GridBlazorDropDown/tree/master
I have managed to make it work.
Big Thanks for the component.
Linked repository GridBlazorDropDown is now updated with working example, in case someone else would need it.
Services are also changed so that they inherit IGenericService
to easily use generic component.
@faina09 one more Q.
I now have DropDown Remote Lookup with (Key,Value) not being (Int/String) but (Guid/String).
And the is issue that when on Form Adding new record, on component RemoteDropDownComponent.razor.cs
in method:
private void ChangeValue(ChangeEventArgs e)
{
var value = e?.Value?.ToString();
Model.SetValue($"{_RlstField}", value);
}
value
is not Guid but comes as int (seems it is sequence number of select list) and can not be parsed into Guid.
For example if having 2 element in List: [('CBB291BC-38CF-41B4-D753-08D9695EB894', 'Item 1'), ('35476E01-0C21-4EAE-D754-08D9695EB894', 'Second Item')] So when select the second one instead of '35476E01-0C21-4EAE-D754-08D9695EB894' value is '2'.
Any idea how to fix it? I could update linked repos from previous post with example if it could help.
Actually I think I have solved the problem, seems the issue was something else (wrong Service for lookup). Will check it further.
How could Dropdown field in a Form be configured for remote lookup. Is there somewhere example with this usage? If there would be dozens of thousand of Companies, to load in combo only say first 50 based on search input. So field would need some trigger on input change that would call method on server which will return top 50 to load combo. Is something like this supported with simple config, if not, is there a way to achieve it with custom setup? Thx in advance.