Added edit modal
This commit is contained in:
@@ -10,6 +10,9 @@ public class PropertyConfig(PropertyInfo info) {
|
||||
public bool Sortable { get; set; } = true;
|
||||
public bool Searchable { get; set; } = true;
|
||||
public PropertyInfo? DisplayedProperty { get; set; }
|
||||
public Func<object, string>? Formatter { get; set; }
|
||||
public bool Editable { get; set; } = true;
|
||||
public bool Creatable { get; set; } = true;
|
||||
}
|
||||
|
||||
public class PropertyConfig<TProp>(PropertyConfig config) {
|
||||
@@ -40,4 +43,19 @@ public class PropertyConfig<TProp>(PropertyConfig config) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public PropertyConfig<TProp> Format(Func<TProp, string> formatter) {
|
||||
config.Formatter = obj => formatter.Invoke((TProp)obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PropertyConfig<TProp> Editable(bool editable) {
|
||||
config.Editable = editable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PropertyConfig<TProp> Creatable(bool creatable) {
|
||||
config.Creatable = creatable;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HopFrame.Core.Config;
|
||||
@@ -17,7 +19,18 @@ public class TableConfig {
|
||||
ContextConfig = config;
|
||||
|
||||
foreach (var info in tableType.GetProperties()) {
|
||||
Properties.Add(new PropertyConfig(info));
|
||||
var propConfig = new PropertyConfig(info);
|
||||
|
||||
if (info.GetCustomAttributes(true).Any(a => a is DatabaseGeneratedAttribute)) {
|
||||
propConfig.Creatable = false;
|
||||
propConfig.Editable = false;
|
||||
}
|
||||
|
||||
if (info.GetCustomAttributes(true).Any(a => a is KeyAttribute)) {
|
||||
propConfig.Editable = false;
|
||||
}
|
||||
|
||||
Properties.Add(propConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ public interface ITableManager {
|
||||
public int TotalPages(int perPage = 20);
|
||||
public Task DeleteItem(object item);
|
||||
|
||||
public string DisplayProperty(object item, PropertyInfo info, TableConfig? tableConfig);
|
||||
public string DisplayProperty(object? item, PropertyInfo info, TableConfig? tableConfig);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Reflection;
|
||||
using HopFrame.Core.Config;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -50,14 +51,27 @@ internal sealed class TableManager<TModel>(DbContext context, TableConfig config
|
||||
return false;
|
||||
}
|
||||
|
||||
public string DisplayProperty(object item, PropertyInfo info, TableConfig? tableConfig) {
|
||||
public string DisplayProperty(object? item, PropertyInfo info, TableConfig? tableConfig) {
|
||||
if (item is null) return string.Empty;
|
||||
var prop = tableConfig?.Properties.Find(prop => prop.Info.Name == info.Name);
|
||||
if (prop is null) return item.ToString() ?? string.Empty;
|
||||
|
||||
var propValue = prop.Info.GetValue(item);
|
||||
if (propValue is null || prop.DisplayedProperty is null)
|
||||
return propValue?.ToString() ?? string.Empty;
|
||||
if (propValue is null)
|
||||
return string.Empty;
|
||||
|
||||
if (prop.Formatter is not null) {
|
||||
return prop.Formatter.Invoke(propValue);
|
||||
}
|
||||
|
||||
if (prop.DisplayedProperty is null) {
|
||||
var key = prop.Info.PropertyType
|
||||
.GetProperties()
|
||||
.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute))
|
||||
.FirstOrDefault();
|
||||
|
||||
return key?.GetValue(propValue)?.ToString() ?? propValue.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
var innerConfig = explorer.GetTable(propValue.GetType());
|
||||
return DisplayProperty(propValue, prop.DisplayedProperty, innerConfig);
|
||||
|
||||
59
src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor
Normal file
59
src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor
Normal file
@@ -0,0 +1,59 @@
|
||||
@implements IDialogContentComponent<EditorDialogData>
|
||||
|
||||
@using HopFrame.Web.Models
|
||||
@using HopFrame.Core.Services
|
||||
@using HopFrame.Web.Helpers
|
||||
|
||||
<FluentDialogBody>
|
||||
@foreach (var property in Content.Config.Properties) {
|
||||
if (!_currentlyEditing && !property.Creatable) continue;
|
||||
|
||||
<div style="margin-bottom: 20px">
|
||||
@if (property.Info.PropertyType.IsNumeric()) {
|
||||
<FluentNumberField
|
||||
TValue="double"
|
||||
Label="@property.Name"
|
||||
Value="@(Convert.ToDouble(property.Info.GetValue(Content.CurrentObject)))"
|
||||
Style="width: 100%;"
|
||||
Disabled="@(!property.Editable)"
|
||||
/>
|
||||
} else if (property.Info.PropertyType == typeof(bool)) {
|
||||
<FluentSwitch
|
||||
Label="@property.Name"
|
||||
Value="@(Convert.ToBoolean(property.Info.GetValue(Content.CurrentObject)))"
|
||||
/>
|
||||
}
|
||||
else {
|
||||
<FluentTextField
|
||||
Label="@property.Name"
|
||||
Value="@_manager?.DisplayProperty(Content.CurrentObject, property.Info, Content.Config)"
|
||||
Style="width: 100%;"
|
||||
Disabled="@(!property.Editable)"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</FluentDialogBody>
|
||||
|
||||
@inject IContextExplorer Explorer
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required EditorDialogData Content { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public required FluentDialog Dialog { get; set; }
|
||||
|
||||
private ITableManager? _manager;
|
||||
private bool _currentlyEditing;
|
||||
|
||||
protected override void OnInitialized() {
|
||||
if (Dialog.Instance is null) return;
|
||||
_currentlyEditing = Content.CurrentObject is not null;
|
||||
Dialog.Instance.Parameters.Title = Content.CurrentObject is null ? "Add entry" : "Edit entry";
|
||||
Dialog.Instance.Parameters.PreventScroll = true;
|
||||
Dialog.Instance.Parameters.Width = "500px";
|
||||
Dialog.Instance.Parameters.PrimaryAction = "Save";
|
||||
_manager = Explorer.GetTableManager(Content.Config.PropertyName);
|
||||
}
|
||||
}
|
||||
@@ -4,30 +4,38 @@
|
||||
|
||||
@using HopFrame.Core.Config
|
||||
@using HopFrame.Core.Services
|
||||
@using HopFrame.Web.Models
|
||||
@using Microsoft.JSInterop
|
||||
@using System.Text.Json
|
||||
|
||||
<FluentDialogProvider />
|
||||
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<FluentToolbar Class="hopframe-toolbar">
|
||||
<h3>@_config?.PropertyName</h3>
|
||||
<FluentSpacer />
|
||||
<FluentSearch @oninput="OnSearch" @onchange="OnSearch" />
|
||||
<FluentButton>Add Entry</FluentButton>
|
||||
<FluentButton OnClick="async () => { await CreateOrEdit(null); }">Add Entry</FluentButton>
|
||||
</FluentToolbar>
|
||||
|
||||
<div style="overflow-y: auto; flex-grow: 1">
|
||||
<FluentDataGrid Items="_currentlyDisplayedModels?.AsQueryable()">
|
||||
@{ var dataIndex = 0; }
|
||||
@foreach (var property in _config!.Properties.Where(prop => prop.List)) {
|
||||
<PropertyColumn Title="@property.Name" Property="o => _manager!.DisplayProperty(o, property.Info, _config)" Style="min-width: max-content; min-height: 43px" Sortable="@property.Sortable"/>
|
||||
<PropertyColumn
|
||||
Title="@property.Name" Property="o => _manager!.DisplayProperty(o, property.Info, _config)"
|
||||
Style="min-width: max-content; min-height: 43px;"
|
||||
Sortable="@property.Sortable"
|
||||
/>
|
||||
}
|
||||
|
||||
<TemplateColumn Title="Actions" Align="@Align.End" Style="min-height: 43px; min-width: max-content">
|
||||
@{ var currentElement = _currentlyDisplayedModels!.ElementAt(dataIndex); }
|
||||
<FluentButton aria-label="Edit entry">
|
||||
@{ var currentElement = _currentlyDisplayedModels!.ElementAtOrDefault(dataIndex); }
|
||||
<FluentButton aria-label="Edit entry" OnClick="async () => { await CreateOrEdit(currentElement); }">
|
||||
<FluentIcon Value="@(new Icons.Regular.Size16.Edit())"/>
|
||||
</FluentButton>
|
||||
|
||||
<FluentButton aria-label="Delete entry" OnClick="async () => { await DeleteEntry(currentElement); }">
|
||||
<FluentButton aria-label="Delete entry" OnClick="async () => { await DeleteEntry(currentElement!); }">
|
||||
<FluentIcon Value="@(new Icons.Regular.Size16.Delete())" Color="Color.Warning"/>
|
||||
</FluentButton>
|
||||
|
||||
@@ -38,7 +46,7 @@
|
||||
</TemplateColumn>
|
||||
</FluentDataGrid>
|
||||
|
||||
@if (_currentlyDisplayedModels?.Any() == true) {
|
||||
@if (_totalPages > 1) {
|
||||
<div class="hopframe-paginator">
|
||||
<FluentButton BackgroundColor="transparent" OnClick="() => ChangePage(_currentPage - 1)">
|
||||
<FluentIcon Value="@(new Icons.Regular.Size20.ArrowPrevious())" Color="Color.Neutral" />
|
||||
@@ -78,6 +86,7 @@
|
||||
@inject IContextExplorer Explorer
|
||||
@inject NavigationManager Navigator
|
||||
@inject IJSRuntime Js
|
||||
@inject IDialogService Dialogs
|
||||
|
||||
@code {
|
||||
|
||||
@@ -106,7 +115,9 @@
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender) {
|
||||
try {
|
||||
await Js.InvokeVoidAsync("removeBg");
|
||||
}catch (Exception) {}
|
||||
}
|
||||
|
||||
private CancellationTokenSource _searchCancel = new();
|
||||
@@ -132,6 +143,10 @@
|
||||
}
|
||||
|
||||
private async Task DeleteEntry(object element) { //TODO: display confirmation
|
||||
var dialog = await Dialogs.ShowConfirmationAsync("Do you really want to delete this entry?");
|
||||
var result = await dialog.Result;
|
||||
if (result.Cancelled) return;
|
||||
|
||||
await _manager!.DeleteItem(element);
|
||||
|
||||
if (!string.IsNullOrEmpty(_searchTerm)) {
|
||||
@@ -141,4 +156,8 @@
|
||||
OnInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateOrEdit(object? element) {
|
||||
var panel = await Dialogs.ShowPanelAsync<HopFrameEditor>(new EditorDialogData(_config!, element), new());
|
||||
}
|
||||
}
|
||||
22
src/HopFrame.Web/Helpers/TypeExtensions.cs
Normal file
22
src/HopFrame.Web/Helpers/TypeExtensions.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace HopFrame.Web.Helpers;
|
||||
|
||||
internal static class TypeExtensions {
|
||||
public static bool IsNumeric(this Type o) {
|
||||
switch (Type.GetTypeCode(o)) {
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.UInt32:
|
||||
case TypeCode.UInt64:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.Int64:
|
||||
case TypeCode.Decimal:
|
||||
case TypeCode.Double:
|
||||
case TypeCode.Single:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/HopFrame.Web/Models/EditorDialogData.cs
Normal file
8
src/HopFrame.Web/Models/EditorDialogData.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using HopFrame.Core.Config;
|
||||
|
||||
namespace HopFrame.Web.Models;
|
||||
|
||||
public sealed class EditorDialogData(TableConfig config, object? current = null) {
|
||||
public object? CurrentObject { get; } = current;
|
||||
public TableConfig Config { get; } = config;
|
||||
}
|
||||
@@ -9,3 +9,4 @@
|
||||
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||
@using Microsoft.FluentUI.AspNetCore.Components.Extensions
|
||||
@using HopFrame.Web.Components.Layout
|
||||
@using HopFrame.Web.Components.Dialogs
|
||||
|
||||
@@ -14,4 +14,6 @@ public class Post {
|
||||
|
||||
[ForeignKey("author")]
|
||||
public User? Author { get; set; }
|
||||
|
||||
public bool Published { get; set; }
|
||||
}
|
||||
@@ -10,8 +10,4 @@ public class User {
|
||||
public string? Password { get; set; }
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
|
||||
public override string ToString() {
|
||||
return Id.ToString();
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,17 @@ builder.Services.AddHopFrame(options => {
|
||||
.Sortable(false);
|
||||
});
|
||||
|
||||
/* context.Table<Post>()
|
||||
.Property(p => p.Author)
|
||||
.DisplayedProperty(u => u!.Username); */
|
||||
context.Table<Post>()
|
||||
.Property(p => p.Author)
|
||||
.DisplayedProperty(u => u!.Username);
|
||||
.Format(user => $"{user?.FirstName} {user?.LastName}");
|
||||
|
||||
context.Table<Post>()
|
||||
.Property(p => p.Id)
|
||||
.SetDisplayName("ID")
|
||||
.Editable(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user