Skip to content

Commit fa422ba

Browse files
Merge pull request #69 from IowaComputerGurus/feature/auto-filter
Feature/auto filter
2 parents c9ba93a + 382f16b commit fa422ba

File tree

6 files changed

+109
-76
lines changed

6 files changed

+109
-76
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ var exportDefinition = new SpreadsheetConfiguration<SimpleExportData>
4545
DocumentSubTitle = "Showing the full options",
4646
ExportData = GetSampleExportData(100),
4747
WorksheetName = "Sample",
48-
FreezePanes = true
48+
FreezePanes = true,
49+
AutoFilterDataRows = true
4950
};
5051
var fileContent = exportGenerator.CreateSingleSheetSpreadsheet(exportDefinition);
5152
System.IO.File.WriteAllBytes("Sample.xlsx", fileContent);
@@ -76,4 +77,6 @@ This package is primarily geared towards the exporting of lists of objects into
7677
* Data type formatting for Date & Currency fields
7778
* Auto-fit of all columns for display
7879
* The ability to freeze the header columns into a freeze pane for single sheet, or multi-sheet exports
79-
* Support for Curreny, Date, F0, F1, and F2 fixed date formats
80+
* The ability to add "Auto Filter" behavior to the data table portion of a sheet, while still supporting all other items
81+
* The ability to automatically add "simple formula" totals to columsn. (SUM, AVG, etc)
82+
* Support for Curreny, Date, F0, F1, F2, and F3 fixed data formats
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
1+
namespace NetCore.Utilities.SpreadsheetExample.Models;
42

5-
namespace NetCore.Utilities.SpreadsheetExample.Models
3+
internal class SecondExportData
64
{
7-
class SecondExportData
8-
{
9-
}
10-
}
5+
6+
}
Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
using System;
22
using ICG.NetCore.Utilities.Spreadsheet;
33

4-
namespace NetCore.Utilities.SpreadsheetExample.Models
4+
namespace NetCore.Utilities.SpreadsheetExample.Models;
5+
6+
public class SimpleExportData
57
{
6-
public class SimpleExportData
7-
{
8-
public string Title { get; set; }
8+
public string Title { get; set; }
9+
10+
[SpreadsheetColumn("Due Date", format: "D")]
11+
public DateTime DueDate { get; set; }
912

10-
[SpreadsheetColumn("Due Date", format:"D")]
11-
public DateTime DueDate { get; set; }
12-
13-
[SpreadsheetColumn("Total Cost", format:"C", formula: "SUM")]
14-
public decimal TotalCost { get; set; }
13+
[SpreadsheetColumn("Total Cost", format: "C", formula: "SUM")]
14+
public decimal TotalCost { get; set; }
1515

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

19-
public string Notes { get; set; }
20-
}
19+
public string Notes { get; set; }
2120
}
Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,64 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
34
using ICG.NetCore.Utilities.Spreadsheet;
45
using Microsoft.Extensions.DependencyInjection;
56
using NetCore.Utilities.SpreadsheetExample.Models;
67

