Looping over QuickGrid-columns instead of hardcoding with Blazor - c#

I have been using the QuickGrid build by the Blazor team (https://aspnet.github.io/quickgridsamples/). It is working, but I have many columns, so I am interested in figuring out whether I can loop over the <PropertyColumn>-components instead of hardcoding them.
My Component.razor component:
<div class="scrollable" >
<QuickGrid Items="MethodThatGetsIQueryableElements()">
#foreach (var ColumnDTO in ColumnDTOs) {
if (ColumnDTO.Show)
{
if (ColumnDTO.Name == ComponentEnums.Department)
{
<PropertyColumn Property="#(v => v.Department)" Sortable="true" />
}
if (ColumnDTO.Name == ComponentEnums.Name)
{
<PropertyColumn Property="#(v => v.Name)" Sortable="true" />
}
[... Numerous other columns in same format...]
}
}
</QuickGrid>
</div>
The initial conditional statement is connected with a checkbox, so basically checking/unchecking will show/hide the given column. My main difficulty is with the lambda expression for the Property-parameter. I can't seem to get it working with a foreach-loop.
The Items I am trying to loop through is a IQueryable<RowDTO> with the RowDTO just holding information on each row of data. The ColumnDTO holds the name of the column based on an enum-class (ComponentEnums) and the show/hide-boolean. Samples of the classes:
RowDTO.cs:
public class RowDTO
{
public RowDTO(string department, string name, [....] )
{
Department = department;
Name = name;
[....]
}
public string Department { get; set; }
public string Name { get; set; }
[....]
}
ColumnDTO.cs:
public class ColumnDTO
{
public ColumnDTO(ComponentEnums name, bool show)
{
Name = name;
Show = show;
}
public ComponentEnums Name { get; set; }
public bool Show { get; set; } = true;
}

Related

angular kendo grid custom filter not working on objects that has collection property

