blazor
This commit is contained in:
parent
16e1d8d216
commit
2ed1090524
439
docz/docs/03-demos/02-frontend/09-blazor.md
Normal file
439
docz/docs/03-demos/02-frontend/09-blazor.md
Normal file
@ -0,0 +1,439 @@
|
||||
---
|
||||
title: Sheets in Blazor Sites
|
||||
sidebar_label: Blazor
|
||||
pagination_prev: demos/index
|
||||
pagination_next: demos/grid/index
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
import current from '/version.js';
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
|
||||
[Blazor](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor) is a
|
||||
framework for building user interfaces using C#, HTML, JS and CSS.
|
||||
|
||||
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
|
||||
data from spreadsheets.
|
||||
|
||||
This demo uses Blazor and SheetJS to process and generate spreadsheets. We'll
|
||||
explore how to load SheetJS in Razor components and compare common state models
|
||||
and data flow strategies.
|
||||
|
||||
:::caution Blazor support is considered experimental.
|
||||
|
||||
Great open source software grows with user tests and reports. Any issues should
|
||||
be reported to the Blazor project for further diagnosis.
|
||||
|
||||
:::
|
||||
|
||||
:::danger Telemetry
|
||||
|
||||
**The `dotnet` command embeds telemetry.**
|
||||
|
||||
The `DOTNET_CLI_TELEMETRY_OPTOUT` environment variable should be set to `1`.
|
||||
|
||||
["Platform Configuration"](#platform-configuration) includes instructions for
|
||||
setting the environment variable on supported platforms.
|
||||
|
||||
:::
|
||||
|
||||
## Integration Details
|
||||
|
||||
### Installation
|
||||
|
||||
The SheetJS library can be loaded when the page is loaded or imported whenever
|
||||
the library functionality is used.
|
||||
|
||||
#### Standalone Script
|
||||
|
||||
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
|
||||
can be loaded in the root HTML page (typically `wwwroot/index.html`):
|
||||
|
||||
<CodeBlock language="html">{`\
|
||||
<!-- use version ${current} -->
|
||||
<script lang="javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
|
||||
</CodeBlock>
|
||||
|
||||
#### ECMAScript Module
|
||||
|
||||
The SheetJS ECMAScript module script can be dynamically imported from functions.
|
||||
This ensures the library is only loaded when necessary. The following example
|
||||
loads the library and returns a Promise that resolves to the version string:
|
||||
|
||||
<CodeBlock language="js">{`\
|
||||
async function sheetjs_version(id) {
|
||||
/* dynamically import the script in the event listener */
|
||||
// highlight-next-line
|
||||
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
|
||||
\n\
|
||||
/* use the library */
|
||||
return XLSX.version;
|
||||
}`}
|
||||
</CodeBlock>
|
||||
|
||||
### Calling JS from C#
|
||||
|
||||
#### Setup
|
||||
|
||||
The primary mechanism for invoking JS functions from Blazor is `IJSRuntime`[^1].
|
||||
It should be injected at the top of relevant Razor component scripts:
|
||||
|
||||
```csharp title="Injecting IJSRuntime"
|
||||
@inject IJSRuntime JS
|
||||
```
|
||||
|
||||
#### Fire and Forget
|
||||
|
||||
When exporting a file with the SheetJS `writeFile` method[^2], browser APIs do
|
||||
not provide success or error feedback. As a result, this demo invokes functions
|
||||
using the `InvokeVoidAsync` static method[^3]:
|
||||
|
||||
```csharp title="Invoking JS functions from C#"
|
||||
private async Task ExportDataset() {
|
||||
await JS.InvokeVoidAsync("export_method", data);
|
||||
}
|
||||
```
|
||||
|
||||
Methods are commonly bound to buttons in the Razor template using `@onclick`:
|
||||
|
||||
```html title="Binding callback to a HTML button"
|
||||
<button @onclick="ExportDataset">Export Dataset</button>
|
||||
```
|
||||
|
||||
### State in Blazor
|
||||
|
||||
The example [presidents sheet](https://docs.sheetjs.com/pres.xlsx) has one
|
||||
header row with "Name" and "Index" columns.
|
||||
|
||||
![`pres.xlsx` data](pathname:///pres.png)
|
||||
|
||||
#### C# Representation
|
||||
|
||||
The natural C# representation of a single row is a class object:
|
||||
|
||||
```csharp title="President class"
|
||||
public class President {
|
||||
public string Name { get; set; }
|
||||
public int Index { get; set; }
|
||||
}
|
||||
|
||||
var PrezClinton = new President() { Name = "Bill Clinton", Index = 42 };
|
||||
```
|
||||
|
||||
The entire dataset is typically stored in an array of class objects:
|
||||
|
||||
```csharp title="President dataset"
|
||||
private President[] data;
|
||||
```
|
||||
|
||||
#### Data Interchange
|
||||
|
||||
`InvokeVoidAsync` can pass data from the C# state to a JS function:
|
||||
|
||||
```csharp
|
||||
await JS.InvokeVoidAsync("export_method", data);
|
||||
```
|
||||
|
||||
Each row in the dataset will be passed as a separate argument to the JavaScript
|
||||
method, so the JavaScript code should collect the arguments:
|
||||
|
||||
```js title="Collecting rows in a JS callback"
|
||||
/* NOTE: blazor spreads the C# array, so the ... spread syntax is required */
|
||||
async function export_method(...rows) {
|
||||
/* display the array of objects */
|
||||
console.log(rows);
|
||||
}
|
||||
```
|
||||
|
||||
Each row is a simple JavaScript object.
|
||||
|
||||
:::caution pass
|
||||
|
||||
Blazor automatically spreads arrays. Each row is passed as a separate argument
|
||||
to the JavaScript method.
|
||||
|
||||
The example method uses the JavaScript spread syntax to collect the arguments.
|
||||
|
||||
:::
|
||||
|
||||
#### Exporting Data
|
||||
|
||||
With the collected array of objects, the SheetJS `json_to_sheet` method[^4] will
|
||||
generate a SheetJS worksheet[^5] from the dataset. After creating a workbook[^6]
|
||||
object with the `book_new` method[^7], the file is written with `writeFile`[^2]:
|
||||
|
||||
<CodeBlock title="JS Callback for exporting datasets" language="javascript">{`\
|
||||
/* NOTE: blazor spreads the C# array, so the spread is required */
|
||||
async function export_method(...rows) {
|
||||
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
|
||||
const ws = XLSX.utils.json_to_sheet(rows);
|
||||
const wb = XLSX.utils.book_new(ws, "Data");
|
||||
XLSX.writeFile(wb, "SheetJSBlazor.xlsx");
|
||||
}`}
|
||||
</CodeBlock>
|
||||
|
||||
|
||||
### HTML Tables
|
||||
|
||||
When displaying datasets, Razor components typically generate HTML tables:
|
||||
|
||||
```html title="Razor template from official starter"
|
||||
<table class="table" id="weather-table">
|
||||
<thead>
|
||||
<tr><th>Date</th><th>Temp. (C)</th><th>Temp. (F)</th><th>Summary</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var forecast in forecasts)
|
||||
{
|
||||
<tr>
|
||||
<td>@forecast.Date.ToShortDateString()</td>
|
||||
<td>@forecast.TemperatureC</td>
|
||||
<td>@forecast.TemperatureF</td>
|
||||
<td>@forecast.Summary</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
If it has an `id`, JS code on the frontend can find the table element using the
|
||||
`document.getElementById` DOM method. A SheetJS workbook object can be generated
|
||||
using the `table_to_book` method[^8] and exported with `writeFile`[^2]:
|
||||
|
||||
<CodeBlock title="JS Callback for exporting HTML TABLE elements" language="javascript">{`\
|
||||
/* NOTE: blazor spreads the C# array, so the spread is required */
|
||||
async function export_method() {
|
||||
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
|
||||
const wb = XLSX.utils.table_to_book(document.getElementById("weather-table"));
|
||||
XLSX.writeFile(wb, "SheetJSBlazor.xlsx");
|
||||
}`}
|
||||
</CodeBlock>
|
||||
|
||||
This approach uses data that already exists in the document, so no additional
|
||||
data is passed from C# to JavaScript.
|
||||
|
||||
## Complete Demo
|
||||
|
||||
The Blazor + WASM starter app includes a "Weather" component that displays data
|
||||
from a C#-managed dataset. This demo uses SheetJS to export data in two ways:
|
||||
|
||||
- "Export Dataset" will send row objects from the underlying C# data store to
|
||||
the frontend. The SheetJS `json_to_sheet` method[^4] builds the worksheet.
|
||||
|
||||
- "Export HTML Table" will scrape the table using the SheetJS `table_to_book`
|
||||
method[^8]. No extra data will be sent to the frontend.
|
||||
|
||||
:::note Tested Deployments
|
||||
|
||||
This demo was tested in the following deployments:
|
||||
|
||||
| Architecture | Date |
|
||||
|:-------------|:-----------|
|
||||
| `darwin-arm` | 2024-10-15 |
|
||||
|
||||
:::
|
||||
|
||||
### Platform Configuration
|
||||
|
||||
0) Set the `DOTNET_CLI_TELEMETRY_OPTOUT` environment variable to `1`.
|
||||
|
||||
<details open>
|
||||
<summary><b>How to disable telemetry</b> (click to hide)</summary>
|
||||
|
||||
<Tabs groupId="os">
|
||||
<TabItem value="unix" label="Linux/MacOS">
|
||||
|
||||
Add the following line to `.profile`, `.bashrc` and `.zshrc`:
|
||||
|
||||
```bash title="(add to .profile , .bashrc , and .zshrc)"
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
```
|
||||
|
||||
Close and restart the Terminal to load the changes.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="win" label="Windows">
|
||||
|
||||
Type `env` in the search bar and select "Edit the system environment variables".
|
||||
|
||||
In the new window, click the "Environment Variables..." button.
|
||||
|
||||
In the new window, look for the "System variables" section and click "New..."
|
||||
|
||||
Set the "Variable name" to `DOTNET_CLI_TELEMETRY_OPTOUT` and the value to `1`.
|
||||
|
||||
Click "OK" in each window (3 windows) and restart your computer.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
1) Install .NET
|
||||
|
||||
<details>
|
||||
<summary><b>Installation Notes</b> (click to show)</summary>
|
||||
|
||||
For macOS x64 and ARM64, install the `dotnet-sdk` Cask with Homebrew:
|
||||
|
||||
```bash
|
||||
brew install --cask dotnet-sdk
|
||||
```
|
||||
|
||||
For Steam Deck Holo and other Arch Linux x64 distributions, the `dotnet-sdk` and
|
||||
`dotnet-runtime` packages should be installed using `pacman`:
|
||||
|
||||
```bash
|
||||
sudo pacman -Syu dotnet-sdk dotnet-runtime
|
||||
```
|
||||
|
||||
https://dotnet.microsoft.com/en-us/download/dotnet/6.0 is the official source
|
||||
for Windows and ARM64 Linux versions.
|
||||
|
||||
</details>
|
||||
|
||||
2) Open a new Terminal window in macOS or PowerShell window in Windows.
|
||||
|
||||
### App Creation
|
||||
|
||||
3) Create a new `blazorwasm` app:
|
||||
|
||||
```bash
|
||||
dotnet new blazorwasm -o SheetJSBlazorWasm
|
||||
cd SheetJSBlazorWasm
|
||||
dotnet run
|
||||
```
|
||||
|
||||
When the Blazor service runs, the terminal will display a URL:
|
||||
|
||||
```text
|
||||
info: Microsoft.Hosting.Lifetime[14]
|
||||
// highlight-next-line
|
||||
Now listening on: http://localhost:6969
|
||||
```
|
||||
|
||||
4) In a new browser window, open the displayed URL from Step 3.
|
||||
|
||||
5) Click the "Weather" link and confirm the page includes a data table.
|
||||
|
||||
6) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
|
||||
|
||||
### SheetJS Integration
|
||||
|
||||
7) Add the following script tag to `wwwroot/index.html` in the `HEAD` block:
|
||||
|
||||
<CodeBlock title="wwwroot/index.html (add within the HEAD block)" language="html">{`\
|
||||
<script>
|
||||
/* NOTE: blazor spreads the C# array, so the spread is required */
|
||||
async function export_dataset(...rows) {
|
||||
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
|
||||
const ws = XLSX.utils.json_to_sheet(rows);
|
||||
const wb = XLSX.utils.book_new(ws, "Data");
|
||||
XLSX.writeFile(wb, "SheetJSBlazorDataset.xlsx");
|
||||
}
|
||||
\n\
|
||||
async function export_html(id) {
|
||||
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
|
||||
const wb = XLSX.utils.table_to_book(document.getElementById(id));
|
||||
XLSX.writeFile(wb, "SheetJSBlazorHTML.xlsx");
|
||||
}
|
||||
</script>`}
|
||||
</CodeBlock>
|
||||
|
||||
8) Inject the `IJSRuntime` dependency near the top of `Pages/Weather.razor`:
|
||||
|
||||
```csharp title="Pages/Weather.razor (add highlighted lines)"
|
||||
@page "/weather"
|
||||
@inject HttpClient Http
|
||||
// highlight-next-line
|
||||
@inject IJSRuntime JS
|
||||
```
|
||||
|
||||
9) Add an ID to the `TABLE` element in `Pages/Weather.razor`:
|
||||
|
||||
```html title="Pages/Weather.razor (add id to TABLE element)"
|
||||
{
|
||||
<!-- highlight-next-line -->
|
||||
<table class="table" id="weather-table">
|
||||
<thead>
|
||||
<tr>
|
||||
```
|
||||
|
||||
10) Add callbacks to the `@code` section in `Pages/Weather.razor`:
|
||||
|
||||
```csharp title="Pages/Weather.razor (add within the @code section)"
|
||||
private async Task ExportDataset()
|
||||
{
|
||||
await JS.InvokeVoidAsync("export_dataset", forecasts);
|
||||
}
|
||||
|
||||
private async Task ExportHTML()
|
||||
{
|
||||
await JS.InvokeVoidAsync("export_html", "weather-table");
|
||||
}
|
||||
```
|
||||
|
||||
11) Add Export buttons to the template in `Pages/Weather.razor`:
|
||||
|
||||
```csharp title="Pages/Weather.razor (add highlighted lines)"
|
||||
<p>This component demonstrates fetching data from the server.</p>
|
||||
|
||||
<!-- highlight-start -->
|
||||
<button @onclick="ExportDataset">Export Dataset</button>
|
||||
<button @onclick="ExportHTML">Export HTML TABLE</button>
|
||||
<!-- highlight-end -->
|
||||
```
|
||||
|
||||
|
||||
### Testing
|
||||
|
||||
12) Launch the `dotnet` process again:
|
||||
|
||||
```bash
|
||||
dotnet run
|
||||
```
|
||||
|
||||
When the Blazor service runs, the terminal will display a URL:
|
||||
|
||||
```text
|
||||
info: Microsoft.Hosting.Lifetime[14]
|
||||
Now listening on: http://localhost:6969
|
||||
```
|
||||
|
||||
13) In a new browser window, open the displayed URL from Step 12.
|
||||
|
||||
14) Click the "Weather" link. The page should match the following screenshot:
|
||||
|
||||
![SheetJSBlazorWasm with Exports](pathname:///blazor/weather.png)
|
||||
|
||||
15) Click the "Export Dataset" button and save the generated file to
|
||||
`SheetJSBlazorDataset.xlsx`. Open the file in a spreadsheet editor and confirm
|
||||
the data matches the table. The column labels will differ since the underlying
|
||||
dataset uses different labels.
|
||||
|
||||
![SheetJSBlazorDataset.xlsx](pathname:///blazor/dataset.png)
|
||||
|
||||
16) Click the "Export HTML TABLE" button and save the generated file to
|
||||
`SheetJSBlazorHTML.xlsx`. Open the file in a spreadsheet editor and confirm the
|
||||
data matches the table. The column labels will match the HTML table.
|
||||
|
||||
![SheetJSBlazorHTML.xlsx](pathname:///blazor/html.png)
|
||||
|
||||
:::note pass
|
||||
|
||||
It is somewhat curious that the official `dotnet` Blazor sample dataset marks
|
||||
`1 C` and `-13 C` as "freezing" but marks `-2 C` as "chilly". It stands to
|
||||
reason that `-2 C` should also be freezing.
|
||||
|
||||
:::
|
||||
|
||||
[^1]: See ["Microsoft.JSInterop.IJSRuntime"](https://learn.microsoft.com/en-us/dotnet/api/microsoft.jsinterop.ijsruntime) in the `dotnet` documentation.
|
||||
[^2]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
|
||||
[^3]: See ["Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync"](https://learn.microsoft.com/en-us/dotnet/api/microsoft.jsinterop.jsruntimeextensions.invokevoidasync) in the `dotnet` documentation.
|
||||
[^4]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
|
||||
[^5]: See ["Sheet Objects"](/docs/csf/sheet)
|
||||
[^6]: See ["Workbook Object"](/docs/csf/book)
|
||||
[^7]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)
|
||||
[^8]: See [`table_to_book` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet)
|
@ -2,7 +2,7 @@
|
||||
title: Legacy Frameworks
|
||||
pagination_prev: demos/index
|
||||
pagination_next: demos/grid/index
|
||||
sidebar_position: 9
|
||||
sidebar_position: 18
|
||||
sidebar_custom_props:
|
||||
skip: 1
|
||||
---
|
BIN
docz/static/blazor/dataset.png
Normal file
BIN
docz/static/blazor/dataset.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
docz/static/blazor/html.png
Normal file
BIN
docz/static/blazor/html.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
docz/static/blazor/weather.png
Normal file
BIN
docz/static/blazor/weather.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 125 KiB |
Loading…
Reference in New Issue
Block a user