7-
namespace NetCore.Utilities.SpreadsheetExample
8+
namespace NetCore.Utilities.SpreadsheetExample;
9+
10+
internal class Program
811
{
9-
class Program
12+
private static void Main(string[] args)
1013
{
11-
static void Main(string[] args)
14+
//Setup our DI Container
15+
var services = new ServiceCollection();
16+
services.UseIcgNetCoreUtilitiesSpreadsheet();
17+
var provider = services.BuildServiceProvider();
18+
19+
//Get our generator and export
20+
var exportGenerator = provider.GetRequiredService<ISpreadsheetGenerator>();
21+
var exportDefinition = new SpreadsheetConfiguration<SimpleExportData>
1222
{
13-
//Setup our DI Container
14-
var services = new ServiceCollection();
15-
services.UseIcgNetCoreUtilitiesSpreadsheet();
16-
var provider = services.BuildServiceProvider();
23+
RenderTitle = true,
24+
DocumentTitle = "Sample Export of 100 Records",
25+
RenderSubTitle = true,
26+
DocumentSubTitle = "Showing the full options",
27+
ExportData = GetSampleExportData(100),
28+
WorksheetName = "Sample",
29+
FreezeHeaders = true,
30+
AutoFilterDataRows = true
31+
};
32+
var fileContent = exportGenerator.CreateSingleSheetSpreadsheet(exportDefinition);
33+
File.WriteAllBytes("Sample.xlsx", fileContent);
1734

18-
//Get our generator and export
19-
var exportGenerator = provider.GetRequiredService<ISpreadsheetGenerator>();
20-
var exportDefinition = new SpreadsheetConfiguration<SimpleExportData>
35+
//Sample 2 sheet export
36+
var multiSheetDefinition = new MultisheetConfiguration()
37+
.WithSheet("Sheet 1", GetSampleExportData(100))
38+
.WithSheet("Additional Sheet", GetSampleExportData(500), config =>
2139
{
22-
RenderTitle = true,
23-
DocumentTitle = "Sample Export of 100 Records",
24-
RenderSubTitle = true,
25-
DocumentSubTitle = "Showing the full options",
26-
ExportData = GetSampleExportData(100),
27-
WorksheetName = "Sample",
28-
FreezeHeaders = true
29-
};
30-
var fileContent = exportGenerator.CreateSingleSheetSpreadsheet(exportDefinition);
31-
System.IO.File.WriteAllBytes("Sample.xlsx", fileContent);
32-
33-
//Sample 2 sheet export
34-
var multiSheetDefinition = new MultisheetConfiguration()
35-
.WithSheet("Sheet 1", GetSampleExportData(100))
36-
.WithSheet("Additional Sheet", GetSampleExportData(500), config =>
37-
{
38-
config.DocumentTitle = "Lots of data";
39-
config.RenderTitle = true;
40-
config.FreezeHeaders = true;
41-
});
40+
config.DocumentTitle = "Lots of data";
41+
config.RenderTitle = true;
42+
config.FreezeHeaders = true;
43+
config.AutoFilterDataRows = true;
44+
});
4245

43-
var multiFileContent = exportGenerator.CreateMultiSheetSpreadsheet(multiSheetDefinition);
44-
System.IO.File.WriteAllBytes("Sample-Multi.xlsx", multiFileContent);
45-
Console.WriteLine("Files Created");
46-
Console.ReadLine();
47-
}
46+
var multiFileContent = exportGenerator.CreateMultiSheetSpreadsheet(multiSheetDefinition);
47+
File.WriteAllBytes("Sample-Multi.xlsx", multiFileContent);
48+
Console.WriteLine("Files Created");
49+
Console.ReadLine();
50+
}
4851

49-
private static List<SimpleExportData> GetSampleExportData(int numberOfRecords)
50-
{
51-
var listData = new List<SimpleExportData>();
52-
for (var i = 0; i < numberOfRecords; i++)
52+
private static List<SimpleExportData> GetSampleExportData(int numberOfRecords)
53+
{
54+
var listData = new List<SimpleExportData>();
55+
for (var i = 0; i < numberOfRecords; i++)
56+
listData.Add(new SimpleExportData
5357
{
54-
listData.Add(new SimpleExportData
55-
{DueDate = DateTime.Now.AddDays(i), Notes = $"Record {i} notes", TotalCost = 15m, TestingNumbers = 1234.4567289m, Title = $"Sample Data Row #{i}"});
56-
}
58+
DueDate = DateTime.Now.AddDays(i), Notes = $"Record {i} notes", TotalCost = 15m,
59+
TestingNumbers = 1234.4567289m, Title = $"Sample Data Row #{i}"
60+
});
5761

58-
return listData;
59-
}
62+
return listData;
6063
}
61-
}
64+
}