I want to have a filter on a column that is a collection of objects.
I use kendo-grid to show Product objects:
public class Product
{
public string Id{ get; set; }
public string Title{ get; set; }
public ObservableCollection<Tag> Tags { get; set; }
}
public class Tag
{
public string Id{ get; set; }
public string Title{ get; set; }
}
and I want to filter grid by tags, so I wrote custom filter for kendo grid in typescript
private tagFilter: any[] = [];
public tagChange(values: any[], filterService: FilterService): void {
filterService.filter({
filters: values.map(value => ({
field: "tags.id",
operator: "eq",
value
})),
logic: "or"
});}
public tagFilters(filter: CompositeFilterDescriptor): FilterDescriptor[] {
this.tagFilter.splice(
0, this.tagFilter.length,
...flatten(filter).map(({ value }) => value)
);
return this.tagFilter; }
const flatten = filter => { const filters = (filter || {}).filters; if (filters) {
return filters.reduce((acc, curr) => acc.concat(curr.filters ? flatten(curr) : [curr]), []); } return [];};
and HTML Code is
<kendo-grid-column field="tags.id" title="Tag" width="150">
<ng-template kendoGridFilterMenuTemplate let-column="column" let-filter="filter" let-filterService="filterService">
<kendo-multiselect style="width:220px"
[data]="allTags"
textField="title"
valueField="id"
[valuePrimitive]="true"
[value]="tagFilters(filter)"
(valueChange)="tagChange($event, filterService)">
</kendo-multiselect>
</ng-template>
and backend code is C# and and search service is
public virtual DataSourceResult SearchKendo(DataSourceRequest criteria)
{
using (var repository = _repositoryFactory())
{
return repository.Products.Include("Tags").ToDataSourceResult(criteria.Take, criteria.Skip, criteria.Sort, criteria.Filter);
}
}
but in run time I get this error
System.Linq.Dynamic.Core.Exceptions.ParseException: 'No property or field 'id' exists in type 'ObservableCollection`1''
It seems that "tags" is not known as collection of objects, indeed it is considered as a object

Net Core 3.0: multiple checkboxes with integer variables

In my web app I have a self referencing many to many relationship of plants. The M2M relationships are good neighbours and bad neighbours. I want to be able to have a form where a user can check off the both types of neighbours and then save the form.
What I have so far:
For brevity, I will only show code to Good neighbours relation, the bad neighbours is the same.
Models
public class Plant
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<GoodPlants> GoodNeighbours { get; set; }
}
public class GoodPlants
{
public int PlantId { get; set; }
public int GoodNeighbourId { get; set; }
public virtual Plant Plant { get; set; }
public virtual Plant GoodNeighbour {get; set;}
}
My viewmodel EditViewModel:
public class EditViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<PlantIsSelectedViewModel> AllPlants { get; set; }
}
And the model PlantIsSelected
public class PlantIsSelectedViewModel
{
public int id { get; set; }
public string name { get; set; }
public bool isGoodNeighbour { get; set; }
public bool isBadNeighbour { get; set; }
}
I have an Edit method to display the edited plant and all the other plants:
var plant = await _context.Plants.FindAsync(id);
ICollection<Plant> plants = _context.Plants.ToList();
ICollection<GoodPlants> goodNeighbours = await _context.GoodNeighbours
.Where(g => g.PlantId == id)
.Include(g => g.GoodNeighbour)
.ToListAsync();
GoodPlants ownGoodPlant = goodNeighbours.FirstOrDefault(i => i.GoodNeighbour.Id == plant.Id);
Plant ownPlant = plants.FirstOrDefault(i => i.Id == plant.Id);
goodNeighbours.Remove(ownGoodPlant);
plants.Remove(ownPlant);
//populate the viewmodel
EditViewModel plantModel = new EditViewModel();
plantModel.Id = plant.Id;
plantModel.Name = plant.Name;
plantModel.AllPlants = _mapper.Map<ICollection<PlantIsSelectedViewModel>>(Plants);
foreach (var element in plantModel.AllPlants)
{
if (goodNeighbours.Any(g => g.GoodNeighbour.Id == element.id))
{
element.isGoodNeighbour = true;
}
else if (badNeighbours.Any(g => g.BadNeighbour.Id == element.id))
{
element.isBadNeighbour = true;
}
}
This desperately needs refactoring, but thats not the main issue here.
In my view I contrast the elements of AllPlants collection if it appears on either of the neighbours collections and have the checkbox set to checked or not:
<tbody>
#foreach (var item in Model.AllPlants)
{
<tr>
#if (item.isGoodNeighbour)
{
<td>
#Html.Label(item.name)
</td>
<td>
<input type="checkbox" name="#Model.AllPlants"
value="#item.id"
#(Html.Raw("checked=\"checked\""))
/>
</td>
<td>
<input type="checkbox" name="#Model.AllPlants"
value="#item.id" />
</td>
}
// else statements below for badneighbours and cases without any relation.
// ...
</tr>
}
I want to know how I can keep track of all the selected items (and unselected), get them in my editViewModel and send them back to my [HttpPost] Edit method. My current [HttpPost] method receives the same viewmodel, but the AllPlants property is empty.
How do I receive the correct data back?
Thanks in advance, I'm pretty stuck!

How to use #bind-Value in a dynamic form?

I am using Blazor to create a dynamic form from the properties of my model.
I am using a for each loop to loop through the properties of the model.
public class SensorType
{
public int Id { get; set; }
[Required]
[MaxLength(30)]
[Display(Name = "Sensor Type Name")]
public string Name { get; set; }
[Required]
[MaxLength(500)]
public string Description { get; set; }
[Required]
[MaxLength(2048)]
[Display(Name = "Datasheet URL")]
public string DatasheetUrl { get; set; }
}
I implemented this razor view, where I try to bind to public SensorType sensortype { get; set; }. But I need to bind to sensortype.property where property is whatever property the model has that is in the for each loop. But I cannot simply just call say #bind-Value="sensortype.property". Any ideas on how to do this? I don't want to have to manually type every field. Thanks!
<EditForm Model="#sensortype" OnValidSubmit="#SaveSensorType">
#foreach(var property in typeof(SensorType).GetProperties())
{
if(property.Name == "Id")
{
continue;
}
<div class="form-group row">
<label class="col-sm-2 col-form-label">#(GetAttribute(property, false)) </label> //This function get the name of the property in a human-readable way.
<div class="col-sm-10">//I would like to bind here to sensortype's property in the for each loop but sensortype.property gives me an error.
<InputTextArea class="form-control" #bind-Value="sensortype.property" value="sensortype.property.Name" placeholder="Description of the Type of Sensor" />
</div>
</div>
}
I am playing this days with blazor and I made a kind of DynamicForm, is not perfect but is working. I just want to show you my dynamic form as proof of concept, not at something which I wold use in production.
Basicly we want to write something like this:
<DynamicForm #bind-Model="MySensorType" />
//this will generate a form with fields for all properties of the model
So, on Index view let's create a property for MySensorType and some markup to see if the model is changing when the form fields are edited.
#page "/"
<div style="display:flex">
<div>
<DynamicForm #bind-Model="MySensorType" />
</div>
<div style="background:yellow;flex:1;margin:20px;">
<p>Id: #MySensorType.Id</p>
<p>Name: #MySensorType.Name</p>
<p>Description: #MySensorType.Description</p>
<p>Url: #MySensorType.DatasheetUrl</p>
</div>
</div>
#code {
public SensorType MySensorType { get; set; } = new SensorType();
public class SensorType
{
public int Id { get; set; } = 1;
public string Name { get; set; } = "Some Name";
public string Description { get; set; } = "Some Description";
public string DatasheetUrl { get; set; } = "This is a URL";
}
}
In order to generate automatically the fields, we need some kind of DynamicField.
Below component is for named "DynamicField"
<div>
<label>#Caption</label>
#if (Value is String sValue)
{
<input type="text" value="#sValue" #onchange="OnChange"/>
}
#if (Value is int iValue)
{
<input type="number" value="#iValue" #onchange="OnChange" />
}
</div>
#code {
[Parameter] public string Caption { get; set; }
[Parameter] public object Value { get; set; }
[Parameter] public EventCallback<object> ValueChanged { get; set; }
async void OnChange(ChangeEventArgs e)
{
await ValueChanged.InvokeAsync(e.Value);
}
}
Now, we can create the wrapper so called DynamicForm:
#typeparam T
#foreach (var p in Properties)
{
<DynamicField Value="#p.Value" Caption="#p.Key" ValueChanged="#((e)=>OnValueChanged(e,p.Key))"/>
}
#code{
[Parameter] public T Model { get; set; }
[Parameter] public EventCallback<T> ModelChanged { get; set; }
public Dictionary<string, object> Properties { get; set; } = new Dictionary<string, object>();
protected override void OnInitialized()
{
var props = Model.GetType().GetProperties();
foreach (var p in props)
{
Properties.Add(p.Name, p.GetValue(Model));
}
}
void OnValueChanged(object e, string prop)
{
var p = Model.GetType().GetProperty(prop);
if (p.PropertyType == typeof(int))
{
var intValue = Convert.ToInt32(e);
p.SetValue(Model, intValue);
}
if (p.PropertyType == typeof(string))
{
p.SetValue(Model, e.ToString());
}
ModelChanged.InvokeAsync(Model);
}
}
What actually is happening here, we are using Reflection to get all properties of the model, send them to DynamicFields, and when those values are changed we set the new value to the model and call ModelChanged to send new values.
On my computer this works, and every time when I change a value, MySensorType is showing the new value on Index component.
You can see that I created dynamic fields only for Number and String, if you have DateTime or Select, you need to extend this DynamicField, for select will be more difficult.
By the way, on Index view you can put a button and call SaveChanges with your logic and use MySensorType.

How to access Dictionary Model Property value from View - ASP.Net MVC

I want to get value of Dictionary from View.
In the view, I have a main/first foreach loop that retrieve data from Model, and inside the main/first loop, I need to retrieve the ListAttribute value according to the first loop by Id.
**//Code in the View - First loop to retrieve data from Model**
#model IEnumerable<ABC.Web.Models.Room.Housing>
foreach (var item in Model.OrderBy(x => x.Id))
{
<div class="col-xs-18 col-sm-4">
<div class="thumbnail">
*...(remainer of the code)*
//Here to insert second loop to retrieve *ListAttribute*
}
//Code in the Model
namespace ABC.Web.Models.Room
{
public class Housing
{
public string[] FloorPlan { get; set; }
public Dictionary<string, ListAttribute> ListAttributes { get; set;}
public string Latitude { get; set; }
}
public partial class ListAttribute
{
public string name { get; set; }
public string icon { get; set; }
public string value { get; set; }
}
}
If item is an object of type Housing, then you can do this:
foreach (KeyValuePair<string, ListAttribute> listItem in item.ListAttributes)
{
// Then you will have...
// listItem.Key -> string
// listItem.Value -> ListAttribute
}
Source

Bind multiselect selections to list of objects

I have a view model like so:
public class ListingPlanEditorViewModel
{
public ListingPlan Plan { get; set; }
public IEnumerable<Directory> SiteDirectories { get; set; }
}
One property is an object of type ListingPlan here:
public class ListingPlan
{
public int? ListingPlanID { get; set; }
public int DescriptionLinesCount { get; set; }
public List<Directory> Directories { get; set; }
}
The object Directory looks like this:
public class Directory
{
public int DirectoryID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
I have a controller that returns a ListingPlanEditorViewModel to the view:
public ActionResult ConfigurePlan(int? listingIdentifier)
{
ListingPlan plan = new ListingPlan()
{
DescriptionLinesCount = 10,
Directories = new List<Directory>()
{
new Directory()
{
DirectoryID = 3
},
new Directory()
{
DirectoryID = 4
}
}
};
ListingPlanEditorViewModel model = new ListingPlanEditorViewModel()
{
Plan = plan,//_listingRepository.GetListingPlan(listingIdentifier, null),
SiteDirectories = _database.GetDirectories()
};
return View(model);
}
I would like to create a multiselect box that will bind the selected values back to the Plan property in the ListingPlanEditorViewModel, setting the DirectoryID property for each selection. So after binding I should have a List of Directory objects. All with their DirectoryID's set.
I'm having some trouble doing this. I can create the multiselectbox with the correct select options in it, but I am unable to retrieve them in my post action which looks like this:
#using (Html.BeginForm("ConfigurePlan", "ListingPlan"))
{
<div class="form-body">
#Html.ListBoxFor(model => model.Plan.Directories, new MultiSelectList(Model.SiteDirectories, "DirectoryID", "Name"))
</div>
<button type="submit">submit</button>
}
You have to create an [] or List of IDs in the ViewModel that will store selected values.
public class ListingPlanEditorViewModel
{
public ListingPlan Plan { get; set; }
public IEnumerable<Directory> SiteDirectories { get; set; }
public int[] DirectoryIDs {get;set;}
}
The View will change according. The Directories selected will be stored in DirectoryIDs.
#using (Html.BeginForm("ConfigurePlan", "ListingPlan"))
{
<div class="form-body">
#Html.ListBoxFor(model => model.DirectoryIDs, new MultiSelectList(Model.SiteDirectories, "DirectoryID", "Name"))
</div>
<button type="submit">submit</button>
}
Now on POST Action you can query the database and get the Directories that was selected by user.
Note: You can't just get the full objects because the ListBoxFor will generate a <select multiple ... > ... </select> tag won't know how to bind to your object.

Categories