MvcGrid HtmlGridExtensions
using MyCarrierPacketsUtility.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Mvc;
namespace NonFactors.Mvc.Grid
{
public static class HtmlGridExtensions
{
/// <summary>
/// Stores the html attributes for the grid cells in column level. The key is the column and the value is a dictionary of html attributes.
/// This is needed because the IGridColumn does not have a way to hold the html attributes for the cells, and we want to be able to
/// set different attributes for different columns.
/// </summary>
private static readonly IDictionary<IGridColumn, Dictionary<string, string>> _cellAttributes = new Dictionary<IGridColumn, Dictionary<string, string>>();
/// <summary>
/// Stores attribute mappings for each grid instance, associating cell identifiers with their corresponding attribute values.
/// </summary>
private static readonly Dictionary<IGrid, Dictionary<string, string>> _gridCellsAttributes = new Dictionary<IGrid, Dictionary<string, string>>();
/// <summary>
/// Assigns custom HTML attributes to the cells of the specified grid column.
/// Usage: .CellAttributed(new { @data_label = columns.GetColumnLabel(model => model.CustomerUserName), @data_other = "something" })
/// </summary>
/// <remarks>Use this method to add custom HTML attributes, such as CSS classes or data
/// attributes, to all cells in a grid column.
/// This method takes precedence over any attributes set at the grid level by GridColumnCellAttributed.</remarks>
/// <param name="column">The grid column to which the HTML attributes will be applied.</param>
/// <param name="htmlAttributes">An object containing the HTML attributes to assign to each cell in the column. Can be an anonymous object or
/// a dictionary. If null, no attributes are applied.</param>
/// <returns>The same grid column instance with the specified cell HTML attributes applied.</returns>
public static IGridColumn CellAttributed(this IGridColumn column, object htmlAttributes)
{
if (htmlAttributes == null)
{
return column;
}
_cellAttributes[column] = AnonymousObjectToDictionary(htmlAttributes);
return column;
}
/// <summary>
/// Adds or updates custom HTML attributes for all cells in the grid columns.
/// Usage: .GridColumnCellAttributed(new { @data_label = AttributedItem.Use_Column_Title_Or_Name, @data_other = "something" })
/// </summary>
/// <remarks>If the specified htmlAttributes parameter is null, no changes are made to the grid.
/// The attributes are applied to all cells in the grid columns for the current grid instance.</remarks>
/// <typeparam name="T">The type of the items in the grid.</typeparam>
/// <param name="html">The HTML grid to which the cell attributes will be applied.</param>
/// <param name="htmlAttributes">An object containing the HTML attributes to apply to each cell. Cannot be null.</param>
/// <returns>The same HTML grid instance with the specified cell attributes applied.</returns>
public static IHtmlGrid<T> GridColumnCellAttributed<T>(this IHtmlGrid<T> html, object htmlAttributes)
{
if (htmlAttributes == null)
{
return html;
}
_gridCellsAttributes[html.Grid] = AnonymousObjectToDictionary(htmlAttributes);
return html;
}
/// <summary>
/// Gets the display label for a specified column based on a property accessor expression.
/// </summary>
/// <remarks>If the column label is not explicitly set, the method attempts to generate a label
/// from the property name. This method is useful for dynamically retrieving user-friendly column headers in
/// grid-based UI components.</remarks>
/// <typeparam name="T">The type of the data item represented by the grid columns.</typeparam>
/// <typeparam name="TProperty">The type of the property accessed by the expression.</typeparam>
/// <param name="columns">The collection of grid columns to search for the label.</param>
/// <param name="accessor">An expression that identifies the property for which to retrieve the column label.</param>
/// <returns>The display label associated with the specified column, or an empty string if no label is found.</returns>
public static String GetColumnLabel<T, TProperty>(this IGridColumnsOf<T> columns, Expression<Func<T, TProperty>> accessor)
{
var columnName = nameof(accessor).PascalCaseToKababCase();
var columnLabel = string.Empty;
if (!string.IsNullOrEmpty(columnName))
{
columnLabel = (string)columns.Where(c => c.Name == columnName).FirstOrDefault()?.Title;
if (string.IsNullOrEmpty(columnLabel))
{
var me = accessor.Body as MemberExpression;
columnLabel = me?.Member.Name.ToWords();
}
}
return columnLabel;
}
/// <summary>
/// Retrieves the collection of HTML attributes associated with the specified grid column.
/// </summary>
/// <param name="column">The grid column for which to retrieve cell attributes. Cannot be null.</param>
/// <returns>A dictionary containing the HTML attribute names and values for the specified column. Returns an empty
/// dictionary if no attributes are defined.</returns>
public static Dictionary<string, string> GetCellAttributes(this IGridColumn column)
{
return _cellAttributes.TryGetValue(column, out var attrs)
? attrs
: new Dictionary<string, string>();
}
/// <summary>
/// Retrieves a dictionary of HTML attributes for a specific cell in the grid, based on the provided column.
/// </summary>
/// <remarks>If an attribute value is set to use the column's title or name, the method resolves
/// and substitutes the appropriate value. If neither is available, or if the attribute is set to use the column
/// index, the method uses the column's zero-based index within the grid.</remarks>
/// <param name="grid">The grid instance from which to retrieve cell attributes. Cannot be null.</param>
/// <param name="column">The column for which to obtain cell attributes. Cannot be null.</param>
/// <returns>A dictionary containing attribute names and their corresponding values for the specified cell. The
/// dictionary will be empty if no attributes are defined.</returns>
public static Dictionary<string, string> GetCellAttributes(this IGrid grid, IGridColumn column)
{
var attrs = new Dictionary<string, string>();
var attrsProcessed = new Dictionary<string, string>();
_gridCellsAttributes.TryGetValue(grid, out attrs);
if (attrs != null && attrs.Count > 0)
{
foreach (var item in attrs)
{
var itemValue = item.Value;
if (itemValue == AttributedItem.Use_Column_Title_Or_Name.ToString())
{
var propInfo = column.GetType().GetProperty("Title");
itemValue = propInfo?.GetValue(column)?.ToString();
if (string.IsNullOrEmpty(itemValue))
{
propInfo = column.GetType().GetProperty("Name");
itemValue = propInfo?.GetValue(column)?.ToString();
}
}
if (string.IsNullOrEmpty(itemValue) || itemValue == AttributedItem.Use_Column_Index.ToString())
{
itemValue = "" + grid.Columns.TakeWhile(x => x != column).Count();
}
attrsProcessed[item.Key] = itemValue;
}
}
return attrsProcessed;
}
/// <summary>
/// Converts an anonymous object containing HTML attribute names and values to a dictionary of string keys and
/// values.
/// </summary>
/// <remarks>This method is useful for scenarios where HTML attributes are specified using
/// anonymous objects, such as in ASP.NET MVC or Razor views. Property names are converted to their HTML
/// attribute equivalents (e.g., underscores to hyphens).</remarks>
/// <param name="htmlAttributes">An object whose properties represent HTML attribute names and values.
/// Typically an anonymous object, such as new { id = "main", @class = "container" }.</param>
/// <returns>A dictionary containing the HTML attribute names and their corresponding string values. If a property value
/// is null, its value in the dictionary will be null.</returns>
private static Dictionary<string, string> AnonymousObjectToDictionary(object htmlAttributes)
{
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
var dict = new Dictionary<string, string>();
foreach (var attr in attributes)
{
dict[attr.Key] = attr.Value?.ToString();
}
return dict;
}
/// <summary>
/// Retrieves the name of a property referenced by a strongly-typed lambda expression.
/// </summary>
/// <remarks>This method is useful for obtaining property names in a refactoring-safe manner.
/// The expression should be a simple property access (e.g., x => x.PropertyName).</remarks>
/// <typeparam name="T">The type of the object containing the property.</typeparam>
/// <typeparam name="TProperty">The type of the property referenced by the expression.</typeparam>
/// <param name="accessor">An expression that specifies the property whose name is to be retrieved. Must be a member access expression.</param>
/// <returns>The name of the property referenced by the expression, or null if the expression does not represent a member
/// access.</returns>
private static String nameof<T, TProperty>(Expression<Func<T, TProperty>> accessor)
{
var me = accessor.Body as MemberExpression;
return me?.Member.Name;
}
/// <summary>
/// This method would convert a PascalCase string to kebab-case.
/// For example, "PascalCaseString" would become "pascal-case-string".
/// </summary>
/// <param name="input">PascalCaseString</param>
/// <returns>kebab-case-string</returns>
public static string PascalCaseToKababCase(this string input)
{
if (string.IsNullOrEmpty(input))
{
return string.Empty;
}
string result = string.Concat(input.Select((x, i) =>
i > 0 && char.IsUpper(x) ? "-" + x.ToString() : x.ToString()
)).ToLower();
return result;
}
/// <summary>
/// This method would convert a PascalCase string to words separated by spaces.
/// For example, "PascalCaseString" would become "Pascal Case String".
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static string ToWords(this string input)
{
if (string.IsNullOrEmpty(input))
{
return string.Empty;
}
string result = string.Concat(input.Select((x, i) =>
i > 0 && char.IsUpper(x) ? " " + x.ToString() : x.ToString()
));
return result;
}
}
/// <summary>
/// Specifies special values that can be used to determine how column attributes are processed.
/// </summary>
public enum AttributedItem
{
Use_Column_Title_Or_Name,
Use_Column_Index,
}
}