namespace Framework.Session { using Database.dbo; using DatabaseIntegrate.dbo; using Framework.DataAccessLayer; using Framework.DataAccessLayer.DatabaseMemory; using Framework.Json; using Framework.Server; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static Framework.DataAccessLayer.UtilDalType; using static Framework.Json.Grid; internal enum GridRowEnum { None = 0, /// /// Filter row where user enters search text. /// Filter = 1, /// /// Data row loaded from database. /// Index = 2, /// /// Data row not yet inserted into database. /// New = 3, /// /// Data row at the end of the grid showing total. /// Total = 4 } /// /// Grid load and process. /// internal static class UtilGrid { /// /// Returns ColumnList for data grid. /// private static List LoadColumnList(Grid grid) { var configFieldDictionary = ConfigFieldDictionary(grid); AppJson appJson = grid.ComponentOwner(); var result = new List(); var fieldList = UtilDalType.TypeRowToFieldListDictionary(grid.TypeRow); foreach (var propertyInfo in UtilDalType.TypeRowToPropertyInfoList(grid.TypeRow)) { var field = fieldList[propertyInfo.Name]; configFieldDictionary.TryGetValue(propertyInfo.Name, out List configFieldList); if (configFieldList == null) // No ConfigField defined { configFieldList = new List { null }; } foreach (var configField in configFieldList) { NamingConvention namingConvention = appJson.NamingConventionInternal(grid.TypeRow); string columnText = namingConvention.ColumnTextInternal(grid.TypeRow, propertyInfo.Name, configField?.Text); bool isVisible = namingConvention.ColumnIsVisibleInternal(grid.TypeRow, propertyInfo.Name, configField?.IsVisible); bool isReadOnly = namingConvention.ColumnIsReadOnlyInternal(grid.TypeRow, propertyInfo.Name, configField?.IsReadOnly); double width = namingConvention.ColumnWidthInternal(grid.TypeRow, propertyInfo.Name, configField?.Width); bool isMultiline = namingConvention.ColumnIsMultilineInternal(grid.TypeRow, propertyInfo.Name, configField?.IsMultiline); double sort = namingConvention.ColumnSortInternal(grid.TypeRow, propertyInfo.Name, field, configField?.Sort); result.Add(new GridColumn { FieldNameCSharp = field.FieldNameCSharp, ColumnText = columnText, Description = configField?.Description, IsVisible = isVisible, IsReadOnly = isReadOnly, Width = width, IsMultiline = isMultiline, Sort = sort, SortField = field.Sort }); } } result = result .Where(item => item.IsVisible == true) .OrderBy(item => item.Sort) .ThenBy(item => item.SortField) // Make it deterministic if multiple columns have same Sort. .ToList(); // Column.Id int columnId = 0; foreach (var item in result) { item.Id = columnId += 1; } return result; } /// /// Returns RowStateList for data grid. /// private static List LoadRowStateList(Grid grid) { var result = new List { new GridRowState { RowId = null, RowEnum = GridRowEnum.Filter } }; int rowId = 0; foreach (var row in grid.RowListInternal) { rowId += 1; result.Add(new GridRowState { RowId = rowId, RowEnum = GridRowEnum.Index }); } result.Add(new GridRowState { RowId = null, RowEnum = GridRowEnum.New }); // RowState.Id int count = 0; foreach (var rowState in result) { count += 1; rowState.Id = count; } return result; } private static TValue GetOrAdd(this Dictionary list, TKey key, Func valueFactory, out bool isAdded) { isAdded = false; if (!list.TryGetValue(key, out TValue result)) { isAdded = true; result = valueFactory(key); list.Add(key, result); } return result; } /// /// Overload. /// private static TValue GetOrAdd(this Dictionary list, TKey key, Func valueFactory) { return list.GetOrAdd(key, valueFactory, out bool _); } private static void RenderAnnotation(Grid grid, GridCell cell, string fieldNameCSharp, GridRowEnum rowEnum, Row row, bool isReadOnly, bool isMultiline) { var result = new Grid.AnnotationResult { IsReadOnly = isReadOnly, IsMultiline = isMultiline }; grid.CellAnnotationInternal(rowEnum, row, fieldNameCSharp, result); if (isReadOnly) { // Annotation can not override IsReadOnly if true in configuration. result.IsReadOnly = true; } cell.Html = UtilFramework.StringNull(result.Html); cell.HtmlIsEdit = result.HtmlIsEdit; cell.HtmlLeft = UtilFramework.StringNull(result.HtmlLeft); cell.HtmlRight = UtilFramework.StringNull(result.HtmlRight); cell.IsReadOnly = result.IsReadOnly; cell.IsMultiline = result.IsMultiline; cell.IsPassword = result.IsPassword; cell.Align = result.Align; cell.IsFileUpload = result.IsFileUpload; if (result.PlaceHolder != null) { cell.Placeholder = result.PlaceHolder; // Override default "Search", "New" } } /// /// Render HeaderColumn. /// private static GridCell RenderCellFilterHeaderColumn(Grid grid, GridRowState rowState, GridColumn column, Dictionary<(int, int, GridCellEnum), GridCell> cellList) { var result = cellList.GetOrAdd((column.Id, rowState.Id, GridCellEnum.HeaderColumn), (key) => new GridCell { ColumnId = key.Item1, RowStateId = key.Item2, CellEnum = key.Item3, ColumnText = column.ColumnText, Description = column.Description, }); grid.CellList.Add(result); result.IsSort = GridSortValue.IsSortGet(grid, column.FieldNameCSharp); result.IsVisibleScroll = true; return result; } /// /// Render HeaderRow. /// private static GridCell RenderCellFilterHeaderRow(Grid grid, GridRowState rowState, GridColumn column, Dictionary<(int, int, GridCellEnum), GridCell> cellList) { var result = cellList.GetOrAdd((column.Id, rowState.Id, GridCellEnum.HeaderRow), (key) => new GridCell { ColumnId = key.Item1, RowStateId = key.Item2, CellEnum = key.Item3, ColumnText = column.ColumnText, }); grid.CellList.Add(result); result.IsVisibleScroll = true; return result; } /// /// Render Filter (search) value. /// private static GridCell RenderCellFilterValue(Grid grid, GridRowState rowState, GridColumn column, Dictionary<(int, int, GridCellEnum), GridCell> cellList, Dictionary filterValueList) { filterValueList.TryGetValue(column.FieldNameCSharp, out GridFilterValue filterValue); var result = cellList.GetOrAdd((column.Id, rowState.Id, GridCellEnum.Filter), (key) => new GridCell { ColumnId = key.Item1, RowStateId = key.Item2, CellEnum = key.Item3, Placeholder = "Search" }, out bool isAdded); grid.CellList.Add(result); result.Text = filterValue?.Text; if (column.FieldNameCSharp == filterValue?.FieldNameCSharp && filterValue?.IsFocus == true) { result.TextLeave = filterValue.TextLeave; } result.IsVisibleScroll = true; if (isAdded) { RenderAnnotation(grid, result, column.FieldNameCSharp, rowState.RowEnum, null, result.IsReadOnly, result.IsMultiline); // Cell Filter } return result; } /// /// Render Cell Index. /// private static GridCell RenderCellIndex(Grid grid, GridRowState rowState, GridColumn column, Dictionary<(int, int, GridCellEnum), GridCell> cellList, Dictionary fieldList, GridCell cell, bool isTextLeave) { Row row; if (rowState.Row != null) { row = rowState.Row; } else { row = grid.RowListInternal[rowState.RowId.Value - 1]; } var result = cellList.GetOrAdd((column.Id, rowState.Id, GridCellEnum.Index), (key) => new GridCell { ColumnId = key.Item1, RowStateId = key.Item2, CellEnum = key.Item3, }, out bool isAdded); grid.CellList.Add(result); if (result.RowStateId == cell?.RowStateId) // Cell on same row. { isAdded = true; // Trigger Cell.Text update } if (isAdded) { var field = fieldList[column.FieldNameCSharp]; string text = null; object value = field.PropertyInfo.GetValue(row); if (value != null) { text = field.FrameworkType().CellTextFromValue(value); text = grid.CellTextInternal(row, field.PropertyInfo.Name, text); // Custom convert database value to cell text. text = UtilFramework.StringNull(text); } result.TextLeave = null; if (result.ErrorParse == null) // Do not override user entered text as long as in ErrorParse mode. { if (result == cell && isTextLeave) { result.TextLeave = UtilFramework.StringEmpty(text); // Do not change text while user modifies. } else { result.Text = text; } } } result.IsVisibleScroll = true; if (grid.GridLookup != null) { if (grid.GridLookupDestRowStateId == rowState.Id && grid.GridLookupDestFieldNameCSharp == column.FieldNameCSharp) { result.GridLookup = grid.GridLookup; } } if (isAdded) { RenderAnnotation(grid, result, column.FieldNameCSharp, rowState.RowEnum, row, column.IsReadOnly, column.IsMultiline); // Cell } return result; } /// /// Render new field. /// private static GridCell RenderCellNew(Grid grid, GridRowState rowState, GridColumn column, Dictionary<(int, int, GridCellEnum), GridCell> cellList) { var result = cellList.GetOrAdd((column.Id, rowState.Id, GridCellEnum.New), (key) => new GridCell { ColumnId = key.Item1, RowStateId = key.Item2, CellEnum = key.Item3, Placeholder = "New", }, out bool isAdded); grid.CellList.Add(result); result.IsVisibleScroll = true; if (isAdded) { RenderAnnotation(grid, result, column.FieldNameCSharp, rowState.RowEnum, null, result.IsReadOnly, result.IsMultiline); // Cell New } return result; } /// /// Render data grid in table mode. /// private static void RenderModeTable(Grid grid, FrameworkConfigGridIntegrate configGrid, List columnList, List rowStateList, Dictionary<(int, int, GridCellEnum), GridCell> cellList, GridCell cell, bool isTextLeave) { // Render Grid.StyleColumnList grid.StyleColumnList = new List(); double widthValueTotal = 0; double widthCount = 0; foreach (var column in columnList) { if (column.WidthValue != null) { widthCount += 1; widthValueTotal += column.WidthValue.Value; } } double widthValueAvg = (100 - widthValueTotal) / (columnList.Count - widthCount); foreach (var column in columnList) { string width; double? widthValue; string widthUnit; if (column.WidthValue != null) { width = column.WidthValue + "%"; widthValue = column.WidthValue; widthUnit = "%"; } else { width = widthValueAvg + "%"; widthValue = widthValueAvg; widthUnit = "%"; } if (column != columnList.Last()) { grid.StyleColumnList.Add(new GridStyleColumn { Width = width, WidthValue = widthValue, WidthUnit = widthUnit }); } else { grid.StyleColumnList.Add(new GridStyleColumn()); } } // Render Grid.StyleRowList grid.StyleRowList = new List(); foreach (var rowState in rowStateList) { switch (rowState.RowEnum) { case GridRowEnum.Filter: if ((configGrid?.IsShowHeader).GetValueOrDefault(true)) { grid.StyleRowList.Add(new GridStyleRow()); // See also enum GridCellEnum.HeaderColumn grid.StyleRowList.Add(new GridStyleRow()); // Filter value } break; case GridRowEnum.Index: grid.StyleRowList.Add(new GridStyleRow()); // Cell value break; case GridRowEnum.New: if ((configGrid?.IsAllowInsert).GetValueOrDefault(true)) { grid.StyleRowList.Add(new GridStyleRow()); // Cell value } break; } } // Render Grid.CellList var fieldList = UtilDalType.TypeRowToFieldListDictionary(grid.TypeRow); var filter = new GridFilter(grid); var filterValueList = filter.FilterValueList(); foreach (var rowState in rowStateList) { // Render Filter if (rowState.RowEnum == GridRowEnum.Filter && (configGrid?.IsShowHeader).GetValueOrDefault(true)) { // Filter Header foreach (var column in columnList) { RenderCellFilterHeaderColumn(grid, rowState, column, cellList); } // Filter Value foreach (var column in columnList) { RenderCellFilterValue(grid, rowState, column, cellList, filterValueList); } } // Render Index if (rowState.RowEnum == GridRowEnum.Index) { foreach (var column in columnList) { RenderCellIndex(grid, rowState, column, cellList, fieldList, cell, isTextLeave); } } // Render New if (rowState.RowEnum == GridRowEnum.New && (configGrid?.IsAllowInsert).GetValueOrDefault(true)) { foreach (var column in columnList) { RenderCellNew(grid, rowState, column, cellList); } } } } /// /// Render data grid in stack mode. /// private static void RenderModeStack(Grid grid, FrameworkConfigGridIntegrate configGrid, List columnList, List rowStateList, Dictionary<(int, int, GridCellEnum), GridCell> cellList, GridCell cell, bool isTextLeave) { // Render Grid.StyleColumnList grid.StyleColumnList = new List(); grid.StyleColumnList.Add(new GridStyleColumn()); // One column for ModeStack // Render Grid.StyleRowList grid.StyleRowList = new List(); foreach (var rowState in rowStateList) { foreach (var column in columnList) { switch (rowState.RowEnum) { case GridRowEnum.Filter: if ((configGrid?.IsShowHeader).GetValueOrDefault(true)) { grid.StyleRowList.Add(new GridStyleRow()); // See also enum GridCellEnum.HeaderColumn grid.StyleRowList.Add(new GridStyleRow()); // Filter value } break; case GridRowEnum.Index: grid.StyleRowList.Add(new GridStyleRow()); // See also enum GridCellEnum.HeaderRow grid.StyleRowList.Add(new GridStyleRow()); // Cell value break; case GridRowEnum.New: if ((configGrid?.IsAllowInsert).GetValueOrDefault(true)) { grid.StyleRowList.Add(new GridStyleRow()); // See also enum GridCellEnum.HeaderRow grid.StyleRowList.Add(new GridStyleRow()); // Cell value } break; } } } // Render Grid.CellList var fieldList = UtilDalType.TypeRowToFieldListDictionary(grid.TypeRow); var filter = new GridFilter(grid); var filterValueList = filter.FilterValueList(); int count = 0; foreach (var rowState in rowStateList) { count += 1; // Render Filter if (rowState.RowEnum == GridRowEnum.Filter && (configGrid?.IsShowHeader).GetValueOrDefault(true)) { foreach (var column in columnList) { // Filter Header RenderCellFilterHeaderColumn(grid, rowState, column, cellList); // Filter Value RenderCellFilterValue(grid, rowState, column, cellList, filterValueList); } } // Render Index if (rowState.RowEnum == GridRowEnum.Index) { foreach (var column in columnList) { // Filter Header RenderCellFilterHeaderRow(grid, rowState, column, cellList).IsOdd = count % 2 == 1; // Index RenderCellIndex(grid, rowState, column, cellList, fieldList, cell, isTextLeave).IsOdd = count % 2 == 1; } } // Render New if (rowState.RowEnum == GridRowEnum.New && (configGrid?.IsAllowInsert).GetValueOrDefault(true)) { foreach (var column in columnList) { // Filter Header RenderCellFilterHeaderRow(grid, rowState, column, cellList); // Index RenderCellNew(grid, rowState, column, cellList); } } } } /// /// Render Grid.CellList. /// /// If not null, method GridCellText(); is called for all cells on this data row. private static void Render(Grid grid, GridCell cell = null, bool isTextLeave = true) { UtilFramework.LogDebug(string.Format("RENDER ({0}) IsCell={1};", grid.TypeRow?.Name, cell != null)); var configGrid = grid.ConfigGrid; // IsVisibleScroll int count = 0; double widthMax = ConfigWidthMax(configGrid); double width = 0; bool isFirst = true; foreach (var column in grid.ColumnList) // Contains visible columns only. { count += 1; column.IsVisibleScroll = count - 1 >= grid.OffsetColumn && (width + column.Width <= widthMax || isFirst); if (column.IsVisibleScroll) { width += column.Width; isFirst = false; } } var columnList = grid.ColumnList.Where(item => item.IsVisibleScroll).ToList(); // Column width foreach (var item in columnList) { if (item.WidthValue == null) // User might have changed column width with mouse { item.WidthValue = Math.Round(100 / width * item.Width); } } foreach (var rowState in grid.RowStateList) { rowState.IsVisibleScroll = true; } var rowStateList = grid.RowStateList.Where(item => item.IsVisibleScroll).ToList(); // Grid.CellList clear var cellList = grid.CellList.ToDictionary(item => (item.ColumnId, item.RowStateId, item.CellEnum)); // Key (ColumnId, RowState, CellEnum) grid.CellList = new List(); foreach (var cellLocal in cellList.Values) { cellLocal.IsVisibleScroll = false; } if (grid.Mode == GridMode.Table) { RenderModeTable(grid, configGrid, columnList, rowStateList, cellList, cell, isTextLeave); } else { RenderModeStack(grid, configGrid, columnList, rowStateList, cellList, cell, isTextLeave); } // IsHidePagination grid.IsHidePagination = !(configGrid?.IsShowPagination).GetValueOrDefault(true); // IsShowConfig var settingResult = AppJson.SettingInternal(grid, new AppJson.SettingArgs { Grid = grid }); grid.IsShowConfig = settingResult.GridIsShowConfig; grid.IsShowConfigDeveloper = settingResult.GridIsShowConfigDeveloper; // Preserve cell in ErrorParse or ErrorSave state foreach (var cellLocal in cellList.Values) { if (cellLocal.IsVisibleScroll == false) // Cell not visible { if (cellLocal.ErrorParse != null || cellLocal.ErrorSave != null || cellLocal.Warning != null) { grid.CellList.Add(cellLocal); // Preserve cell } } } // Cell.Id count = 0; foreach (var cellLocal in grid.CellList) { count += 1; cellLocal.Id = count; } // Cell.IsSelect foreach (var cellLocal in grid.CellList) { GridRowState rowState = grid.RowStateList[cellLocal.RowStateId - 1]; cellLocal.IsSelect = rowState.IsSelect; } } /// /// Load (full) data grid with config. /// /// Query for rows. If null, only config is loaded. private static async Task LoadFullAsync(Grid grid, IQueryable query) { if (query != null) { UtilFramework.Assert(grid.TypeRow == query.ElementType); } // Get config grid and field query string tableNameCSharp = UtilDalType.TypeRowToTableNameCSharp(grid.TypeRow); Grid.QueryConfigResult queryConfigResult; if (grid.IsGridLookup == false) { queryConfigResult = grid.QueryConfigInternal(tableNameCSharp); } else { queryConfigResult = grid.GridDest.LookupQueryConfigInternal(grid, tableNameCSharp); } // Display grid mode grid.Mode = queryConfigResult.GridMode; // Load config grid if (queryConfigResult.ConfigGridQuery != null) { var configGridList = await queryConfigResult.ConfigGridQuery.QueryExecuteAsync(); // In memory ConfigName filter grid.ConfigGrid = configGridList.Where(item => item.ConfigName == queryConfigResult.ConfigName).SingleOrDefault(); } var configGrid = grid.ConfigGrid; // Load row if (query != null) { query = Data.QuerySkipTake(query, 0, ConfigRowCountMax(configGrid)); } // Load config field (Task) var configFieldListTask = Task.FromResult(new List()); if (queryConfigResult.ConfigFieldQuery != null) { configFieldListTask = queryConfigResult.ConfigFieldQuery.QueryExecuteAsync(); } // Load row (Task) var rowListTask = query?.QueryExecuteAsync(); // Load config field and row in parallel if (rowListTask == null) { await Task.WhenAll(configFieldListTask); } else { await Task.WhenAll(configFieldListTask, rowListTask); } // Load config field grid.ConfigFieldList = configFieldListTask.Result; // In memory ConfigName filter grid.ConfigFieldList = grid.ConfigFieldList.Where(item => item.ConfigName == queryConfigResult.ConfigName).ToList(); // RowList if (query != null) { grid.RowListInternal = rowListTask.Result; } // Truncate grid.TruncateInternal(grid.RowListInternal); // ColumnList grid.ColumnList = LoadColumnList(grid); } /// /// Load (reload) data grid. Config is not loaded again. /// private static async Task LoadReloadAsync(Grid grid, IQueryable query) { var configGrid = grid.ConfigGrid; // Filter if (grid.FilterValueList != null) { foreach (var filter in grid.FilterValueList) { if (!filter.IsClear && !(filter.FilterOperator == FilterOperator.None)) { query = Data.QueryFilter(query, filter.FieldNameCSharp, filter.FilterValue, filter.FilterOperator); } } } // Sort if (grid.SortValueList != null) { IOrderedQueryable queryOrder = null; bool isFirst = true; foreach (var value in grid.SortValueList) { if (isFirst) { isFirst = false; queryOrder = Data.QueryOrderBy(query, value.FieldNameCSharp, value.IsSort); ; } else { queryOrder = Data.QueryOrderByThenBy(queryOrder, value.FieldNameCSharp, value.IsSort); ; } } if (queryOrder != null) { query = queryOrder; } } // Skip, Take query = Data.QuerySkipTake(query, grid.OffsetRow, ConfigRowCountMax(configGrid)); // Load row grid.RowListInternal = await query.QueryExecuteAsync(); // Trunctae grid.TruncateInternal(grid.RowListInternal); } /// /// Load or reload data grid. Also first load for lookup. /// private static async Task LoadAsync(Grid grid, IQueryable query, bool isRowSelectFirst) { Type typeRowOld = grid.TypeRow; grid.TypeRow = query?.ElementType; grid.DatabaseEnum = DatabaseMemoryInternal.DatabaseEnum(query); if (grid.TypeRow == null) { grid.ColumnList = new List(); ; grid.RowStateList = new List(); grid.RowListInternal = new List(); grid.CellList = new List(); grid.GridLookup = null; Render(grid); return; } // Load full (with config) or reload if (typeRowOld != query?.ElementType) { // ColumnList, RowList await LoadFullAsync(grid, query); } else { // RowList await LoadReloadAsync(grid, query); } // RowStateList grid.RowStateList = LoadRowStateList(grid); // Select first row on data grid. But not on lookup grid. if (grid.IsGridLookup == false && isRowSelectFirst) { await RowSelectAsync(grid, grid.RowStateList.Where(item => item.RowEnum == GridRowEnum.Index).FirstOrDefault()); } grid.CellList = new List(); Render(grid); } /// /// Load or reload data grid and (re)render. /// public static async Task LoadAsync(Grid grid) { IQueryable query; bool isRowSelectFirst = false; if (grid.IsGridLookup == false) { grid.QueryInternal(out query, out isRowSelectFirst); } else { GridLookupToGridDest(grid, out var gridDest, out var rowDest, out string fieldNameCSharpDest, out var cellDest); query = gridDest.LookupQueryInternal(rowDest, fieldNameCSharpDest, cellDest.Text); } await LoadAsync(grid, query, isRowSelectFirst); } /// /// Load or reload data grid configuration only. For example after config change. See also class PageConfigGrid. /// internal static async Task LoadConfigOnlyAsync(Grid grid) { await LoadFullAsync(grid, null); grid.CellList = new List(); Render(grid); } /// /// GridLookup to GridDest. GridDest is the destination grid to write to when user selects a row in lookup grid. /// private static void GridLookupToGridDest(Grid gridLookup, out Grid gridDest, out Row rowDest, out string fieldNameCSharpDest, out GridCell cellDest) { UtilFramework.Assert(gridLookup.IsGridLookup); gridDest = gridLookup.GridDest; var rowStateDest = gridDest.RowStateList[gridLookup.GridLookupDestRowStateId.Value - 1]; rowDest = null; if (rowStateDest.RowId != null) { rowDest = gridDest.RowListInternal[rowStateDest.RowId.Value - 1]; } fieldNameCSharpDest = gridLookup.GridLookupDestFieldNameCSharp; var fieldNameCSharpDestLocal = fieldNameCSharpDest; var columnDest = gridDest.ColumnList.Where(item => item.FieldNameCSharp == fieldNameCSharpDestLocal).Single(); var rowStateDestLocal = rowStateDest; cellDest = gridDest.CellList.Where(item => item.RowStateId == rowStateDestLocal.Id && item.ColumnId == columnDest.Id).Single(); } /// /// Close lookup data grid. /// private static void GridLookupClose(Grid grid) { UtilFramework.Assert(grid.IsGridLookup == false); if (grid.GridLookup != null) { UtilFramework.Assert(grid.GridLookup.IsGridLookup); GridLookupToGridDest(grid.GridLookup, out var _, out var _, out var _, out var cellDest); cellDest.GridLookup = null; grid.GridLookup = null; } } /// /// Open lookup data grid. /// private static void GridLookupOpen(Grid grid, GridRowState rowState, string fieldNameCSharp, GridCell cell) { if (grid.GridLookup?.GridLookupDestFieldNameCSharp != fieldNameCSharp) { GridLookupClose(grid); // User changed the column focus on same row without closing lookup data grid } if (grid.GridLookup == null) { UtilFramework.Assert(cell.GridLookup == null); Grid gridLookup = new Grid(grid); grid.GridLookup = gridLookup; cell.GridLookup = gridLookup; gridLookup.IsHide = true; // Render data grid to cell. See also property GridCell.GridLookup gridLookup.IsGridLookup = true; grid.GridLookup.GridDest = grid; gridLookup.GridLookupDestRowStateId = rowState.Id; gridLookup.GridLookupDestFieldNameCSharp = fieldNameCSharp; } UtilFramework.Assert(grid.GridLookup.IsGridLookup); UtilFramework.Assert(grid.GridLookup.GridLookupDestRowStateId == rowState.Id && cell.RowStateId == rowState.Id); UtilFramework.Assert(grid.GridLookup.GridLookupDestFieldNameCSharp == fieldNameCSharp && grid.ColumnList[cell.ColumnId - 1].FieldNameCSharp == fieldNameCSharp); UtilFramework.Assert(grid.GridLookup.GridDest == grid); } /// /// Returns RowCountMax rows to load. /// private static int ConfigRowCountMax(FrameworkConfigGridIntegrate configGrid) { return configGrid?.RowCountMax == null ? 10 : configGrid.RowCountMax.Value; // By default load 10 rows. } /// /// Returns ColumnCountMax of columns to render. /// private static double ConfigWidthMax(FrameworkConfigGridIntegrate configGrid) { return configGrid?.WidthMax == null ? 5 : configGrid.WidthMax.Value; } /// /// Returns ColumnCountMax of columns to render. /// private static int ConfigColumnCountMax(FrameworkConfigGridIntegrate configGrid) { return 5; } /// /// Returns data grid field configuration records. (FieldName, FrameworkConfigFieldIntegrate). One FieldName can have multiple FrameworkConfigFieldIntegrate because of InstanceName. /// private static Dictionary> ConfigFieldDictionary(Grid grid) { var result = new Dictionary>(); foreach (var item in grid.ConfigFieldList) { if (!result.ContainsKey(item.FieldNameCSharp)) { result[item.FieldNameCSharp] = new List(); } result[item.FieldNameCSharp].Add(item); } return result; } /// /// After load, if no row in data grid is selected, select first row. /// public static async Task RowSelectAsync(Grid grid, GridRowState rowState, bool isRender = false) { foreach (var item in grid.RowStateList) { item.IsSelect = false; } if (rowState != null) { Row row = grid.RowListInternal[rowState.RowId.Value - 1]; rowState.IsSelect = true; UtilFramework.Assert(row == grid.RowSelect); await grid.RowSelectAsync(); } if (isRender) { Render(grid); } } /// /// User modified text, now open lookup window. /// private static async Task ProcessGridLookupOpenAsync(Grid grid, GridRowState rowState, GridColumn column, GridCell cell) { var query = grid.LookupQueryInternal(rowState.Row, column.FieldNameCSharp, cell.Text); if (query != null) { GridLookupOpen(grid, rowState, column.FieldNameCSharp, cell); await LoadAsync(grid.GridLookup, query, isRowSelectFirst: false); } } /// /// Process incoming RequestJson. /// public static async Task ProcessAsync(AppJson appJson) { // IsClickSort (user clicked column sort) await ProcessIsClickSortAsync(appJson); // CellIsModify (user changed text) await ProcessCellIsModify(appJson); // IsClickEnum (user clicked paging button) await ProcessIsClickEnum(appJson); // RowIsClick (user clicked data row) await ProcessRowIsClickAsync(appJson); // IsClickConfig (user clicked column configuration button) await ProcessIsClickConfigAsync(appJson); // IsTextLeave (user clicked tab button to leave cell) ProcessIsTextLeave(appJson); // StyleColumn (user changed with mouse column width) ProcessStyleColumn(appJson); } /// /// User clicked column sort. /// private static async Task ProcessIsClickSortAsync(AppJson appJson) { if (UtilSession.Request(appJson, CommandEnum.GridIsClickSort, out CommandJson commandJson, out Grid grid)) { GridCell cell = grid.CellList[commandJson.GridCellId - 1]; GridColumn column = grid.ColumnList[cell.ColumnId - 1]; GridSortValue.IsSortSwitch(grid, column.FieldNameCSharp); grid.OffsetRow = 0; // Page to first row after user clicked column sort. await LoadAsync(grid); } } /// /// User clicked column header configuration icon. /// private static async Task ProcessIsClickConfigAsync(AppJson appJson) { if (UtilSession.Request(appJson, CommandEnum.GridIsClickConfig, out CommandJson commandJson, out Grid grid)) { if (grid.IsShowConfig || grid.IsShowConfigDeveloper) { GridCell cell = grid.CellList[commandJson.GridCellId - 1]; GridColumn column = grid.ColumnList[cell.ColumnId - 1]; Page page = grid.ComponentOwner(); string tableNameCSharp = UtilDalType.TypeRowToTableNameCSharp(grid.TypeRow); var pageConfigGrid = new PageConfigGrid(page, tableNameCSharp, column.FieldNameCSharp, grid.ConfigGrid?.ConfigName, isDeveloper: false); // Never show data grid header column config in developer config (mode). await pageConfigGrid.InitAsync(); } } } /// /// Synchronize Cell.Text when user leaves field. /// private static void ProcessIsTextLeave(AppJson appJson) { if (UtilSession.Request(appJson, CommandEnum.GridIsTextLeave, out CommandJson commandJson, out Grid grid)) { GridCell cell = grid.CellList[commandJson.GridCellId - 1]; cell.Text = cell.TextLeave; cell.TextLeave = null; } } /// /// Send column width to server after user resizes a column. /// private static void ProcessStyleColumn(AppJson appJson) { if (UtilSession.Request(appJson, CommandEnum.StyleColumnWidth, out CommandJson commandJson, out Grid grid)) { var columnList = grid.ColumnList.Where(item => item.IsVisibleScroll).ToArray(); columnList[commandJson.ResizeColumnIndex].WidthValue = commandJson.ResizeColumnWidthValue; Render(grid); // Update Grid.StyleColumnList } } /// /// Show warning message on cell if row could not be saved. /// private static void ProcessCellIsModifyWarningNotSaved(Grid grid, GridCell cell) { foreach (var item in grid.CellList) { if (item.RowStateId == cell.RowStateId) { if (item.IsModified) { if (item.ErrorParse == null && item.ErrorSave == null) { item.Warning = "Not saved because of other errors!"; } } else { item.Warning = null; } } } } /// /// Call after successful save. /// private static void ProcessCellIsModifyReset(Grid grid, GridCell cell) { foreach (var item in grid.CellList) { if (item.RowStateId == cell.RowStateId) { item.IsModified = false; item.TextOld = null; item.ErrorParse = null; item.ErrorSave = null; item.Warning = null; } } } /// /// Parse /// private static async Task ProcessCellIsModifyParseAsync(Grid grid, GridRowEnum rowEnum, Row row, GridColumn column, Field field, GridCell cell, CommandJson commandJson, FileUploadArgs fileUpload) { cell.ErrorParse = null; // Parse try { // Validate if (cell.Text == null) { if (!UtilFramework.IsNullable(field.PropertyInfo.PropertyType)) { throw new Exception("Value can not be null!"); } } // Parse custom Grid.ParseResultInternal result = new Grid.ParseResultInternal(); grid.CellParseInternal(row, column.FieldNameCSharp, UtilFramework.StringEmpty(cell.Text), result); // Custom parse of user entered text. if (result.IsHandled == false) { await grid.CellParseInternalAsync(row, column.FieldNameCSharp, UtilFramework.StringEmpty(cell.Text), result); } // Parse custom (FileUpload) if (commandJson.GridCellTextBase64 != null) { var startsWith = "data:application/octet-stream;base64,"; // User uploaded empty (0 bytes) file if (commandJson.GridCellTextBase64 == "data:") { startsWith = "data:"; } UtilFramework.Assert(commandJson.GridCellTextBase64.StartsWith(startsWith)); var data = System.Convert.FromBase64String(commandJson.GridCellTextBase64.Substring(startsWith.Length)); fileUpload.FieldName = column.FieldNameCSharp; fileUpload.Data = data; fileUpload.FileName = commandJson.GridCellTextBase64FileName; // Is handled in terms of data is available in update and insert args parameter. result.IsHandled = true; } // Parse default if (!result.IsHandled) { if (result.ErrorParse != null) { throw new Exception("ErrorParse has been set without IsHandled!"); // Custom parse workflow! } Data.CellTextParse(field, cell.Text, row, out string errorParse); result.IsHandled = true; result.ErrorParse = errorParse; } cell.ErrorParse = result.ErrorParse; } catch (Exception exception) { cell.ErrorParse = UtilFramework.ExceptionToString(exception); } } /// /// Parse /// private static void ProcessCellIsModifyParseFilter(Grid grid, GridColumn column, Field field, GridCell cell) { cell.ErrorParse = null; // Parse try { // Parse custom GridCellParseFilterResult result = new GridCellParseFilterResult(new GridFilter(grid)); grid.CellParseFilter(column.FieldNameCSharp, UtilFramework.StringEmpty(cell.Text), result); // Custom parse of user entered text. string errorParse = result.ErrorParse; if (!result.IsHandled) { Data.CellTextParseFilter(field, cell.Text, new GridFilter(grid), out errorParse); // Parse default } cell.ErrorParse = errorParse; } catch (Exception exception) { cell.ErrorParse = UtilFramework.ExceptionToString(exception); } } /// /// Save (Update). /// private static async Task ProcessCellIsModifyUpdateAsync(Grid grid, Row rowOld, Row row, GridCell cell, FileUploadArgs fileUpload) { // Save try { Grid.UpdateResultInternal result = new Grid.UpdateResultInternal(); await grid.UpdateInternalAsync(rowOld, row, fileUpload, grid.DatabaseEnum, result); if (!result.IsHandled) { await Data.UpdateAsync(rowOld, row, grid.DatabaseEnum); } } catch (Exception exception) { cell.ErrorSave = UtilFramework.ExceptionToString(exception); } // Truncate grid.TruncateInternal(new List(new Row[] { row })); } /// /// Save (Insert). /// private static async Task ProcessCellIsModifyInsertAsync(Grid grid, Row row, GridCell cell, FileUploadArgs fileUpload) { // Save try { // Save custom Grid.InsertResultInternal result = new Grid.InsertResultInternal(); await grid.InsertInternalAsync(row, fileUpload, grid.DatabaseEnum, result); if (!result.IsHandled) { // Save default await Data.InsertAsync(row, grid.DatabaseEnum); } } catch (Exception exception) { cell.ErrorSave = UtilFramework.ExceptionToString(exception); } // Truncate grid.TruncateInternal(new List(new Row[] { row })); } /// /// Reset ErrorSave on all cells on row. /// private static void ProcessCellIsModifyErrorSaveReset(Grid grid, GridCell cell) { foreach (var item in grid.CellList) { if (item.RowStateId == cell.RowStateId) { item.ErrorSave = null; } } } /// /// Returns true, if row has a not solved parse error. /// private static bool ProcessCellIsModifyIsErrorParse(Grid grid, GridCell cell) { bool result = false; foreach (var item in grid.CellList) { if (item.RowStateId == cell.RowStateId) { result = result || item.ErrorParse != null; } } return result; } /// /// Set Text and preserve TextOld. /// private static void ProcessCellIsModifyText(GridCell cell, CommandJson commandJson) { string textOld = cell.Text; cell.Text = commandJson.GridCellText; if (cell.IsModified == false && cell.Text != textOld) { cell.IsModified = true; cell.TextOld = textOld; } if (cell.IsModified == true && cell.Text == cell.TextOld) { cell.IsModified = false; cell.TextOld = null; } } /// /// Update IsModify flag. /// private static void ProcessCellIsModifyUpdate(Grid grid) { foreach (var cell in grid.CellList) { if (cell.IsModified && cell.Text == cell.TextOld || cell.TextLeave == cell.TextOld) { cell.IsModified = false; cell.TextOld = null; } } } /// /// Create and queue RowIsClick command. /// public static void QueueRowIsClick(Grid grid, Row row) { var rowIndex = grid.RowListInternal.IndexOf(row); var rowState = grid.RowStateList.First(item => item.RowId == rowIndex + 1); var cell = grid.CellList.First(item => item.RowStateId == rowState.Id); grid.ComponentOwner().RequestJson.CommandAdd(new CommandJson { CommandEnum = CommandEnum.GridIsClickRow, ComponentId = grid.Id, GridCellId = cell.Id }); } /// /// User selected data row. /// private static async Task ProcessRowIsClickAsync(AppJson appJson) { if (UtilSession.Request(appJson, CommandEnum.GridIsClickRow, out CommandJson commandJson, out Grid grid)) { var rowSelectLocal = grid.RowSelect; int rowStateId; // Get rowStateId either from cell or directly from command. if (commandJson.GridCellId != 0) { GridCell cell = grid.CellList[commandJson.GridCellId - 1]; rowStateId = cell.RowStateId; } else { rowStateId = commandJson.RowStateId; } Row rowSelect = null; foreach (var rowState in grid.RowStateList) { if (rowState.RowEnum == GridRowEnum.Index) { rowState.IsSelect = rowState.Id == rowStateId; if (rowState.IsSelect) { rowSelect = grid.RowListInternal[rowState.RowId.Value - 1]; } } } if (rowSelect != null) { if (grid.IsGridLookup == false) { // Grid normal row selected GridLookupClose(grid); Render(grid); UtilFramework.Assert(rowSelect == grid.RowSelect); if (rowSelect != rowSelectLocal) { await grid.RowSelectAsync(); // Load detail data grid } } else { // Grid lookup row selected GridLookupClose(grid.GridDest); UtilFramework.Assert(rowSelect == grid.RowSelect); LookupRowSelectArgs args = new LookupRowSelectArgs { RowSelect = grid.RowSelect, FieldName = grid.GridLookupDestFieldNameCSharp }; LookupRowSelectResult result = new LookupRowSelectResult(); grid.GridDest.LookupRowSelect(args, result); if (result.Text != null) { GridLookupToGridDest(grid, out var gridDest, out _, out _, out var cellDest); // TODO Use command queue appJson.RequestJson = new RequestJson(new CommandJson { CommandEnum = CommandEnum.GridCellIsModify, ComponentId = gridDest.Id, GridCellId = cellDest.Id, GridCellText = result.Text, GridCellTextIsLookup = true }); await ProcessCellIsModify(appJson); } } } } } /// /// User modified text. /// private static async Task ProcessCellIsModify(AppJson appJson) { if (UtilSession.Request(appJson, CommandEnum.GridCellIsModify, out CommandJson commandJson, out Grid grid)) { GridCell cell = grid.CellList[commandJson.GridCellId - 1]; if (cell.IsReadOnly) { throw new ExceptionSecurity("Cell IsReadOnly!"); } GridColumn column = grid.ColumnList[cell.ColumnId - 1]; var field = UtilDalType.TypeRowToFieldListDictionary(grid.TypeRow)[column.FieldNameCSharp]; GridRowState rowState = grid.RowStateList[cell.RowStateId - 1]; // Track IsModified ProcessCellIsModifyText(cell, commandJson); cell.Warning = null; // Parse Filter if (rowState.RowEnum == GridRowEnum.Filter) { new GridFilter(grid).TextSet(column.FieldNameCSharp, cell.Text); // Used after data grid reload to restore filter. // Parse ProcessCellIsModifyParseFilter(grid, column, field, cell); if (!ProcessCellIsModifyIsErrorParse(grid, cell)) { // Reload grid.OffsetRow = 0; // Back to first row. await LoadAsync(grid); } } var fileUpload = new FileUploadArgs(); // Parse Index if (rowState.RowEnum == GridRowEnum.Index) { Row row = grid.RowListInternal[rowState.RowId.Value - 1]; if (rowState.Row == null) { rowState.Row = (Row)Activator.CreateInstance(grid.TypeRow); Data.RowCopy(row, rowState.Row); } // ErrorSave reset ProcessCellIsModifyErrorSaveReset(grid, cell); // Parse await ProcessCellIsModifyParseAsync(grid, rowState.RowEnum, rowState.Row, column, field, cell, commandJson, fileUpload); if (!ProcessCellIsModifyIsErrorParse(grid, cell)) { // Save await ProcessCellIsModifyUpdateAsync(grid, row, rowState.Row, cell, fileUpload); if (cell.ErrorSave == null) { Data.RowCopy(rowState.Row, row); // Copy new Id to ProcessCellIsModifyReset(grid, cell); } } // Lookup if (!commandJson.GridCellTextIsLookup) // Do not open lookup again after lookup row has been clicked by user. { await ProcessGridLookupOpenAsync(grid, rowState, column, cell); } // Do not set Cell.TextLeave on response if user clicked lookup row. bool isTextLeave = true; if (commandJson.GridCellTextIsLookup) { isTextLeave = false; } // Do not set Cell.TextLeave on response if user did not enter text but clicked the file upload button. if (commandJson.GridCellTextBase64 != null) { isTextLeave = false; } Render(grid, cell, isTextLeave); // Call method GridCellText(); for all cells on this data row ProcessCellIsModifyUpdate(grid); // Update IsModify ProcessCellIsModifyWarningNotSaved(grid, cell); // Not saved warning } // Parse New if (rowState.RowEnum == GridRowEnum.New) { if (rowState.Row == null) { rowState.Row = (Row)Activator.CreateInstance(grid.TypeRow); } // ErrorSave reset ProcessCellIsModifyErrorSaveReset(grid, cell); // Parse await ProcessCellIsModifyParseAsync(grid, rowState.RowEnum, rowState.Row, column, field, cell, commandJson, fileUpload); if (!ProcessCellIsModifyIsErrorParse(grid, cell)) { // Save await ProcessCellIsModifyInsertAsync(grid, rowState.Row, cell, fileUpload); if (cell.ErrorSave == null) { grid.RowListInternal.Add(rowState.Row); rowState.RowId = grid.RowListInternal.Count; foreach (var item in grid.CellList) { if (item.RowStateId == cell.RowStateId) // Cells in same row { rowState.RowEnum = GridRowEnum.Index; // From New to Index item.Placeholder = null; } } rowState.Row = null; grid.RowStateList.Add(new GridRowState { Id = grid.RowStateList.Count + 1, RowEnum = GridRowEnum.New }); ProcessCellIsModifyReset(grid, cell); await RowSelectAsync(grid, rowState); } } // Lookup if (!commandJson.GridCellTextIsLookup) // Do not open lookup again after lookup row has been clicked by user. { await ProcessGridLookupOpenAsync(grid, rowState, column, cell); } // Do not set Cell.TextLeave if user clicked lookup row. bool isTextLeave = true; if (commandJson.GridCellTextIsLookup) { isTextLeave = false; } Render(grid, cell, isTextLeave); // Call method GridCellText(); for all cells on this data row ProcessCellIsModifyUpdate(grid); // Update IsModify ProcessCellIsModifyWarningNotSaved(grid, cell); // Not saved warning } } } /// /// User clicked paging button. /// private static async Task ProcessIsClickEnum(AppJson appJson) { if (UtilSession.Request(appJson, CommandEnum.GridIsClickEnum, out CommandJson commandJson, out Grid grid)) { var isClickConfig = commandJson.GridIsClickEnum == GridIsClickEnum.Config; var isClickConfigDeveloper = commandJson.GridIsClickEnum == GridIsClickEnum.ConfigDeveloper; // Security if (isClickConfig) { if (!grid.IsShowConfig) { throw new ExceptionSecurity("Grid Config not allowed!"); } } if (isClickConfigDeveloper) { if (!grid.IsShowConfigDeveloper) { throw new ExceptionSecurity("Grid ConfigDeveloper not allowed!"); } } // Grid config if (isClickConfig || isClickConfigDeveloper) { if (grid.TypeRow != null) // Do not show config if for example no query is defined for data grid. { Page page = grid.ComponentOwner(); // Show data grid config in developer config (mode) if user clicked developer button. bool isDeveloper = commandJson.GridIsClickEnum == GridIsClickEnum.ConfigDeveloper; string tableNameCSharp = UtilDalType.TypeRowToTableNameCSharp(grid.TypeRow); var pageConfigGrid = new PageConfigGrid(page, tableNameCSharp, null, grid?.ConfigGrid.ConfigName, isDeveloper); await pageConfigGrid.InitAsync(); } } // Grid reload if (commandJson.GridIsClickEnum == GridIsClickEnum.Reload) { // Reset filter, sort grid.FilterValueList = null; grid.SortValueList = null; grid.OffsetRow = 0; grid.OffsetColumn = 0; await LoadAsync(grid); } // Grid page up if (commandJson.GridIsClickEnum == GridIsClickEnum.PageUp) { var configGrid = grid.ConfigGrid; grid.OffsetRow -= ConfigRowCountMax(configGrid); if (grid.OffsetRow < 0) { grid.OffsetRow = 0; } await LoadAsync(grid); } // Grid page down if (commandJson.GridIsClickEnum == GridIsClickEnum.PageDown) { var configGrid = grid.ConfigGrid; int rowCount = grid.RowListInternal.Count; int rowCountMax = ConfigRowCountMax(configGrid); if (rowCount == rowCountMax) // Page down further on full grid only. { grid.OffsetRow += rowCountMax; } await LoadAsync(grid); } // Grid page left if (commandJson.GridIsClickEnum == GridIsClickEnum.PageLeft) { grid.OffsetColumn -= 1; if (grid.OffsetColumn < 0) { grid.OffsetColumn = 0; } Render(grid); } // Grid page right if (commandJson.GridIsClickEnum == GridIsClickEnum.PageRight) { var configGrid = grid.ConfigGrid; double widthTotal = grid.ColumnWidthTotal(grid.OffsetColumn); double widthMax = ConfigWidthMax(configGrid); if (widthTotal > widthMax && !(grid.OffsetColumn == grid.ColumnList.Count - 1)) { grid.OffsetColumn += 1; Render(grid); } } // Grid mode table if (commandJson.GridIsClickEnum == GridIsClickEnum.ModeTable) { grid.Mode = GridMode.Table; Render(grid); } // Grid mode stack if (commandJson.GridIsClickEnum == GridIsClickEnum.ModeStack) { grid.Mode = GridMode.Stack; Render(grid); } // Excel if (commandJson.GridIsClickEnum == GridIsClickEnum.ExcelDownload) { UtilGridExcel.Export(grid); } } } } } namespace Framework.Session { using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using Framework.DataAccessLayer; using Framework.Json; using System; using System.IO; using Page = Json.Page; using CellExcel = DocumentFormat.OpenXml.Spreadsheet.Cell; using RowExcel = DocumentFormat.OpenXml.Spreadsheet.Row; using Row = DataAccessLayer.Row; internal static class UtilGridExcel { private static void ExportStyle(SpreadsheetDocument spreadsheetDocument) { // See also: https://stackoverflow.com/questions/11116176/cell-styles-in-openxml-spreadsheet-spreadsheetml var stylesPart = spreadsheetDocument.WorkbookPart.AddNewPart(); stylesPart.Stylesheet = new Stylesheet { // blank font list Fonts = new Fonts() }; stylesPart.Stylesheet.Fonts.AppendChild(new Font()); stylesPart.Stylesheet.Fonts.Count = (uint)stylesPart.Stylesheet.Fonts.ChildElements.Count; // White font Font font = new Font(); font.Append(new Color() { Rgb = "ffffff" }); font.Append(new Bold()); stylesPart.Stylesheet.Fonts.AppendChild(font); stylesPart.Stylesheet.Fonts.Count = (uint)stylesPart.Stylesheet.Fonts.ChildElements.Count; // create fills stylesPart.Stylesheet.Fills = new Fills(); // create a solid red fill var solidBlue = new PatternFill() { PatternType = PatternValues.Solid }; solidBlue.ForegroundColor = new ForegroundColor { Rgb = HexBinaryValue.FromString("FF0000FF") }; // red fill solidBlue.BackgroundColor = new BackgroundColor { Indexed = 64 }; stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.None } }); // required, reserved by Excel stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.Gray125 } }); // required, reserved by Excel stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = solidBlue }); stylesPart.Stylesheet.Fills.Count = (uint)stylesPart.Stylesheet.Fills.ChildElements.Count; //// blank border list stylesPart.Stylesheet.Borders = new Borders(); stylesPart.Stylesheet.Borders.AppendChild(new Border()); stylesPart.Stylesheet.Borders.Count = (uint)stylesPart.Stylesheet.Borders.ChildElements.Count; //// blank cell format list stylesPart.Stylesheet.CellStyleFormats = new CellStyleFormats(); stylesPart.Stylesheet.CellStyleFormats.AppendChild(new CellFormat()); stylesPart.Stylesheet.CellStyleFormats.Count = (uint)stylesPart.Stylesheet.CellStyleFormats.ChildElements.Count; // cell format list stylesPart.Stylesheet.CellFormats = new CellFormats(); // empty one for index 0, seems to be required stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat()); // cell format references style format 0, font 0, border 0, fill 2 and applies the fill stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat { FormatId = 0, FontId = 1, BorderId = 0, FillId = 2, ApplyFill = true }); // .AppendChild(new Alignment { Horizontal = HorizontalAlignmentValues.Center }); stylesPart.Stylesheet.CellFormats.Count = (uint)stylesPart.Stylesheet.CellFormats.ChildElements.Count; stylesPart.Stylesheet.Save(); } private static string GetExcelColumnName(int columnNumber) { int dividend = columnNumber; string columnName = String.Empty; int modulo; while (dividend > 0) { modulo = (dividend - 1) % 26; columnName = Convert.ToChar(65 + modulo).ToString() + columnName; dividend = (int)((dividend - modulo) / 26); } return columnName; } private static void ExportData(WorksheetPart worksheetPart, Grid grid) { var fieldList = UtilDalType.TypeRowToFieldListDictionary(grid.TypeRow); var sheetData = worksheetPart.Worksheet.GetFirstChild(); var columnList = grid.ColumnList; int rowCount = 0; foreach (var rowState in grid.RowStateList) { rowCount += 1; var rowExcel = new RowExcel() { RowIndex = (uint)rowCount }; sheetData.Append(rowExcel); int columnCount = 0; foreach (var column in columnList) { columnCount += 1; string cellReference = GetExcelColumnName(columnCount) + rowCount; // Column header cell if (rowState.RowEnum == GridRowEnum.Filter) { var cell = new CellExcel() { CellReference = cellReference, DataType = CellValues.String, CellValue = new CellValue(column.ColumnText), StyleIndex = 1 }; rowExcel.Append(cell); } // Data cell if (rowState.RowEnum == GridRowEnum.Index) { Row row; if (rowState.Row == null) { row = grid.RowListInternal[rowState.RowId.Value - 1]; } else { row = rowState.Row; } var field = fieldList[column.FieldNameCSharp]; string text = null; object value = field.PropertyInfo.GetValue(row); if (value != null) { text = grid.CellTextInternal(row, field.FieldNameCSharp, text); // Custom convert database value to cell text. text = UtilFramework.StringNull(text); if (text == null) { text = field.FrameworkType().CellTextFromValue(value); } } var cell = new CellExcel() { CellReference = cellReference, DataType = CellValues.String, CellValue = new CellValue(text) }; rowExcel.Append(cell); } } } } public static void Export(Grid grid) { var fileName = Path.GetTempFileName(); using (var spreadsheetDocument = SpreadsheetDocument.Create(fileName, SpreadsheetDocumentType.Workbook)) { // Add a WorkbookPart to the document. WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart(); workbookpart.Workbook = new Workbook(); // Add a WorksheetPart to the WorkbookPart. WorksheetPart worksheetPart = workbookpart.AddNewPart(); worksheetPart.Worksheet = new Worksheet(new SheetData()); // Add Sheets to the Workbook. Sheets sheets = spreadsheetDocument.WorkbookPart.Workbook. AppendChild(new Sheets()); // Append a new worksheet and associate it with the workbook. Sheet sheet = new Sheet() { Id = spreadsheetDocument.WorkbookPart. GetIdOfPart(worksheetPart), SheetId = 1, Name = "Data" }; sheets.Append(sheet); ExportStyle(spreadsheetDocument); ExportData(worksheetPart, grid); workbookpart.Workbook.Save(); // Close the document. spreadsheetDocument.Close(); } // Send file with app.json to download in client. AppJson appJson = grid.ComponentOwner(); appJson.Download(File.ReadAllBytes(fileName), "Grid.xlsx"); File.Delete(fileName); } } }