Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Blazorise.sln
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazorise.Weavers", "Source
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazorise.Weavers.Fody", "Source\SourceGenerators\Blazorise.Weavers.Fody\Blazorise.Weavers.Fody.csproj", "{FFC4A285-1A16-4DD4-8B8C-141521E405B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazorise.Exporters.Bson", "Source\Extensions\Blazorise.Exporters.Bson\Blazorise.Exporters.Bson.csproj", "{01A482C0-8DD8-4A9D-95FF-5CC2F71DB41B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazorise.Exporters.Csv", "Source\Extensions\Blazorise.Exporters.Csv\Blazorise.Exporters.Csv.csproj", "{1D465B0D-4905-438A-8581-A0657A602A33}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazorise.Exporters", "Source\Extensions\Blazorise.Exporters\Blazorise.Exporters.csproj", "{B5B0EB7F-3457-4E93-AF9A-DE864CBB21BE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazorise.Scheduler", "Source\Extensions\Blazorise.Scheduler\Blazorise.Scheduler.csproj", "{E2582180-8E51-43E1-0943-588D720FCAB5}"
EndProject
Global
Expand Down Expand Up @@ -425,6 +431,18 @@ Global
{FFC4A285-1A16-4DD4-8B8C-141521E405B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFC4A285-1A16-4DD4-8B8C-141521E405B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFC4A285-1A16-4DD4-8B8C-141521E405B0}.Release|Any CPU.Build.0 = Release|Any CPU
{01A482C0-8DD8-4A9D-95FF-5CC2F71DB41B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01A482C0-8DD8-4A9D-95FF-5CC2F71DB41B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01A482C0-8DD8-4A9D-95FF-5CC2F71DB41B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01A482C0-8DD8-4A9D-95FF-5CC2F71DB41B}.Release|Any CPU.Build.0 = Release|Any CPU
{1D465B0D-4905-438A-8581-A0657A602A33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D465B0D-4905-438A-8581-A0657A602A33}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D465B0D-4905-438A-8581-A0657A602A33}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D465B0D-4905-438A-8581-A0657A602A33}.Release|Any CPU.Build.0 = Release|Any CPU
{B5B0EB7F-3457-4E93-AF9A-DE864CBB21BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5B0EB7F-3457-4E93-AF9A-DE864CBB21BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5B0EB7F-3457-4E93-AF9A-DE864CBB21BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5B0EB7F-3457-4E93-AF9A-DE864CBB21BE}.Release|Any CPU.Build.0 = Release|Any CPU
{E2582180-8E51-43E1-0943-588D720FCAB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2582180-8E51-43E1-0943-588D720FCAB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2582180-8E51-43E1-0943-588D720FCAB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -501,6 +519,9 @@ Global
{EAB7EC89-900A-4280-B24A-152B9DD2B503} = {9731051E-0AA7-411E-A76A-987854F034DA}
{BF5FFB8C-45AD-4875-BB01-2DA388890419} = {0538DB67-B4F3-4D00-B969-D3874A52E405}
{FFC4A285-1A16-4DD4-8B8C-141521E405B0} = {0538DB67-B4F3-4D00-B969-D3874A52E405}
{01A482C0-8DD8-4A9D-95FF-5CC2F71DB41B} = {9731051E-0AA7-411E-A76A-987854F034DA}
{1D465B0D-4905-438A-8581-A0657A602A33} = {9731051E-0AA7-411E-A76A-987854F034DA}
{B5B0EB7F-3457-4E93-AF9A-DE864CBB21BE} = {9731051E-0AA7-411E-A76A-987854F034DA}
{E2582180-8E51-43E1-0943-588D720FCAB5} = {9731051E-0AA7-411E-A76A-987854F034DA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
2 changes: 2 additions & 0 deletions Demos/Blazorise.Demo/Blazorise.Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<ProjectReference Include="..\..\Source\Extensions\Blazorise.SignaturePad\Blazorise.SignaturePad.csproj" />
<ProjectReference Include="..\..\Source\Extensions\Blazorise.FluentValidation\Blazorise.FluentValidation.csproj" />
<ProjectReference Include="..\..\Source\Extensions\Blazorise.PdfViewer\Blazorise.PdfViewer.csproj" />
<ProjectReference Include="..\..\Source\Extensions\Blazorise.Exporters.Csv\Blazorise.Exporters.Csv.csproj" />
<ProjectReference Include="..\..\Source\Extensions\Blazorise.Exporters.Bson\Blazorise.Exporters.Bson.csproj" />
<ProjectReference Include="..\..\Source\Extensions\Blazorise.Scheduler\Blazorise.Scheduler.csproj" />
<ProjectReference Include="..\Apps\TodoApp\TodoApp.csproj" />
</ItemGroup>
Expand Down
7 changes: 7 additions & 0 deletions Demos/Blazorise.Demo/Pages/Tests/DataGrid/DataGridPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
<CardText>Combine diferent datagrid options</CardText>
</CardBody>
<CardBody>
<Row>
<Column>
<Button Color="Color.Primary" Clicked="@OnExport">
Export TEST
</Button>
</Column>
</Row>
<Row>
<Column>
<Fields>
Expand Down
20 changes: 20 additions & 0 deletions Demos/Blazorise.Demo/Pages/Tests/DataGrid/DataGridPage.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Threading.Tasks;
using Blazorise.DataGrid;
using Blazorise.DataGrid.Utils;
using Blazorise.Exporters.Bson;
using Blazorise.Exporters.Csv;
using Blazorise.Shared.Data;
using Blazorise.Shared.Models;
using Microsoft.AspNetCore.Components;
Expand Down Expand Up @@ -251,5 +253,23 @@ private void OnSortChanged( DataGridSortChangedEventArgs eventArgs )
Console.WriteLine( $"Sort changed > Field: {eventArgs.ColumnFieldName}{sort}; Direction: {eventArgs.SortDirection};" );
}

private async Task OnExport()
{
// Simple export to CSV with default options
var result1 = await dataGrid.Export( new CsvToFileExporter() );

// Copy to clipboard without headers
var result2 = await dataGrid.Export( new CsvToClipboardExporter( new() { ExportHeader = false } ) );

// Export to CSV file with a custom file name and only the first 3 rows
var result3 = await dataGrid.Export(
new CsvToFileExporter( new() { FileName = "custom-csv-file.csv" } ),
new DataGridExportOptions { NumberOfRows = 3 }
);

// Bson export (defined in an external project)
var result4 = await dataGrid.Export( new BsonToFileExporter() );
}

#endregion
}
2 changes: 1 addition & 1 deletion Source/Blazorise/Base/BaseTypographyComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected override void BuildClasses( ClassBuilder builder )
protected async Task OnClickHandler()
{
if ( CopyToClipboard )
await JSUtilitiesModule.CopyToClipboard( ElementRef, ElementId );
await JSUtilitiesModule.CopyContentToClipboard( ElementRef, ElementId );
}

#endregion
Expand Down
19 changes: 18 additions & 1 deletion Source/Blazorise/Interfaces/Modules/IJSUtilitiesModule.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#region Using directives
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
#endregion

namespace Blazorise.Modules;
Expand Down Expand Up @@ -164,8 +165,15 @@ public interface IJSUtilitiesModule : IBaseJSModule
/// <param name="elementRef">Reference to the rendered element.</param>
/// <param name="elementId">ID of the rendered element.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
ValueTask CopyToClipboard( ElementReference elementRef, string elementId );
ValueTask CopyContentToClipboard( ElementReference elementRef, string elementId );

/// <summary>
/// Copies the specified string content to the clipboard.
/// </summary>
/// <param name="stringToCopy">The string content to copy to the clipboard.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
ValueTask<string[]> CopyStringToClipboard( string stringToCopy );

/// <summary>
/// Writes a log message to the browser console.
/// </summary>
Expand All @@ -179,4 +187,13 @@ public interface IJSUtilitiesModule : IBaseJSModule
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains true if the theme is in dark mode, otherwise false.</returns>
ValueTask<bool> IsSystemDarkMode();

/// <summary>
/// Exports data to a specified file with a given MIME type asynchronously.
/// </summary>
/// <param name="data">The byte array containing the data to be exported.</param>
/// <param name="fileName">The name of the file to which the data will be exported.</param>
/// <param name="mimeType">The MIME type that describes the format of the data being exported.</param>
/// <returns>Array of error strings, empty means no error during export</returns>
ValueTask<string[]> ExportToFile( byte[] data, string fileName, string mimeType );
}
10 changes: 9 additions & 1 deletion Source/Blazorise/Modules/JSUtilitiesModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,13 @@ public virtual ValueTask<string> GetUserAgent()
=> InvokeSafeAsync<string>( "getUserAgent" );

