Skip to content

Commit

Permalink
Merge pull request #69 from IowaComputerGurus/feature/auto-filter
Browse files Browse the repository at this point in the history
Feature/auto filter
  • Loading branch information
mitchelsellers authored Dec 25, 2023
2 parents c9ba93a + 382f16b commit fa422ba
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 76 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ var exportDefinition = new SpreadsheetConfiguration<SimpleExportData>
DocumentSubTitle = "Showing the full options",
ExportData = GetSampleExportData(100),
WorksheetName = "Sample",
FreezePanes = true
FreezePanes = true,
AutoFilterDataRows = true
};
var fileContent = exportGenerator.CreateSingleSheetSpreadsheet(exportDefinition);
System.IO.File.WriteAllBytes("Sample.xlsx", fileContent);
Expand Down Expand Up @@ -76,4 +77,6 @@ This package is primarily geared towards the exporting of lists of objects into
* Data type formatting for Date & Currency fields
* Auto-fit of all columns for display
* The ability to freeze the header columns into a freeze pane for single sheet, or multi-sheet exports
* Support for Curreny, Date, F0, F1, and F2 fixed date formats
* The ability to add "Auto Filter" behavior to the data table portion of a sheet, while still supporting all other items
* The ability to automatically add "simple formula" totals to columsn. (SUM, AVG, etc)
* Support for Curreny, Date, F0, F1, F2, and F3 fixed data formats
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace NetCore.Utilities.SpreadsheetExample.Models;

namespace NetCore.Utilities.SpreadsheetExample.Models
internal class SecondExportData
{
class SecondExportData
{
}
}

}
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
using System;
using ICG.NetCore.Utilities.Spreadsheet;

namespace NetCore.Utilities.SpreadsheetExample.Models
namespace NetCore.Utilities.SpreadsheetExample.Models;

public class SimpleExportData
{
public class SimpleExportData
{
public string Title { get; set; }
public string Title { get; set; }

[SpreadsheetColumn("Due Date", format: "D")]
public DateTime DueDate { get; set; }

[SpreadsheetColumn("Due Date", format:"D")]
public DateTime DueDate { get; set; }

[SpreadsheetColumn("Total Cost", format:"C", formula: "SUM")]
public decimal TotalCost { get; set; }
[SpreadsheetColumn("Total Cost", format: "C", formula: "SUM")]
public decimal TotalCost { get; set; }

[SpreadsheetColumn("Testing Numbers", format:"F3")]
public decimal TestingNumbers { get; set; }
[SpreadsheetColumn("Testing Numbers", format: "F3")]
public decimal TestingNumbers { get; set; }

public string Notes { get; set; }
}
public string Notes { get; set; }
}
93 changes: 48 additions & 45 deletions samples/NetCore.Utilities.SpreadsheetExample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,61 +1,64 @@
using System;
using System.Collections.Generic;
using System.IO;
using ICG.NetCore.Utilities.Spreadsheet;
using Microsoft.Extensions.DependencyInjection;
using NetCore.Utilities.SpreadsheetExample.Models;

namespace NetCore.Utilities.SpreadsheetExample
namespace NetCore.Utilities.SpreadsheetExample;