src/NetCore.Utilities.Spreadsheet/OpenXmlSpreadsheetGenerator.cs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,24 @@ public bool CreateSingleSheetSpreadsheet<T>(Stream output, SpreadsheetConfigurat
5858
stylesPart.Stylesheet = CreateStylesheet();
5959
stylesPart.Stylesheet.Save();
6060

61-
var data = CreateExportSheet(exportConfiguration, out var columns);
61+
var data = CreateExportSheet(exportConfiguration, out var columns, out var filter);
6262

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

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

71+
//Load the actual data
7172
worksheetPart.Worksheet.Append(columns);
7273
worksheetPart.Worksheet.Append(data);
7374

75+
//If Filtering, add it after we have added the data
76+
if (exportConfiguration.AutoFilterDataRows)
77+
worksheetPart.Worksheet.Append(filter);
78+
7479
//Add the sheet to the workbook
7580
var sheets = spreadsheetDocument.WorkbookPart.Workbook.AppendChild(new Sheets());
7681
var sheet = new Sheet
@@ -166,17 +171,24 @@ public bool CreateMultiSheetSpreadsheet(Stream output, IEnumerable<ISpreadsheetC
166171
var sheetId = 1u;
167172
foreach (var item in exportSheets)
168173
{
169-
var data = CreateExportSheet(item, out var columns);
174+
var data = CreateExportSheet(item, out var columns, out var filter);
170175

171176
//Add a worksheet to our document
172177
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
173178
worksheetPart.Worksheet = new Worksheet();
174-
//If we are freezing panes add the sheet views
179+
180+
//If we are freezing panes add the sheet views before loading the data
175181
if (item.FreezeHeaders)
176182
worksheetPart.Worksheet.Append(CreateFreezePane(item));
183+
184+
//Load the data
177185
worksheetPart.Worksheet.Append(columns);
178186
worksheetPart.Worksheet.Append(data);
179187

188+
//If we are filtering, add the filter ater the data
189+
if (item.AutoFilterDataRows)
190+
worksheetPart.Worksheet.Append(filter);
191+
180192
//Add the sheet to the workbook
181193
var sheet = new Sheet
182194
{
@@ -222,7 +234,7 @@ private static bool IsOfType<T>(Type t)
222234

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

225-
private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfiguration, out Columns columns)
237+
private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfiguration, out Columns columns, out AutoFilter filter)
226238
{
227239
//Build out our sheet information
228240
var data = new SheetData();
@@ -291,7 +303,8 @@ private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfi
291303
data.Append(headerRow);
292304
currentRow++;
293305

294-
uint? firstDataRow = headerProperties.Any(d => string.IsNullOrWhiteSpace(d.Formula) == false) ? currentRow : null;
306+
var dataRowIndex = currentRow;
307+
var requiresFormula = headerProperties.Any(d => string.IsNullOrWhiteSpace(d.Formula) == false);
295308

296309
//Run the data
297310
foreach (var item in exportConfiguration.ExportData)
@@ -320,7 +333,8 @@ private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfi
320333
currentRow++;
321334
}
322335

323-
if (firstDataRow != null)
336+
//Add the formula(s) as needed
337+
if (requiresFormula)
324338
{
325339
var dataRow = new Row { RowIndex = currentRow };
326340
foreach (var prop in headerProperties)
@@ -331,7 +345,7 @@ private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfi
331345
{
332346
CellReference = GetCellReferenceByRowAndColumn(currentRow, prop.Order),
333347
DataType = CellValues.Number,
334-
CellFormula = new CellFormula($"{prop.Formula}({GetCellReferenceByRowAndColumn(firstDataRow.Value, prop.Order)}:{GetCellReferenceByRowAndColumn(currentRow - 1, prop.Order)})")
348+
CellFormula = new CellFormula($"{prop.Formula}({GetCellReferenceByRowAndColumn(dataRowIndex, prop.Order)}:{GetCellReferenceByRowAndColumn(currentRow - 1, prop.Order)})")
335349
};
336350

337351
//Match the formatting of the column for this
@@ -358,6 +372,16 @@ private static SheetData CreateExportSheet(ISpreadsheetConfiguration exportConfi
358372
{
359373
CalculateSizes(outputMap.Values.ToList());
360374
}
375+
376+
filter = new AutoFilter();
377+
if (exportConfiguration.AutoFilterDataRows)
378+
{
379+
//Start 1 row up from data row start (to include header)
380+
var startPosition = GetCellReferenceByRowAndColumn(dataRowIndex -1, 1);
381+
var endPosition = GetCellReferenceByRowAndColumn(currentRow - 1, headerProperties.Max(p => p.Order));
382+
filter.Reference = $"{startPosition}:{endPosition}";
383+
}
384+
361385
return data;
362386
}
363387

src/NetCore.Utilities.Spreadsheet/SpreadsheetConfiguration.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ public interface ISpreadsheetConfiguration
6363
/// if set to true the header(s) will be frozen
6464
/// </summary>
6565
public bool FreezeHeaders { get; set; }
66+
67+
/// <summary>
68+
/// If set to true the data section of the sheet will have auto-filter turned on
69+
/// </summary>
70+
public bool AutoFilterDataRows { get; set; }
6671
}
6772
/// <inheritdoc />
6873
/// <typeparam name="TRecord">The type to be exported</typeparam>
@@ -130,6 +135,9 @@ public class SpreadsheetConfiguration<T> : ISpreadsheetConfiguration<T> where T
130135
/// if set to true the header(s) will be frozen
131136
/// </summary>
132137
public bool FreezeHeaders { get; set; }
138+
139+
/// <inheritdoc />
140+
public bool AutoFilterDataRows { get; set; }
133141
}
134142

135143
/// <summary>

0 commit comments

Comments
 (0)