Skip to content

Commit 874e2c2

Browse files
committed
add column text align, add Download Service
1 parent 2c0729b commit 874e2c2

File tree

11 files changed

+174
-26
lines changed

11 files changed

+174
-26
lines changed

samples/Sample.Core/Pages/DataGrid/Examples/Basic.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
</DataColumn>
2727
<DataColumn TItem="Person" Property="p => p.FirstName" SortIndex="1" MinWidth="200px" />
2828
<DataColumn TItem="Person" Property="p => p.LastName" SortIndex="0" ColumnStyle="min-width: 200px" />
29-
<DataColumn TItem="Person" Property="p => p.Score" Width="120px" Style="@SoreStyle" />
29+
<DataColumn TItem="Person" Property="p => p.Score" Width="120px" Style="@SoreStyle" Align="TextAlign.Right" />
3030
<DataColumn TItem="Person" Property="p => p.Location" ColumnStyle="min-width: 200px" Sortable="false" FilterValues="Locations" />
31-
<DataColumn TItem="Person" Property="p => p.Birthday" Width="110px" Format="d" />
31+
<DataColumn TItem="Person" Property="p => p.Birthday" Width="110px" Format="d" Align="TextAlign.End" />
3232
<DataColumn TItem="Person" Property="p => p.FullName" Visible="false" MultiLine="false" />
3333
</DataColumns>
3434
<DataPagination Context="grid">

src/LoreSoft.Blazor.Controls/Data/DataColumn.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public class DataColumn<TItem> : ComponentBase
4242
[Parameter]
4343
public string? MaxWidth { get; set; }
4444

45+
[Parameter]
46+
public TextAlign? Align { get; set; }
4547

4648
[Parameter]
4749
public string? Format { get; set; }
@@ -52,9 +54,18 @@ public class DataColumn<TItem> : ComponentBase
5254
[Parameter]
5355
public Func<TItem, string>? Style { get; set; }
5456

57+
[Parameter]
58+
public TextAlign? HeaderAlign { get; set; }
59+
60+
[Parameter]
61+
public string? HeaderStyle { get; set; }
62+
5563
[Parameter]
5664
public string? HeaderClass { get; set; }
5765

66+
[Parameter]
67+
public string? FooterStyle { get; set; }
68+
5869
[Parameter]
5970
public string? FooterClass { get; set; }
6071

@@ -297,6 +308,22 @@ internal string ComputeStyle(TItem data)
297308
.AddStyle("overflow", "hidden", !MultiLine)
298309
.AddStyle("text-overflow", "ellipsis", !MultiLine)
299310
.AddStyle("white-space", "nowrap", !MultiLine)
311+
.AddStyle("text-align", "center", Align is TextAlign.Center)
312+
.AddStyle("text-align", "left", Align is TextAlign.Left or TextAlign.Start)
313+
.AddStyle("text-align", "right", Align is TextAlign.Right or TextAlign.End)
300314
.ToString();
301315
}
316+
317+
internal string ComputeHeaderStyle()
318+
{
319+
return StyleBuilder.Default(HeaderStyle ?? string.Empty)
320+
.AddStyle("width", Width, (v) => v.HasValue())
321+
.AddStyle("min-width", MinWidth, (v) => v.HasValue())
322+
.AddStyle("max-width", MaxWidth, (v) => v.HasValue())
323+
.AddStyle("text-align", "center", HeaderAlign is TextAlign.Center)
324+
.AddStyle("text-align", "left", HeaderAlign is TextAlign.Left or TextAlign.Start)
325+
.AddStyle("text-align", "right", HeaderAlign is TextAlign.Right or TextAlign.End)
326+
.ToString();
327+
}
328+
302329
}

src/LoreSoft.Blazor.Controls/Data/DataGrid.razor

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@
5555
}
5656
@foreach (var column in VisibleColumns)
5757
{
58-
<th @key="column" class="@column.HeaderClass">
58+
<th @key="column"
59+
class="@column.HeaderClass"
60+
style="@column.ComputeHeaderStyle()">
5961
<div class="data-grid-header">
6062
@if (Sortable && column.Sortable)
6163
{

src/LoreSoft.Blazor.Controls/Data/DataGrid.razor.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public partial class DataGrid<TItem> : DataComponentBase<TItem>
2222
[Inject]
2323
public required IJSRuntime JavaScript { get; set; }
2424

25+
[Inject]
26+
public required DownloadService DownloadService { get; set; }
27+
2528
[Parameter(CaptureUnmatchedValues = true)]
2629
public Dictionary<string, object>? TableAttributes { get; set; }
2730

@@ -265,11 +268,9 @@ await CsvWriter.WriteAsync(
265268
// need to reset stream position
266269
memoryStream.Seek(0, SeekOrigin.Begin);
267270

268-
using var streamReference = new DotNetStreamReference(memoryStream, true);
269-
270271
var downloadFile = fileName ?? $"Export {DateTime.Now:yyyy-MM-dd-HH-mm-ss}.csv";
271272

272-
await JavaScript.InvokeVoidAsync("downloadFileStream", downloadFile, streamReference);
273+
await DownloadService.DownloadFileStream(memoryStream, downloadFile, "text/csv");
273274
}
274275

275276

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace LoreSoft.Blazor.Controls;
2+
3+
public enum TextAlign
4+
{
5+
/// <summary>
6+
/// Aligns the content against at the start of the container.
7+
/// </summary>
8+
Start = 0,
9+
10+
/// <summary>
11+
/// Aligns the content at the center of the container.
12+
/// </summary>
13+
Center = 1,
14+
15+
/// <summary>
16+
/// Aligns the content at the end of the container.
17+
/// </summary>
18+
End = 2,
19+
20+
/// <summary>
21+
/// Justifies the content against the left of the container.
22+
/// </summary>
23+
Left = 3,
24+
25+
/// <summary>
26+
/// Justifies the content at the right of the container.
27+
/// </summary>
28+
Right = 4,
29+
}
Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,45 @@
1-
downloadFileStream = async (fileName, streamReference) => {
2-
const arrayBuffer = await streamReference.arrayBuffer();
3-
const blob = new Blob([arrayBuffer]);
4-
const url = URL.createObjectURL(blob);
5-
const anchorElement = document.createElement('a');
1+
window.BlazorControls = {
2+
downloadFileStream: async (streamReference, fileName, mimeType) => {
3+
if (!streamReference) {
4+
console.error('streamReference is null or undefined.');
5+
return;
6+
}
67

7-
anchorElement.href = url;
8-
anchorElement.download = fileName ?? '';
8+
const arrayBuffer = await streamReference.arrayBuffer();
9+
const blob = new Blob([arrayBuffer], { type: mimeType || '' });
10+
const url = URL.createObjectURL(blob);
911

10-
anchorElement.click();
11-
anchorElement.remove();
12+
try {
13+
const anchorElement = document.createElement('a');
14+
anchorElement.style.display = 'none';
15+
anchorElement.href = url;
16+
anchorElement.download = fileName || '';
1217

13-
URL.revokeObjectURL(url);
14-
}
18+
document.body.appendChild(anchorElement);
19+
20+
anchorElement.click();
21+
22+
document.body.removeChild(anchorElement);
23+
} finally {
24+
URL.revokeObjectURL(url);
25+
}
26+
},
27+
28+
triggerFileDownload: (url, fileName) => {
29+
if (!url) {
30+
console.error('url is null, undefined, or empty.');
31+
return;
32+
}
33+
34+
const anchorElement = document.createElement('a');
35+
anchorElement.style.display = 'none';
36+
anchorElement.href = url;
37+
anchorElement.download = fileName || '';
1538

16-
triggerFileDownload = (fileName, url) => {
17-
const anchorElement = document.createElement('a');
39+
document.body.appendChild(anchorElement);
1840

19-
anchorElement.href = url;
20-
anchorElement.download = fileName ?? '';
41+
anchorElement.click();
2142

22-
anchorElement.click();
23-
anchorElement.remove();
43+
document.body.removeChild(anchorElement);
44+
}
2445
}

src/LoreSoft.Blazor.Controls/ServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ public static class ServiceCollectionExtensions
77
{
88
public static IServiceCollection AddBlazorControls(this IServiceCollection services)
99
{
10+
services.TryAddScoped<DownloadService>();
11+
1012
services.AddProgressBar();
1113
services.AddToaster();
1214

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.ComponentModel;
2+
using System.Text;
3+
4+
using Microsoft.JSInterop;
5+
6+
namespace LoreSoft.Blazor.Controls;
7+
8+
/// <summary>
9+
/// Provides methods for downloading files in Blazor applications using JavaScript interop.
10+
/// </summary>
11+
public class DownloadService(IJSRuntime javaScript)
12+
{
13+
private readonly IJSRuntime _javaScript = javaScript ?? throw new ArgumentNullException(nameof(javaScript));
14+
15+
/// <summary>
16+
/// Downloads a file from a provided <see cref="Stream"/> to the user's device.
17+
/// </summary>
18+
/// <param name="stream">The stream containing the file data.</param>
19+
/// <param name="fileName">The name of the file to be downloaded. Optional.</param>
20+
/// <param name="mimeType">The MIME type of the file. Optional.</param>
21+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
22+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="stream"/> is null.</exception>
23+
public async Task DownloadFileStream(Stream stream, string? fileName = null, string? mimeType = null)
24+
{
25+
ArgumentNullException.ThrowIfNull(stream);
26+
27+
using var streamReference = new DotNetStreamReference(stream, true);
28+
29+
await _javaScript.InvokeVoidAsync("BlazorControls.downloadFileStream", streamReference, fileName, mimeType);
30+
}
31+
32+
/// <summary>
33+
/// Downloads a text file to the user's device.
34+
/// </summary>
35+
/// <param name="text">The text content to be downloaded.</param>
36+
/// <param name="fileName">The name of the file to be downloaded. Optional.</param>
37+
/// <param name="mimeType">The MIME type of the file. Optional.</param>
38+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
39+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="text"/> is null.</exception>
40+
public async Task DownloadFileText(string text, string? fileName = null, string? mimeType = null)
41+
{
42+
ArgumentNullException.ThrowIfNull(text);
43+
44+
var bytes = Encoding.UTF8.GetBytes(text);
45+
await using var stream = new MemoryStream(bytes);
46+
47+
// need to reset stream position
48+
stream.Seek(0, SeekOrigin.Begin);
49+
50+
await DownloadFileStream(stream, fileName, mimeType);
51+
}
52+
53+
/// <summary>
54+
/// Triggers a file download from a specified URL.
55+
/// </summary>
56+
/// <param name="url">The URL of the file to download.</param>
57+
/// <param name="fileName">The name of the file to be downloaded. Optional.</param>
58+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
59+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="url"/> is null.</exception>
60+
public async Task TriggerFileDownload(string url, string? fileName = null)
61+
{
62+
ArgumentNullException.ThrowIfNull(url);
63+
64+
await _javaScript.InvokeVoidAsync("BlazorControls.triggerFileDownload", url, fileName);
65+
}
66+
}

src/LoreSoft.Blazor.Controls/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
}
2020
},
2121
"scripts": {
22-
"build": "parcel build",
22+
"build": "parcel build --no-cache",
2323
"watch": "parcel watch"
2424
},
2525
"devDependencies": {

src/LoreSoft.Blazor.Controls/wwwroot/BlazorControls.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)