internal class Program
{
class Program
private static void Main(string[] args)
{
static void Main(string[] args)
//Setup our DI Container
var services = new ServiceCollection();
services.UseIcgNetCoreUtilitiesSpreadsheet();
var provider = services.BuildServiceProvider();

//Get our generator and export
var exportGenerator = provider.GetRequiredService<ISpreadsheetGenerator>();
var exportDefinition = new SpreadsheetConfiguration<SimpleExportData>
{
//Setup our DI Container
var services = new ServiceCollection();
services.UseIcgNetCoreUtilitiesSpreadsheet();
var provider = services.BuildServiceProvider();
RenderTitle = true,
DocumentTitle = "Sample Export of 100 Records",
RenderSubTitle = true,
DocumentSubTitle = "Showing the full options",
ExportData = GetSampleExportData(100),
WorksheetName = "Sample",
FreezeHeaders = true,
AutoFilterDataRows = true
};
var fileContent = exportGenerator.CreateSingleSheetSpreadsheet(exportDefinition);
File.WriteAllBytes("Sample.xlsx", fileContent);

//Get our generator and export
var exportGenerator = provider.GetRequiredService<ISpreadsheetGenerator>();
var exportDefinition = new SpreadsheetConfiguration<SimpleExportData>
//Sample 2 sheet export
var multiSheetDefinition = new MultisheetConfiguration()
.WithSheet("Sheet 1", GetSampleExportData(100))
.WithSheet("Additional Sheet", GetSampleExportData(500), config =>
{
RenderTitle = true,
DocumentTitle = "Sample Export of 100 Records",
RenderSubTitle = true,
DocumentSubTitle = "Showing the full options",
ExportData = GetSampleExportData(100),
WorksheetName = "Sample",
FreezeHeaders = true
};
var fileContent = exportGenerator.CreateSingleSheetSpreadsheet(exportDefinition);
System.IO.File.WriteAllBytes("Sample.xlsx", fileContent);

//Sample 2 sheet export
var multiSheetDefinition = new MultisheetConfiguration()
.WithSheet("Sheet 1", GetSampleExportData(100))
.WithSheet("Additional Sheet", GetSampleExportData(500), config =>
{
config.DocumentTitle = "Lots of data";
config.RenderTitle = true;
config.FreezeHeaders = true;
});
config.DocumentTitle = "Lots of data";
config.RenderTitle = true;
config.FreezeHeaders = true;
config.AutoFilterDataRows = true;
});

var multiFileContent = exportGenerator.CreateMultiSheetSpreadsheet(multiSheetDefinition);
System.IO.File.WriteAllBytes("Sample-Multi.xlsx", multiFileContent);
Console.WriteLine("Files Created");
Console.ReadLine();
}
var multiFileContent = exportGenerator.CreateMultiSheetSpreadsheet(multiSheetDefinition);
File.WriteAllBytes("Sample-Multi.xlsx", multiFileContent);
Console.WriteLine("Files Created");
Console.ReadLine();
}

