mirror of https://github.com/abpframework/abp.git
committed by
GitHub
5 changed files with 580 additions and 0 deletions
@ -0,0 +1,24 @@ |
|||
using Microsoft.AspNetCore.Mvc.ViewFeatures; |
|||
using Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
[HtmlTargetElement("abp-dynamic-text", TagStructure = TagStructure.NormalOrSelfClosing)] |
|||
public class AbpDynamicTextTagHelper : AbpTagHelper<AbpDynamicTextTagHelper, AbpDynamicTextTagHelperService> |
|||
{ |
|||
[HtmlAttributeName("abp-model")] |
|||
public ModelExpression Model { get; set; } = default!; |
|||
|
|||
[HtmlAttributeName("column-size")] |
|||
public ColumnSize ColumnSize { get; set; } |
|||
|
|||
[HtmlAttributeName("label-width")] |
|||
public ColumnSize LabelWidth { get; set; } = ColumnSize._4; |
|||
|
|||
public AbpDynamicTextTagHelper(AbpDynamicTextTagHelperService tagHelperService) |
|||
: base(tagHelperService) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,206 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Text.Encodings.Web; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Rendering; |
|||
using Microsoft.AspNetCore.Mvc.ViewFeatures; |
|||
using Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
public class AbpDynamicTextTagHelperService : AbpTagHelperService<AbpDynamicTextTagHelper> |
|||
{ |
|||
protected HtmlEncoder HtmlEncoder { get; } |
|||
protected IServiceProvider ServiceProvider { get; } |
|||
protected List<ModelExpression> Models = new(); |
|||
|
|||
public AbpDynamicTextTagHelperService( |
|||
HtmlEncoder htmlEncoder, |
|||
IServiceProvider serviceProvider |
|||
) |
|||
{ |
|||
HtmlEncoder = htmlEncoder; |
|||
ServiceProvider = serviceProvider; |
|||
} |
|||
|
|||
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
Models = GetModels(context, output); |
|||
NormalizeTagMode(context, output); |
|||
var childContent = await output.GetChildContentAsync(); |
|||
var html = await GetHtmlAsync(context, output); |
|||
SetContent(context, output, html, childContent); |
|||
SetAttributes(context, output); |
|||
} |
|||
|
|||
protected virtual void NormalizeTagMode(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
output.TagMode = TagMode.StartTagAndEndTag; |
|||
output.TagName = "div"; |
|||
} |
|||
|
|||
protected virtual void SetAttributes(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
output.Attributes.AddIfNotContains("class", "abp-dynamic-text"); |
|||
} |
|||
|
|||
protected virtual void SetContent(TagHelperContext context, TagHelperOutput output, string html, TagHelperContent childContent) |
|||
{ |
|||
var content = childContent.GetContent(); |
|||
|
|||
if (content.Contains(AbpFormContentPlaceHolder)) |
|||
{ |
|||
content = content.Replace(AbpFormContentPlaceHolder, html); |
|||
} |
|||
else |
|||
{ |
|||
content = html + content; |
|||
} |
|||
|
|||
output.Content.SetHtmlContent(content); |
|||
} |
|||
|
|||
protected virtual async Task<string> GetHtmlAsync(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
var contentBuilder = new StringBuilder(); |
|||
|
|||
foreach (var model in Models) |
|||
{ |
|||
var textTagHelper = GetTextTagHelper(model); |
|||
var textHtml = await textTagHelper.RenderAsync(new TagHelperAttributeList(), context, HtmlEncoder, "div", TagMode.StartTagAndEndTag); |
|||
|
|||
if (TagHelper.ColumnSize > ColumnSize.Undefined) |
|||
{ |
|||
var columnSize = $"col-12 col-sm-{(int)TagHelper.ColumnSize}"; |
|||
var hidden = IsHidden(model.ModelExplorer) ? " d-none" : ""; |
|||
contentBuilder.AppendLine($"<div class=\"{columnSize}{hidden}\">{textHtml}</div>"); |
|||
} |
|||
else |
|||
{ |
|||
contentBuilder.AppendLine(textHtml); |
|||
} |
|||
} |
|||
|
|||
if (TagHelper.ColumnSize > ColumnSize.Undefined) |
|||
{ |
|||
contentBuilder.Insert(0, "<div class=\"row\">"); |
|||
contentBuilder.AppendLine("</div>"); |
|||
} |
|||
|
|||
return contentBuilder.ToString(); |
|||
} |
|||
|
|||
protected virtual AbpTextTagHelper GetTextTagHelper(ModelExpression model) |
|||
{ |
|||
var textTagHelper = ServiceProvider.GetRequiredService<AbpTextTagHelper>(); |
|||
textTagHelper.AspFor = model; |
|||
textTagHelper.ViewContext = TagHelper.ViewContext; |
|||
|
|||
var textAttribute = model.ModelExplorer.GetAttribute<AbpText>(); |
|||
if (textAttribute == null) |
|||
{ |
|||
textTagHelper.LabelWidth = TagHelper.LabelWidth; |
|||
return textTagHelper; |
|||
} |
|||
|
|||
textTagHelper.Format = textAttribute.Format; |
|||
textTagHelper.LabelWidth = textAttribute.LabelWidth ?? TagHelper.LabelWidth; |
|||
textTagHelper.SuppressLabel = textAttribute.SuppressLabel; |
|||
|
|||
return textTagHelper; |
|||
} |
|||
|
|||
protected bool IsHidden(ModelExplorer model) |
|||
{ |
|||
return model.GetAttribute<HiddenInputAttribute>() != null; |
|||
} |
|||
|
|||
protected virtual List<ModelExpression> GetModels(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
return TagHelper.Model.ModelExplorer.Properties.Aggregate(new List<ModelExpression>(), ExploreModelsRecursively); |
|||
} |
|||
|
|||
protected virtual List<ModelExpression> ExploreModelsRecursively(List<ModelExpression> list, ModelExplorer model) |
|||
{ |
|||
if (model.GetAttribute<DynamicFormIgnore>() != null) |
|||
{ |
|||
return list; |
|||
} |
|||
|
|||
if (IsCsharpClassOrPrimitive(model.ModelType) || IsListOfCsharpClassOrPrimitive(model.ModelType)) |
|||
{ |
|||
list.Add(ModelExplorerToModelExpressionConverter(model)); |
|||
return list; |
|||
} |
|||
|
|||
if (IsListOfSelectItem(model.ModelType) || IsFile(model.ModelType)) |
|||
{ |
|||
list.Add(ModelExplorerToModelExpressionConverter(model)); |
|||
return list; |
|||
} |
|||
|
|||
return model.Properties.Aggregate(list, ExploreModelsRecursively); |
|||
} |
|||
|
|||
protected virtual ModelExpression ModelExplorerToModelExpressionConverter(ModelExplorer explorer) |
|||
{ |
|||
var temp = explorer; |
|||
var propertyName = explorer.Metadata.PropertyName; |
|||
|
|||
while (temp?.Container?.Metadata?.PropertyName != null) |
|||
{ |
|||
temp = temp.Container; |
|||
propertyName = temp.Metadata.PropertyName + "." + propertyName; |
|||
} |
|||
|
|||
return new ModelExpression(propertyName ?? string.Empty, explorer); |
|||
} |
|||
|
|||
protected virtual bool IsListOfCsharpClassOrPrimitive(Type type) |
|||
{ |
|||
var genericType = type.GenericTypeArguments.FirstOrDefault(); |
|||
|
|||
if (genericType == null || !IsCsharpClassOrPrimitive(genericType)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return type.ToString().StartsWith("System.Collections.Generic.IEnumerable`") || |
|||
type.ToString().StartsWith("System.Collections.Generic.List`"); |
|||
} |
|||
|
|||
protected virtual bool IsCsharpClassOrPrimitive(Type? type) |
|||
{ |
|||
if (type == null) return false; |
|||
|
|||
return type.IsPrimitive || |
|||
type.IsValueType || |
|||
type == typeof(string) || |
|||
type == typeof(Guid) || |
|||
type == typeof(DateTime) || |
|||
type == typeof(ValueType) || |
|||
type == typeof(TimeSpan) || |
|||
type == typeof(DateTimeOffset) || |
|||
type.IsEnum; |
|||
} |
|||
|
|||
protected virtual bool IsListOfSelectItem(Type type) |
|||
{ |
|||
return type == typeof(List<SelectListItem>) || |
|||
type == typeof(IEnumerable<SelectListItem>); |
|||
} |
|||
|
|||
protected virtual bool IsFile(Type type) |
|||
{ |
|||
return typeof(IFormFile).IsAssignableFrom(type) || |
|||
typeof(IEnumerable<IFormFile>).IsAssignableFrom(type); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
[AttributeUsage(AttributeTargets.Property)] |
|||
public class AbpText : Attribute |
|||
{ |
|||
public ColumnSize? LabelWidth { get; set; } |
|||
|
|||
public bool SuppressLabel { get; set; } = false; |
|||
|
|||
public string? Format { get; set; } |
|||
|
|||
public AbpText() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using Microsoft.AspNetCore.Mvc.ViewFeatures; |
|||
using Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
[HtmlTargetElement("abp-text", TagStructure = TagStructure.NormalOrSelfClosing)] |
|||
public class AbpTextTagHelper : AbpTagHelper<AbpTextTagHelper, AbpTextTagHelperService> |
|||
{ |
|||
[HtmlAttributeName("asp-for")] |
|||
public ModelExpression AspFor { get; set; } = default!; |
|||
|
|||
[HtmlAttributeName("label")] |
|||
public string? Label { get; set; } |
|||
|
|||
[HtmlAttributeName("label-width")] |
|||
public ColumnSize? LabelWidth { get; set; } |
|||
|
|||
[HtmlAttributeName("suppress-label")] |
|||
public bool SuppressLabel { get; set; } = false; |
|||
|
|||
[HtmlAttributeName("format")] |
|||
public string? Format { get; set; } |
|||
|
|||
public AbpTextTagHelper(AbpTextTagHelperService tagHelperService) |
|||
: base(tagHelperService) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,302 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.Text.Encodings.Web; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using Microsoft.Extensions.Localization; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
public class AbpTextTagHelperService : AbpTagHelperService<AbpTextTagHelper> |
|||
{ |
|||
protected HtmlEncoder HtmlEncoder { get; } |
|||
private readonly IAbpTagHelperLocalizer _tagHelperLocalizer; |
|||
protected readonly IAbpEnumLocalizer _abpEnumLocalizer; |
|||
protected readonly IStringLocalizerFactory _stringLocalizerFactory; |
|||
private readonly ColumnSize DefaultLabelWidth = ColumnSize._4; |
|||
|
|||
public AbpTextTagHelperService( |
|||
HtmlEncoder htmlEncoder, |
|||
IAbpTagHelperLocalizer tagHelperLocalizer, |
|||
IAbpEnumLocalizer abpEnumLocalizer, |
|||
IStringLocalizerFactory stringLocalizerFactory) |
|||
{ |
|||
HtmlEncoder = htmlEncoder; |
|||
_tagHelperLocalizer = tagHelperLocalizer; |
|||
_abpEnumLocalizer = abpEnumLocalizer; |
|||
_stringLocalizerFactory = stringLocalizerFactory; |
|||
} |
|||
|
|||
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
var childContent = await output.GetChildContentAsync(); |
|||
|
|||
NormalizeTagMode(context, output); |
|||
SetAttributes(context, output); |
|||
|
|||
var html = GetHtml(context, output); |
|||
SetContent(context, output, html, childContent); |
|||
} |
|||
|
|||
protected virtual void NormalizeTagMode(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
output.TagMode = TagMode.StartTagAndEndTag; |
|||
output.TagName = "div"; |
|||
} |
|||
|
|||
protected virtual void SetAttributes(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
var cssClass = "mb-3"; |
|||
output.Attributes.AddIfNotContains("class", cssClass); |
|||
} |
|||
|
|||
protected virtual void SetContent(TagHelperContext context, TagHelperOutput output, string html, TagHelperContent childContent) |
|||
{ |
|||
var content = childContent.GetContent(); |
|||
|
|||
if (!string.IsNullOrEmpty(content)) |
|||
{ |
|||
content = html + content; |
|||
} |
|||
else |
|||
{ |
|||
content = html; |
|||
} |
|||
|
|||
output.Content.SetHtmlContent(content); |
|||
} |
|||
|
|||
protected virtual string GetHtml(TagHelperContext context, TagHelperOutput output) |
|||
{ |
|||
var label = GetLabel(); |
|||
var text = GetText(); |
|||
var fieldId = GetFieldId(); |
|||
var value = TagHelper.AspFor.Model; |
|||
|
|||
var contentBuilder = new StringBuilder(); |
|||
var hidden = IsHidden() ? " d-none" : ""; |
|||
contentBuilder.AppendLine($"<div class=\"row{hidden}\">"); |
|||
|
|||
if (!TagHelper.SuppressLabel) |
|||
{ |
|||
var width = TagHelper.LabelWidth ?? |
|||
TagHelper.AspFor.ModelExplorer.GetAttribute<AbpText>()?.LabelWidth ?? |
|||
DefaultLabelWidth; |
|||
var labelWidth = $"col-{(int)width}"; |
|||
contentBuilder.AppendLine($"<div class=\"{labelWidth}\">"); |
|||
contentBuilder.AppendLine(label); |
|||
contentBuilder.AppendLine("</div>"); |
|||
} |
|||
|
|||
contentBuilder.AppendLine($"<div class=\"col\" id=\"{fieldId}\" data-value=\"{HtmlEncoder.Encode(value?.ToString() ?? string.Empty)}\">"); |
|||
contentBuilder.AppendLine(text); |
|||
contentBuilder.AppendLine("</div>"); |
|||
|
|||
contentBuilder.AppendLine("</div>"); |
|||
|
|||
return contentBuilder.ToString(); |
|||
} |
|||
|
|||
protected virtual string GetFieldId() |
|||
{ |
|||
return TagHelper.AspFor.Name |
|||
.Replace(".", "_") |
|||
.Replace("[", "_") |
|||
.Replace("]", "_"); |
|||
} |
|||
|
|||
protected virtual string GetLabel() |
|||
{ |
|||
var label = TagHelper.Label ?? |
|||
TagHelper.AspFor.Metadata.DisplayName ?? |
|||
TagHelper.AspFor.Metadata.PropertyName ?? |
|||
TagHelper.AspFor.Name; |
|||
|
|||
return $"<strong>{HtmlEncoder.Encode(label)}</strong>"; |
|||
} |
|||
|
|||
protected virtual string GetText() |
|||
{ |
|||
if (IsEnum()) |
|||
{ |
|||
return EnumText(); |
|||
} |
|||
if (IsDate()) |
|||
{ |
|||
return DateText(); |
|||
} |
|||
if (IsBoolean()) |
|||
{ |
|||
return BooleanText(); |
|||
} |
|||
if (IsFile()) |
|||
{ |
|||
return FileText(); |
|||
} |
|||
if (IsCollection()) |
|||
{ |
|||
return CollectionText(); |
|||
} |
|||
|
|||
return DefaultText(); |
|||
} |
|||
|
|||
protected virtual string DefaultText() |
|||
{ |
|||
var value = TagHelper.AspFor.Model; |
|||
if (value == null || string.IsNullOrWhiteSpace(value.ToString())) |
|||
{ |
|||
return "<span class=\"text-muted\">-</span>"; |
|||
} |
|||
return StringFormat(HtmlEncoder.Encode(value.ToString()!)); |
|||
} |
|||
|
|||
protected virtual string StringFormat(string? value) |
|||
{ |
|||
var format = GetFormatString(); |
|||
if (format.IsNullOrEmpty() || value.IsNullOrWhiteSpace()) |
|||
{ |
|||
return value ?? string.Empty; |
|||
} |
|||
return string.Format(format, value); |
|||
} |
|||
|
|||
private string GetFormatString() |
|||
{ |
|||
return TagHelper.Format ?? TagHelper.AspFor.ModelExplorer.GetAttribute<AbpText>()?.Format ?? string.Empty; |
|||
} |
|||
|
|||
protected virtual string EnumText() |
|||
{ |
|||
var value = TagHelper.AspFor.Model; |
|||
if (value == null) |
|||
{ |
|||
return DefaultText(); |
|||
} |
|||
|
|||
var modelType = value.GetType(); |
|||
var enumType = modelType.IsEnum ? modelType : Nullable.GetUnderlyingType(modelType); |
|||
var containerLocalizer = _tagHelperLocalizer.GetLocalizerOrNull(TagHelper.AspFor.ModelExplorer.Container.ModelType.Assembly); |
|||
var localizedMemberName = value == null ? "-" : _abpEnumLocalizer.GetString(enumType!, (int)value, |
|||
new[] |
|||
{ |
|||
containerLocalizer, |
|||
_stringLocalizerFactory.CreateDefaultOrNull() |
|||
}!); |
|||
return StringFormat(HtmlEncoder.Encode(localizedMemberName ?? string.Empty)); |
|||
} |
|||
|
|||
protected virtual string DateText() |
|||
{ |
|||
var value = TagHelper.AspFor.Model; |
|||
if (value == null) |
|||
{ |
|||
return DefaultText(); |
|||
} |
|||
|
|||
var format = GetFormatString(); |
|||
if (value is DateTime dateTime) |
|||
{ |
|||
return dateTime.ToString(format); |
|||
} |
|||
|
|||
if (value is DateTimeOffset dateTimeOffset) |
|||
{ |
|||
return dateTimeOffset.ToString(format); |
|||
} |
|||
|
|||
return StringFormat(value.ToString()); |
|||
} |
|||
|
|||
protected virtual string BooleanText() |
|||
{ |
|||
var value = TagHelper.AspFor.Model; |
|||
if (value == null) |
|||
{ |
|||
return DefaultText(); |
|||
} |
|||
|
|||
if (value is bool boolValue) |
|||
{ |
|||
return boolValue |
|||
? "<span class=\"fa fa-check\"></span>" |
|||
: "<span class=\"fa fa-times\"></span>"; |
|||
} |
|||
|
|||
return StringFormat(value.ToString()); |
|||
} |
|||
|
|||
protected virtual string FileText() |
|||
{ |
|||
return "<span class=\"text-info\"><i class=\"bi bi-file\"></i></span>"; |
|||
} |
|||
|
|||
protected virtual string CollectionText() |
|||
{ |
|||
var value = TagHelper.AspFor.Model; |
|||
if (value == null) |
|||
{ |
|||
return DefaultText(); |
|||
} |
|||
|
|||
//todo Consider supporting collection render.
|
|||
if (value is System.Collections.IEnumerable enumerable) |
|||
{ |
|||
var count = 0; |
|||
foreach (var item in enumerable) |
|||
{ |
|||
count++; |
|||
} |
|||
|
|||
return count.ToString(); |
|||
} |
|||
|
|||
return StringFormat(value.ToString()); |
|||
} |
|||
|
|||
protected virtual bool IsHidden() |
|||
{ |
|||
return TagHelper.AspFor.ModelExplorer.GetAttribute<HiddenInputAttribute>() != null; |
|||
} |
|||
|
|||
protected virtual bool IsEnum() |
|||
{ |
|||
return TagHelper.AspFor.ModelExplorer.Metadata.IsEnum; |
|||
} |
|||
|
|||
protected virtual bool IsDate() |
|||
{ |
|||
var modelType = TagHelper.AspFor.Metadata.ModelType; |
|||
return modelType == typeof(DateTime) || |
|||
modelType == typeof(DateTime?) || |
|||
modelType == typeof(DateTimeOffset) || |
|||
modelType == typeof(DateTimeOffset?); |
|||
} |
|||
|
|||
protected virtual bool IsBoolean() |
|||
{ |
|||
var modelType = TagHelper.AspFor.Metadata.ModelType; |
|||
return modelType == typeof(bool) || modelType == typeof(bool?); |
|||
} |
|||
|
|||
protected virtual bool IsFile() |
|||
{ |
|||
var modelType = TagHelper.AspFor.Metadata.ModelType; |
|||
return typeof(IFormFile).IsAssignableFrom(modelType) || |
|||
typeof(IEnumerable<IFormFile>).IsAssignableFrom(modelType); |
|||
} |
|||
|
|||
protected virtual bool IsCollection() |
|||
{ |
|||
var modelType = TagHelper.AspFor.Metadata.ModelType; |
|||
return typeof(System.Collections.IEnumerable).IsAssignableFrom(modelType) && |
|||
modelType != typeof(string); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue