// Copyright (c) MudBlazor 2021 // MudBlazor licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq.Expressions; using System.Text.Json; using Connected.Annotations; using Connected.Utilities; using Microsoft.AspNetCore.Components; namespace Connected.Components; [RequiresUnreferencedCode(CodeMessage.SerializationUnreferencedCodeMessage)] public partial class Column : UIComponent { [CascadingParameter] public DataGrid DataGrid { get; set; } //[CascadingParameter(Name = "HeaderCell")] public HeaderCell HeaderCell { get; set; } [Parameter] public T Value { get; set; } [Parameter] public EventCallback ValueChanged { get; set; } //[Parameter] public bool Visible { get; set; } = true; /// /// Specifies the name of the object's property bound to the column /// [Parameter] public string Field { get; set; } [Parameter] public Type FieldType { get; set; } [Parameter] public string Title { get; set; } [Parameter] public bool HideSmall { get; set; } [Parameter] public int FooterColSpan { get; set; } = 1; [Parameter] public int HeaderColSpan { get; set; } = 1; [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public RenderFragment> HeaderTemplate { get; set; } [Parameter] public RenderFragment> CellTemplate { get; set; } [Parameter] public RenderFragment> FooterTemplate { get; set; } [Parameter] public RenderFragment> GroupTemplate { get; set; } [Parameter] public Func GroupBy { get; set; } #region HeaderCell Properties [Parameter] public string HeaderClass { get; set; } [Parameter] public Func HeaderClassFunc { get; set; } [Parameter] public string HeaderStyle { get; set; } [Parameter] public Func HeaderStyleFunc { get; set; } /// /// Determines whether this columns data can be sorted. This overrides the Sortable parameter on the DataGrid. /// [Parameter] public bool? Sortable { get; set; } [Parameter] public bool? Resizable { get; set; } /// /// Determines whether this columns data can be filtered. This overrides the Filterable parameter on the DataGrid. /// [Parameter] public bool? Filterable { get; set; } [Parameter] public bool? ShowFilterIcon { get; set; } /// /// Determines whether this column can be hidden. This overrides the Hideable parameter on the DataGrid. /// [Parameter] public bool? Hideable { get; set; } [Parameter] public bool Hidden { get; set; } [Parameter] public EventCallback HiddenChanged { get; set; } /// /// Determines whether to show or hide column options. This overrides the ShowColumnOptions parameter on the DataGrid. /// [Parameter] public bool? ShowColumnOptions { get; set; } [Parameter] public Func SortBy { get { return GetLocalSortFunc(); } set { _sortBy = value; } } [Parameter] public SortDirection InitialDirection { get; set; } = SortDirection.None; [Parameter] public string SortIcon { get; set; } = Icons.Material.Filled.ArrowUpward; /// /// Specifies whether the column can be grouped. /// [Parameter] public bool? Groupable { get; set; } /// /// Specifies whether the column is grouped. /// [Parameter] public bool Grouping { get; set; } /// /// Specifies whether the column is sticky. /// [Parameter] public bool StickyLeft { get; set; } [Parameter] public bool StickyRight { get; set; } [Parameter] public RenderFragment> FilterTemplate { get; set; } private CultureInfo _culture; /// /// The culture used to represent this column and by the filtering input field. /// [Parameter] [Category(CategoryTypes.Table.Appearance)] public CultureInfo Culture { get => _culture ?? DataGrid?.Culture; set { _culture = value; } } #endregion #region Cell Properties [Parameter] public string CellClass { get; set; } [Parameter] public Func CellClassFunc { get; set; } [Parameter] public string CellStyle { get; set; } [Parameter] public Func CellStyleFunc { get; set; } [Parameter] public bool IsEditable { get; set; } = true; [Parameter] public RenderFragment> EditTemplate { get; set; } #endregion #region FooterCell Properties [Parameter] public string FooterClass { get; set; } [Parameter] public Func FooterClassFunc { get; set; } [Parameter] public string FooterStyle { get; set; } [Parameter] public Func FooterStyleFunc { get; set; } [Parameter] public bool EnableFooterSelection { get; set; } [Parameter] public AggregateDefinition AggregateDefinition { get; set; } #endregion public Action ColumnStateHasChanged { get; set; } internal string headerClassname => new CssBuilder("mud-table-cell") .AddClass("mud-table-cell-hide", HideSmall) .AddClass("sticky-left", StickyLeft) .AddClass("sticky-right", StickyRight) .AddClass(Class) .Build(); internal string cellClassname; //internal string cellClassname => // new CssBuilder("mud-table-cell") // .AddClass("mud-table-cell-hide", HideSmall) // .AddClass("sticky-right", StickyRight) // .AddClass(Class) // .Build(); internal string footerClassname => new CssBuilder("mud-table-cell") .AddClass("mud-table-cell-hide", HideSmall) .AddClass(Class) .Build(); internal bool grouping; #region Computed Properties internal Type dataType { get { if (FieldType != null) return FieldType; if (Field == null) return typeof(object); if (typeof(T) == typeof(IDictionary) && FieldType == null) throw new ArgumentNullException(nameof(FieldType)); var t = typeof(T).GetProperty(Field).PropertyType; return Nullable.GetUnderlyingType(t) ?? t; } } // This returns the data type for an object when T is an IDictionary. internal Type innerDataType { get { // Handle case where T is IDictionary. if (typeof(T) == typeof(IDictionary)) { // We need to get the actual type here so we need to look at actual data. // get the first item where we have a non-null value in the field to be filtered. var first = DataGrid.Items.FirstOrDefault(x => ((IDictionary)x)[Field] != null); if (first != null) { return ((IDictionary)first)[Field].GetType(); } else { return typeof(object); } } return dataType; } } internal bool isNumber { get { return FilterOperator.NumericTypes.Contains(dataType); } } internal string computedTitle { get { return Title ?? Field; } } internal bool groupable { get { return Groupable ?? DataGrid?.Groupable ?? false; } } internal bool filterable { get { return Filterable ?? DataGrid?.Filterable ?? false; } } #endregion internal int SortIndex { get; set; } = -1; internal HeaderCell HeaderCell { get; set; } private Func _sortBy; internal Func groupBy; internal HeaderContext headerContext; internal FilterContext filterContext; internal FooterContext footerContext; [UnconditionalSuppressMessage("Trimming", "IL2046: 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Suppressing because we annotating the whole component with RequiresUnreferencedCodeAttribute for information that generic type must be preserved.")] protected override void OnInitialized() { if (!Hideable.HasValue) Hideable = DataGrid?.Hideable; groupBy = GroupBy; CompileGroupBy(); if (groupable && Grouping) grouping = Grouping; if (null != DataGrid) DataGrid.AddColumn(this); // Add the HeaderContext headerContext = new HeaderContext(DataGrid); // Add the FilterContext if (filterable) { filterContext = new FilterContext(DataGrid); var operators = FilterOperator.GetOperatorByDataType(dataType); filterContext.FilterDefinition = new FilterDefinition() { DataGrid = this.DataGrid, Field = Field, FieldType = FieldType, Title = Title, Operator = operators.FirstOrDefault() }; } // Add the FooterContext footerContext = new FooterContext(DataGrid); } internal Func GetLocalSortFunc() { if (null == _sortBy) { var type = typeof(T); // set the default SortBy if (type == typeof(IDictionary)) { if (FieldType == null) throw new ArgumentNullException(nameof(FieldType)); var innerType = innerDataType; if (innerType == typeof(JsonElement)) { _sortBy = x => { var json = (JsonElement)(x as IDictionary)[Field]; if (FieldType == typeof(string)) return json.GetString(); else if (isNumber) return json.GetDouble(); else return json.GetRawText(); }; } else { _sortBy = x => Convert.ChangeType((x as IDictionary)[Field], FieldType); } } else { var parameter = Expression.Parameter(type, "x"); var field = Expression.Convert(Expression.Property(parameter, type.GetProperty(Field)), typeof(object)); _sortBy = Expression.Lambda>(field, parameter).Compile(); } } return _sortBy; } internal void CompileGroupBy() { if (groupBy == null && !string.IsNullOrWhiteSpace(Field)) { var type = typeof(T); // set the default GroupBy if (type == typeof(IDictionary)) { groupBy = x => (x as IDictionary)[Field]; } else { var parameter = Expression.Parameter(type, "x"); var field = Expression.Convert(Expression.Property(parameter, type.GetProperty(Field)), typeof(object)); groupBy = Expression.Lambda>(field, parameter).Compile(); } } } // Allows child components to change column grouping. internal void SetGrouping(bool g) { if (groupable) { grouping = g; DataGrid?.ChangedGrouping(this); } } /// /// This method's sole purpose is for the DataGrid to remove grouping in mass. /// internal void RemoveGrouping() { grouping = false; } public async Task HideAsync() { Hidden = true; await HiddenChanged.InvokeAsync(Hidden); } public async Task ShowAsync() { Hidden = false; await HiddenChanged.InvokeAsync(Hidden); } public async Task ToggleAsync() { Hidden = !Hidden; await HiddenChanged.InvokeAsync(Hidden); DataGrid.ExternalStateHasChanged(); } }