private static List<SimpleExportData> GetSampleExportData(int numberOfRecords)
{
var listData = new List<SimpleExportData>();
for (var i = 0; i < numberOfRecords; i++)
private static List<SimpleExportData> GetSampleExportData(int numberOfRecords)
{
var listData = new List<SimpleExportData>();
for (var i = 0; i < numberOfRecords; i++)
listData.Add(new SimpleExportData
{
listData.Add(new SimpleExportData
{DueDate = DateTime.Now.AddDays(i), Notes = $"Record {i} notes", TotalCost = 15m, TestingNumbers = 1234.4567289m, Title = $"Sample Data Row #{i}"});
}
DueDate = DateTime.Now.AddDays(i), Notes = $"Record {i} notes", TotalCost = 15m,
TestingNumbers = 1234.4567289m, Title = $"Sample Data Row #{i}"
});

return listData;
}
return listData;
}
}
}
40 changes: 32 additions & 8 deletions src/NetCore.Utilities.Spreadsheet/OpenXmlSpreadsheetGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,24 @@ public bool CreateSingleSheetSpreadsheet<T>(Stream output, SpreadsheetConfigurat
stylesPart.Stylesheet = CreateStylesheet();
stylesPart.Stylesheet.Save();

var data = CreateExportSheet(exportConfiguration, out var columns);
var data = CreateExportSheet(exportConfiguration, out var columns, out var filter);

//Add a worksheet to our document
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();

//If we are freezing panes add the sheet views
//If we are freezing panes add the sheet views, this is done BEFORE the data is loaded
if (exportConfiguration.FreezeHeaders)
worksheetPart.Worksheet.Append(CreateFreezePane(exportConfiguration));

//Load the actual data
worksheetPart.Worksheet.Append(columns);
worksheetPart.Worksheet.Append(data);

//If Filtering, add it after we have added the data
if (exportConfiguration.AutoFilterDataRows)
worksheetPart.Worksheet.Append(filter);

//Add the sheet to the workbook
var sheets = spreadsheetDocument.WorkbookPart.Workbook.AppendChild(new Sheets());
var sheet = new Sheet
Expand Down Expand Up @@ -166,17 +171,24 @@ public bool CreateMultiSheetSpreadsheet(Stream output, IEnumerable<ISpreadsheetC
var sheetId = 1u;
foreach (var item in exportSheets)
{
var data = CreateExportSheet(item, out var columns);
var data = CreateExportSheet(item, out var columns, out var filter);

//Add a worksheet to our document
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();
//If we are freezing panes add the sheet views

//If we are freezing panes add the sheet views before loading the data
if (item.FreezeHeaders)
worksheetPart.Worksheet.Append(CreateFreezePane(item));

//Load the data
worksheetPart.Worksheet.Append(columns);
worksheetPart.Worksheet.Append(data);

//If we are filtering, add the filter ater the data
if (item.AutoFilterDataRows)
worksheetPart.Worksheet.Append(filter);

//Add the sheet to the workbook
var sheet = new Sheet
{
Expand Down Expand Up @@ -222,7 +234,7 @@ private static bool IsOfType<T>(Type t)

private sealed record OutputPropMap(Column Column, List<Cell> Cells);

private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfiguration, out Columns columns)
private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfiguration, out Columns columns, out AutoFilter filter)
{
//Build out our sheet information
var data = new SheetData();
Expand Down Expand Up @@ -291,7 +303,8 @@ private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfi
data.Append(headerRow);
currentRow++;

uint? firstDataRow = headerProperties.Any(d => string.IsNullOrWhiteSpace(d.Formula) == false) ? currentRow : null;
var dataRowIndex = currentRow;
var requiresFormula = headerProperties.Any(d => string.IsNullOrWhiteSpace(d.Formula) == false);

//Run the data
foreach (var item in exportConfiguration.ExportData)
Expand Down Expand Up @@ -320,7 +333,8 @@ private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfi
currentRow++;
}

if (firstDataRow != null)
//Add the formula(s) as needed
if (requiresFormula)
{
var dataRow = new Row { RowIndex = currentRow };
foreach (var prop in headerProperties)
Expand All @@ -331,7 +345,7 @@ private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfi
{
CellReference = GetCellReferenceByRowAndColumn(currentRow, prop.Order),
DataType = CellValues.Number,
CellFormula = new CellFormula($"{prop.Formula}({GetCellReferenceByRowAndColumn(firstDataRow.Value, prop.Order)}:{GetCellReferenceByRowAndColumn(currentRow - 1, prop.Order)})")
CellFormula = new CellFormula($"{prop.Formula}({GetCellReferenceByRowAndColumn(dataRowIndex, prop.Order)}:{GetCellReferenceByRowAndColumn(currentRow - 1, prop.Order)})")
};

//Match the formatting of the column for this
Expand All @@ -358,6 +372,16 @@ private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfi
{
CalculateSizes(outputMap.Values.ToList());
}

filter = new AutoFilter();
if (exportConfiguration.AutoFilterDataRows)
{
//Start 1 row up from data row start (to include header)
var startPosition = GetCellReferenceByRowAndColumn(dataRowIndex -1, 1);
var endPosition = GetCellReferenceByRowAndColumn(currentRow - 1, headerProperties.Max(p => p.Order));
filter.Reference = $"{startPosition}:{endPosition}";
}

return data;
}

Expand Down
8 changes: 8 additions & 0 deletions src/NetCore.Utilities.Spreadsheet/SpreadsheetConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ public interface ISpreadsheetConfiguration
/// if set to true the header(s) will be frozen
/// </summary>
public bool FreezeHeaders { get; set; }

/// <summary>
/// If set to true the data section of the sheet will have auto-filter turned on
/// </summary>
public bool AutoFilterDataRows { get; set; }
}
/// <inheritdoc />
/// <typeparam name="TRecord">The type to be exported</typeparam>
Expand Down Expand Up @@ -130,6 +135,9 @@ public class SpreadsheetConfiguration<T> : ISpreadsheetConfiguration<T> where T
/// if set to true the header(s) will be frozen
/// </summary>
public bool FreezeHeaders { get; set; }

/// <inheritdoc />
public bool AutoFilterDataRows { get; set; }
}

/// <summary>
Expand Down

0 comments on commit fa422ba

Please sign in to comment.