/// <inheritdoc/>
public ValueTask CopyToClipboard( ElementReference elementRef, string elementId )
public ValueTask CopyContentToClipboard( ElementReference elementRef, string elementId )
=> InvokeSafeVoidAsync( "copyToClipboard", elementRef, elementId );

/// <inheritdoc/>
public ValueTask<string[]> CopyStringToClipboard( string stringToCopy )
=> InvokeSafeAsync<string[]>( "copyStringToClipboard", stringToCopy );

/// <inheritdoc/>
public ValueTask Log( string message, params string[] args )
=> InvokeSafeVoidAsync( "log", message, args );
Expand All @@ -119,6 +123,10 @@ public ValueTask Log( string message, params string[] args )
public ValueTask<bool> IsSystemDarkMode()
=> InvokeSafeAsync<bool>( "isSystemDarkMode" );

/// <inheritdoc/>
public virtual async ValueTask<string []> ExportToFile( byte[] data, string fileName, string mimeType )
=> await InvokeSafeAsync<string []>( "exportToFile", data, fileName, mimeType );

#endregion

#region Properties
Expand Down
51 changes: 51 additions & 0 deletions Source/Blazorise/wwwroot/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,20 @@ export function copyToClipboard(element, elementId) {
}
}

export function copyStringToClipboard(stringToCopy) {
try {

if (!navigator.clipboard) {
return ["Clipboard API not available"];
}

navigator.clipboard.writeText(stringToCopy);
return [];
} catch (error) {
return [`Error copying to clipboard: ${error.message}`];
}
}

