Compilation of different programming
projects I amuse myself with.
Thursday, 31 December 2020
Simple property grid in Blazor
This artile will present a simple property grid in Blazor I have made. The component relies on standard stuff like Bootstrap, jQuery, Twitter Bootstrap and Font Awesome. But the repo url shown here links to the Github repo of mine which can be easily forked if you want to add features (such as editing capabilities). The component already supports nested levels, so if the object you inspect has a hierarchical structure, this is shown in this Blazor component.
Having a component to inspect objects in Blazor is great as Blazor lacks inspect tools (since the app is compiled into a web assembly, we cannot easily inspect state of objects in the app other than the DOM and Javascript objects. With this component we can get basic inspection support to inspect state of the object in the app you desire to inspect).
The Github repo contains also a bundled application which uses the component and shows a sample use-case (also shown in Gif video below). I have tested the component with three levels of depth for a sample object (included in the repo).
The component is available here on my Github repo:
The component consists of two components where one of them is used in a recursive manner to support nested object structure.
The top level component got this code-behind class.
PropertyGridComponentBase.cs
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespaceBlazorPropertyGridComponents.Components
{
publicclassPropertyGridComponentBase : ComponentBase
{
[Inject]
public IJSRuntime JsRuntime { get; set; }
[Parameter] publicobject DataContext { get; set; }
public Dictionary<string, PropertyInfoAtLevelNodeComponent> Props { get; set; }
publicPropertyGridComponentBase()
{
Props = new Dictionary<string, PropertyInfoAtLevelNodeComponent>();
}
protectedoverridevoidOnParametersSet()
{
Props.Clear();
if (DataContext == null)
return;
Props["ROOT"] = MapPropertiesOfDataContext(string.Empty, DataContext, null);
StateHasChanged();
}
privateboolIsNestedProperty(PropertyInfo pi) =>
pi.PropertyType.IsClass && pi.PropertyType.Namespace != "System";
private PropertyInfoAtLevelNodeComponent MapPropertiesOfDataContext(string propertyPath, object parentObject,
PropertyInfo currentProp)
{
if (parentObject == null)
returnnull;
var publicProperties = parentObject.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var propertyNode = new PropertyInfoAtLevelNodeComponent
{
PropertyName = currentProp?.Name ?? "ROOT",
PropertyValue = parentObject,
PropertyType = parentObject.GetType(),
FullPropertyPath = TrimFullPropertyPath($"{propertyPath}.{currentProp?.Name}") ?? "ROOT",
IsClass = parentObject.GetType().IsClass && parentObject.GetType().Namespace != "System"
};
foreach (var p in publicProperties)
{
var propertyValue = p.GetValue(parentObject, null);
if (!IsNestedProperty(p))
{
propertyNode.SubProperties.Add(p.Name, new PropertyInfoAtLevelNodeComponent
{
IsClass = false,
FullPropertyPath = TrimFullPropertyPath($"{propertyPath}.{p.Name}"),
PropertyName = p.Name,
PropertyValue = propertyValue,
PropertyType = p.PropertyType
//note - SubProperties are default empty if not nested property of course.
}
);
}
else
{
//we need to add the sub property but recurse also call to fetch the nested properties
propertyNode.SubProperties.Add(p.Name, new PropertyInfoAtLevelNodeComponent
{
IsClass = true,
FullPropertyPath = propertyPath + p.Name,
PropertyName = p.Name,
PropertyValue = MapPropertiesOfDataContext(TrimFullPropertyPath($"{propertyPath}.{p.Name}"), propertyValue, p),
PropertyType = p.PropertyType
//note - SubProperties are default empty if not nested property of course.
}
);
}
}
return propertyNode;
}
protectedvoidtoggleExpandButton(MouseEventArgs e, string buttonId)
{
JsRuntime.InvokeVoidAsync("toggleExpandButton", buttonId);
}
privatestringTrimFullPropertyPath(string fullpropertypath)
{
if (string.IsNullOrEmpty(fullpropertypath))
return fullpropertypath;
return fullpropertypath.TrimStart('.').TrimEnd('.');
}
}
}
And its razor file looks like this:
PropertyGridComponentBase.razor
@inherits PropertyGridComponentBase
@using BlazorPropertyGridComponents.Components
<tableclass="table table-striped col-md-4 col-lg-3 col-sm-6"><thead><tr><thscope="col">Property</th><thscope="col">Value</th></tr></thead><tbody>
@foreach (KeyValuePair<string, PropertyInfoAtLevelNodeComponent> prop in Props)
{
@if (!prop.Value.IsClass)
{
@* <tr><td>@prop.Key</td><td>@prop.Value</td></tr>*@
}
else
{
var currentNestedDiv = "currentDiv_" + prop.Key;
var currentProp = prop.Value.PropertyValue;
//must be a nested class property
<tr><tdcolspan="2"><buttontype="button"id="@prop.Key"class="btn btn-info fas fa-minus" @onclick="(e) => toggleExpandButton(e,prop.Key)"data-toggle="collapse"data-target="#@currentNestedDiv"></button><divid="@currentNestedDiv"class="collapse show"><PropertyRowComponentDepth="1"PropertyInfoAtLevel="@prop.Value" /></div></td></tr>
}
}
</tbody></table>
@code {
}
We also have this helper class to model each property in the nested structure:
PropertyInfoAtLevelNodeComponent.cs
using System;
using System.Collections.Generic;
namespaceBlazorPropertyGridComponents.Components
{
///<summary>/// Node class for hierarchical structure of property info for an object of given object graph structure.///</summary>publicclassPropertyInfoAtLevelNodeComponent
{
publicPropertyInfoAtLevelNodeComponent()
{
SubProperties = new Dictionary<string, PropertyInfoAtLevelNodeComponent>();
}
publicstring PropertyName { get; set; }
publicobject PropertyValue { get; set; }
public Type PropertyType { get; set; }
public Dictionary<string, PropertyInfoAtLevelNodeComponent> SubProperties { get; privateset; }
publicstring FullPropertyPath { get; set; }
publicbool IsClass { get; set; }
}
}
Our lower component used by the top component code-behind looks like this:
PropertyRowComponentBase.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespaceBlazorPropertyGridComponents.Components
{
publicclassPropertyRowComponentBase : ComponentBase
{
publicPropertyRowComponentBase()
{
DisplayedFullPropertyPaths = new List<string>();
}
[Parameter]
public PropertyInfoAtLevelNodeComponent PropertyInfoAtLevel { get; set; }
[Parameter]
publicint Depth { get; set; }
[Parameter]
public List<string> DisplayedFullPropertyPaths { get; set; }
[Inject]
protected IJSRuntime JsRunTime { get; set; }
protectedvoidtoggleExpandButton(MouseEventArgs e, string buttonId)
{
JsRunTime.InvokeVoidAsync("toggleExpandButton", buttonId);
}
}
}
The razor file looks like this:
PropertyRowComponent.razor
@using BlazorPropertyGridComponents.Components
@inherits PropertyRowComponentBase
@foreach (var item in PropertyInfoAtLevel.SubProperties.Keys)
{
var propertyInfoAtLevel = PropertyInfoAtLevel.SubProperties[item];
if (propertyInfoAtLevel != null)
{
@* if (DisplayedFullPropertyPaths.Contains(propertyInfoAtLevel.FullPropertyPath)){
continue; //the property is already displayed.
}*@
DisplayedFullPropertyPaths.Add(propertyInfoAtLevel.FullPropertyPath);
@* <spanclass="text-white bg-dark">@propertyInfoAtLevel.FullPropertyPath</span>*@
@* <em>
@propertyInfoAtLevel
</em>*@
}
if (!propertyInfoAtLevel.PropertyType.IsClass || propertyInfoAtLevel.PropertyType.Namespace.StartsWith("System"))
{
<tr><td><spantitle="@propertyInfoAtLevel.FullPropertyPath"class="font-weight-bold">@propertyInfoAtLevel.PropertyName</span></td><td><span>@propertyInfoAtLevel.PropertyValue</span></td></tr>
}
else if (propertyInfoAtLevel.PropertyValue != null && propertyInfoAtLevel.PropertyValue is PropertyInfoAtLevelNodeComponent)
{
var nestedLevel = (PropertyInfoAtLevelNodeComponent)propertyInfoAtLevel.PropertyValue;
var collapseOrNotCssClass = Depth == 0 ? "collapse show" : "collapse";
var curDepth = Depth + 1;
collapseOrNotCssClass += " depth" + Depth;
var currentNestedDiv = "collapsingdiv_" + propertyInfoAtLevel.PropertyName;
//must be a nested class property
<tr><tdcolspan="2"><span>@propertyInfoAtLevel.PropertyName</span><buttonid="@propertyInfoAtLevel.FullPropertyPath"type="button" @onclick="(e) => toggleExpandButton(e,propertyInfoAtLevel.FullPropertyPath)"class="fas btn btn-info fa-plus"data-toggle="collapse"data-target="#@currentNestedDiv"></button><divid="@currentNestedDiv"class="@collapseOrNotCssClass"><PropertyRowComponentPropertyInfoAtLevel="@nestedLevel"Depth="@curDepth" /></div></td></tr>
}
}
@code {
}
I updated the Github repo url :
ReplyDeletehttps://github.com/toreaurstadboss/BlazorPropertyGrid/tree/main/BlazorPropertyGrid