OneCompiler

MvcGrid HtmlGridExtensions

52

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,
}

}