function getExponentialParts(num) {
return Array.isArray(num) ? num : String(num).split(/[eE]/);
}
Expand Down Expand Up @@ -355,4 +369,41 @@ export function insertCSSIntoDocumentHead(url) {

export function isSystemDarkMode() {
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}

export function exportToFile(data, fileName, mimeType) {
try {
if (!data || !fileName || !mimeType) {
return ["Missing required parameters"];
}

// Convert .NET byte array to Uint8Array
const uint8Array = new Uint8Array(data);

// Create Blob with specified MIME type
const blob = new Blob([uint8Array], {type: mimeType});
if (!blob) {
return ["Failed to create blob"];
}

// Create temporary URL
const url = URL.createObjectURL(blob);

// Create hidden anchor element
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);

// Trigger download
a.click();

// Cleanup
document.body.removeChild(a);
URL.revokeObjectURL(url);

return [];
} catch (error) {
return [`Error exporting file: ${error.message}`];
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#region Using directives
using System;
using System.Threading.Tasks;
using Blazorise.Exporters;
using Blazorise.Extensions;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

<ItemGroup>
<ProjectReference Include="..\..\Blazorise\Blazorise.csproj" />
<ProjectReference Include="..\Blazorise.Exporters\Blazorise.Exporters.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Blazorise.DataGrid;

/// <summary>
/// Specifies the number of rows to export from a data grid.
/// </summary>
public class DataGridExportOptions
{
/// <summary>
/// Defines the number of rows to export from a data grid. If null, all rows will be exported.
/// </summary>
public int? NumberOfRows { get; init; }

/// <summary>
/// Defines the fields to export from a data grid. If null, all fields will be exported.
/// </summary>
public string[] Fields { get; init; } = null;

/// <summary>
/// Defines whether to include captions instead of field names in the export. Defaults to false.
/// </summary>
public bool UseCaptions { get; init; }
}
74 changes: 73 additions & 1 deletion Source/Extensions/Blazorise.DataGrid/DataGrid.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Blazorise.DataGrid.Utils;
using Blazorise.DeepCloner;
using Blazorise.Exporters;
using Blazorise.Extensions;
using Blazorise.Licensing;
using Blazorise.Modules;
Expand All @@ -25,7 +27,7 @@ namespace Blazorise.DataGrid;
/// </summary>
/// <typeparam name="TItem">Type parameter for the model displayed in the <see cref="DataGrid{TItem}"/>.</typeparam>
[CascadingTypeParameter( nameof( TItem ) )]
public partial class DataGrid<TItem> : BaseDataGridComponent
public partial class DataGrid<TItem> : BaseDataGridComponent, IExportableComponent
{
#region Members

Expand Down Expand Up @@ -508,6 +510,7 @@ protected override async Task OnAfterRenderAsync( bool firstRender )

IsClientMacintoshOS = await IsUserAgentMacintoshOS();
await JSModule.Initialize( tableRef.ElementRef, ElementId );

if ( IsCellNavigable )
{
await JSModule.InitializeTableCellNavigation( tableRef.ElementRef, ElementId );
Expand Down Expand Up @@ -1907,6 +1910,75 @@ public ValueTask ScrollToPixels( int pixels )
public ValueTask ScrollToRow( int row )
=> tableRef.ScrollToRow( row );

/// <summary>
/// Exports data using a specified exporter and options, returning the result of the export operation.
/// </summary>
/// <typeparam name="TExportResult">Defines the type of the result produced by the export operation.</typeparam>
/// <typeparam name="TCellValue">Specifies the type of the cell values in the data being exported.</typeparam>
/// <param name="exporter">An object responsible for handling the export process and generating the output.</param>
/// <param name="options">Configuration settings that influence the export behavior and output format.</param>
/// <returns>The result of the export operation, encapsulated in the specified result type.</returns>
public async Task<TExportResult> Export<TExportResult, TCellValue>( IExporter<TExportResult, TabularSourceData<TCellValue>> exporter, DataGridExportOptions options = null )
where TExportResult : IExportResult, new()
{
if ( exporter is IExporterWithJsModule exporterWithJsModule )
{
exporterWithJsModule.JSUtilitiesModule = JSUtilitiesModule;
}

var data = ExportData<TCellValue>( options );

TExportResult exportResult = await exporter.Export( data );

return exportResult;
}

private TabularSourceData<TCellValue> ExportData<TCellValue>( DataGridExportOptions options )
{
options ??= new();

// Filter columns (exclude Command, MultiSelect, and DisplayTemplate columns)
var columnsToExport = Columns
.Where( column => column.ColumnType != DataGridColumnType.Command &&
column.ColumnType != DataGridColumnType.MultiSelect &&
column.Field != null &&
column.DisplayTemplate == null &&
( options.Fields == null || options.Fields.Contains( column.Field ) ) )
.ToList();

var exportedData = new List<List<TCellValue>>();

var columnNames = ( options.UseCaptions
? columnsToExport.Select( c => c.Caption ?? c.Field )
: columnsToExport.Select( x => x.Field ) ).ToList();

var filteredDataToTake = options.NumberOfRows is null || options.NumberOfRows <= 0
? FilteredData
: FilteredData.Take( options.NumberOfRows.Value );

bool isCellValueString = typeof( TCellValue ) == typeof( string );

foreach ( var item in filteredDataToTake )
{
var rowValues = new List<TCellValue>();

foreach ( var column in columnsToExport )
{
var cellValue = column.GetValue( item );

var formattedValue = isCellValueString
? column.FormatDisplayValue( cellValue ) ?? string.Empty
: cellValue;

rowValues.Add( (TCellValue)formattedValue );
}

exportedData.Add( rowValues );
}

return new TabularSourceData<TCellValue> { Data = exportedData, ColumnNames = columnNames };
}

#endregion

#region Editing
Expand Down
Loading
Loading