Skip to content

Commit

Permalink
Merge pull request #207 from Lombiq/issue/SPAL-17
Browse files Browse the repository at this point in the history
SPAL-17: Dictitionary and URL generation extensions
  • Loading branch information
Psichorex authored Jul 11, 2023
2 parents 1687c7b + 718d036 commit b7b3e22
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ node_modules/
*.user
.pnpm-debug.log
.editorconfig
*.orig
19 changes: 19 additions & 0 deletions Lombiq.HelpfulLibraries.Common/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,25 @@ public static void AddRange<TKey, TValue>(
}
}

/// <summary>
/// Adds a collection of <paramref name="values"/> to the dictionary by generating a key for each item using the
/// <paramref name="keySelector"/>. If the selector's result in <see langword="null"/> that value is not added.
/// </summary>
public static void AddRange<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary,
IEnumerable<TValue> values,
Func<TValue, TKey> keySelector)
{
if (values == null) return;
foreach (var value in values)
{
if (keySelector(value) is { } key)
{
dictionary[key] = value;
}
}
}

/// <summary>
/// Adds a new item to the list identified by a key in the dictionary. If the item is already part of the list
/// then it won't add it again.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.ContentManagement;
using OrchardCore.DisplayManagement;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Threading.Tasks;

Expand All @@ -15,33 +16,49 @@ public static class ContentOrchardHelperExtensions
/// <summary>
/// Gets the given content item's edit URL.
/// </summary>
public static async Task<string> GetItemEditUrlAsync(
this IOrchardHelper orchardHelper,
ContentItem contentItem)
{
var urlHelperFactory = orchardHelper.HttpContext.RequestServices.GetService<IUrlHelperFactory>();
var viewContextAccessor = orchardHelper.HttpContext.RequestServices.GetService<ViewContextAccessor>();
var contentManager = orchardHelper.HttpContext.RequestServices.GetService<IContentManager>();
[Obsolete($"Use {nameof(GetItemEditUrl)} instead as this method does not need to be async.")]
public static Task<string> GetItemEditUrlAsync(this IOrchardHelper orchardHelper, ContentItem contentItem) =>
Task.FromResult(orchardHelper.GetItemEditUrl(contentItem));

/// <summary>
/// Gets the given content item's edit URL.
/// </summary>
[SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")]
public static string GetItemEditUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) =>
orchardHelper.GetItemEditUrl(contentItem.ContentItemId);

var urlHelper = urlHelperFactory.GetUrlHelper(viewContextAccessor.ViewContext);
var metadata = await contentManager.PopulateAspectAsync<ContentItemMetadata>(contentItem);
return urlHelper.Action(metadata.EditorRouteValues["action"].ToString(), metadata.EditorRouteValues);
/// <summary>
/// Gets the given content item's edit URL.
/// </summary>
[SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")]
public static string GetItemEditUrl(this IOrchardHelper orchardHelper, string contentItemId)
{
var urlHelper = orchardHelper.GetUrlHelper();
return urlHelper.EditContentItem(contentItemId);
}

/// <summary>
/// Gets the given content item's display URL.
/// </summary>
public static async Task<string> GetItemDisplayUrlAsync(
this IOrchardHelper orchardHelper,
ContentItem contentItem)
{
var urlHelperFactory = orchardHelper.HttpContext.RequestServices.GetService<IUrlHelperFactory>();
var viewContextAccessor = orchardHelper.HttpContext.RequestServices.GetService<ViewContextAccessor>();
var contentManager = orchardHelper.HttpContext.RequestServices.GetService<IContentManager>();
[Obsolete($"Use {nameof(GetItemDisplayUrl)} instead as this method does not need to be async.")]
public static Task<string> GetItemDisplayUrlAsync(this IOrchardHelper orchardHelper, ContentItem contentItem) =>
Task.FromResult(orchardHelper.GetItemDisplayUrl(contentItem));

var urlHelper = urlHelperFactory.GetUrlHelper(viewContextAccessor.ViewContext);
var metadata = await contentManager.PopulateAspectAsync<ContentItemMetadata>(contentItem);
return urlHelper.Action(metadata.DisplayRouteValues["action"].ToString(), metadata.DisplayRouteValues);
/// <summary>
/// Gets the given content item's display URL.
/// </summary>
[SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")]
public static string GetItemDisplayUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) =>
orchardHelper.GetItemDisplayUrl(contentItem.ContentItemId);

/// <summary>
/// Gets the given content item's display URL.
/// </summary>
[SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")]
public static string GetItemDisplayUrl(this IOrchardHelper orchardHelper, string contentItemId)
{
var urlHelper = orchardHelper.GetUrlHelper();
return urlHelper.DisplayContentItem(contentItemId);
}

/// <summary>
Expand Down Expand Up @@ -83,4 +100,13 @@ public static string Action<TController>(
params (string Key, object Value)[] additionalArguments)
where TController : ControllerBase =>
orchardHelper.HttpContext.Action(taskActionExpression.StripResult(), additionalArguments);

private static IUrlHelper GetUrlHelper(this IOrchardHelper orchardHelper)
{
var serviceProvider = orchardHelper.HttpContext.RequestServices;
var urlHelperFactory = serviceProvider.GetService<IUrlHelperFactory>();
var actionContext = serviceProvider.GetService<IActionContextAccessor>()?.ActionContext;

return urlHelperFactory.GetUrlHelper(actionContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ namespace Microsoft.AspNetCore.Mvc;
public static class OrchardControllerExtensions
{
/// <summary>
/// Uses <see cref="Routing.UrlHelperExtensions.DisplayContentItem"/> extension method to redirect to this <see
/// cref="ContentItem"/>'s display page.
/// Uses <see cref="Routing.UrlHelperExtensions.DisplayContentItem(Microsoft.AspNetCore.Mvc.IUrlHelper,OrchardCore.ContentManagement.IContent)"/>
/// extension method to redirect to this <see cref="ContentItem"/>'s display page.
/// </summary>
public static RedirectResult RedirectToContentDisplay(this Controller controller, IContent content) =>
controller.Redirect(controller.Url.DisplayContentItem(content));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,18 @@ public static string EditContentItemWithTab(this IUrlHelper helper, string tabId
/// Returns a relative URL for the <see cref="ContentItem"/> display page for the given <paramref name="content"/>.
/// </summary>
public static string DisplayContentItem(this IUrlHelper helper, IContent content) =>
helper.DisplayContentItem(content.ContentItem.ContentItemId);

/// <summary>
/// Returns a relative URL for the <see cref="ContentItem"/> display page for the given <paramref name="contentItemId"/>.
/// </summary>
public static string DisplayContentItem(this IUrlHelper helper, string contentItemId) =>
helper.Action(
"Display",
"Item",
new
{
area = OrchardCoreContentsArea,
content.ContentItem.ContentItemId,
contentItemId,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Lombiq.HelpfulLibraries.OrchardCore.TagHelpers;

[HtmlTargetElement("fieldset", Attributes = "asp-for")]
[HtmlTargetElement("fieldset", Attributes = "asp-for,label")]
public class EditorFieldSetTagHelper : TagHelper
{
private const string Class = "class";
Expand All @@ -37,6 +37,9 @@ public class EditorFieldSetTagHelper : TagHelper
[HtmlAttributeName("required")]
public bool IsRequired { get; set; }

[HtmlAttributeName("readonly")]
public bool IsReadOnly { get; set; }

[HtmlAttributeName("options")]
public IEnumerable<SelectListItem> Options { get; set; }

Expand Down Expand Up @@ -81,8 +84,13 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired)
Label.Html().Trim() + (isRequired ? " *" : string.Empty),
htmlAttributes: null);

var attributes = new Dictionary<string, object>();
AddBoolAttribute(attributes, IsReadOnly, "readonly");
AddBoolAttribute(attributes, isRequired, "required");

if (InputType.EqualsOrdinalIgnoreCase("checkbox"))
{
attributes[Class] = "custom-control-input";
var checkbox = _htmlGenerator.GenerateCheckBox(
ViewContext,
For.ModelExplorer,
Expand All @@ -93,9 +101,7 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired)
bool value => value,
_ => bool.TryParse(For.Model.ToString(), out var parsedValue) ? parsedValue : null,
},
new { @class = "form-check-input" });

if (isRequired) MakeRequired(checkbox);
attributes);

label.Attributes[Class] = "form-check-label";

Expand All @@ -112,6 +118,13 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired)
var inputType = InputType;
if (Options != null) inputType = "select";

attributes[Class] = "form-select";
if (inputType != "select")
{
attributes[Class] = "form-control";
attributes["type"] = InputType;
}

var input = inputType == "select"
? _htmlGenerator.GenerateSelect(
ViewContext,
Expand All @@ -120,23 +133,14 @@ private void AppendInputAndLabel(TagHelperOutput output, bool isRequired)
For.Name,
Options,
allowMultiple: false,
new
{
@class = "form-select",
})
attributes)
: _htmlGenerator.GenerateTextBox(
ViewContext,
For.ModelExplorer,
For.Name,
For.Model,
For.ModelExplorer.Metadata.EditFormatString,
new
{
@class = "form-control",
type = InputType,
});

if (isRequired) MakeRequired(input);
attributes);

AppendContent(output, label);
AppendContent(output, input);
Expand Down Expand Up @@ -164,5 +168,11 @@ private static bool HasRequiredAttribute(ModelExpression modelExpression) =>
.GetCustomAttributes(typeof(RequiredAttribute), inherit: false)
.FirstOrDefault() is RequiredAttribute;

private static void MakeRequired(TagBuilder tagBuilder) => tagBuilder.Attributes.Add("required", "required");
private static void AddBoolAttribute(IDictionary<string, object> attributes, bool value, string attributeName)
{
if (value)
{
attributes[attributeName] = attributeName;
}
}
}

0 comments on commit b7b3e22

Please sign in to comment.