BlazorDatasheet

repository·main·Indexed 18 days ago

https://github.com/anmcgrath/blazordatasheet

A lightweight, feature-rich datasheet component for Blazor designed for editing tabular data. It supports virtualization, formulas, conditional formatting, and data validation. The library utilizes a Datasheet UI component and a Sheet data model, offering tools like ObjectEditorBuilder for object-to-sheet generation, A1 notation for range operations, and an ICommand system for extensible, undoable sheet manipulations.

Tokens
3.9K
Snippets
15
Records
16
Agent score
61%

What's inside BlazorDatasheet

  1. Work with Regions and Ranges

    main

    BlazorDatasheet uses Regions to define geometric areas and Ranges to apply operations to those areas within a sheet.

    • Region: A geometric construct (e.g., Region, ColumnRegion, RowRegion).
    • Range: A region that is aware of the Sheet, allowing you to perform sheet-level operations like setting values or formats.

    Common ways to create ranges include using A1 notation (e.g., "A1:C5"), using coordinate bounds, or passing a specific region object.

    // Creating Regions
    var region = new Region(0, 5, 0, 5); // r0 to r5, c0 to c5
    var cellRegion = new Region(0, 0); // cell A1
    var colRegion = new ColumnRegion(0, 4); // col region spanning A to D
    var rowRegion = new RowRegion(0, 3); // row region spanning 1 to 4
    
    // Creating Ranges from a Sheet
    var range1 = sheet.Range("A1:C5");
    var range2 = sheet.Range(new ColumnRegion(0));
    var range3 = sheet.Range(0, 0, 4, 5);
  2. How Datasheet and Sheet work together

    main

    BlazorDatasheet uses two primary abstractions:

    • Datasheet: The Blazor UI component that renders the grid.
    • Sheet: The underlying data model that holds cell data, configuration, and logic.

    You instantiate a Sheet (defining its dimensions) and pass it to the Datasheet component via the Sheet parameter. You can create a sheet manually by specifying rows and columns, or use the ObjectEditorBuilder to generate a sheet from a list of objects.

    By default, cells use a text editor, but you can change this by setting the Type property of specific cells or ranges.

    <Datasheet Sheet="sheet" />
    
    @code{
        private Sheet sheet;
    
        protected override void OnInitialized()
        {
            // Creates an empty 3x3 grid
            sheet = new Sheet(3, 3);
        }
    }
  3. Install and Setup BlazorDatasheet

    main

    To use BlazorDatasheet in your Blazor project, follow these three steps:

    1. Install the NuGet package via the dotnet CLI.
    2. Register the services in your Program.cs file.
    3. Import the required CSS in your _Layout.cshtml or index.html file.

    This ensures the component logic and styles are available to your application.

    # 1. Install the package
    dotnet add package BlazorDatasheet
    // 2. Configure Program.cs
    builder.Services.AddBlazorDatasheet();
    <!-- 3. Import JS/CSS in _Layout.cshtml or index.html -->
    <link href="_content/BlazorDatasheet/sheet-styles.css" rel="stylesheet"/>
  4. Apply formulas to cells

    main

    Formulas can be assigned to cells using the .Formula property. The formula must start with an = sign. BlazorDatasheet automatically re-calculates the formula whenever the cells or ranges referenced by the formula are updated.

    // Sets a formula in cell A1 that adds 10 to the value in A2
    sheet.Cells[0, 0].Formula = "=10+A2";
  5. Set and get cell values

    main

    You can manipulate cell values using direct indexers, range strings, or commands. Values are internally wrapped in a CellValue object and assigned a CellValueType (e.g., Number, Text, Date, Logical) based on the value provided.

    To prevent automatic type conversion, you can explicitly set a cell's Type to "text". You can also intercept and modify values before they are stored by using the Sheet.Cells.BeforeCellValueConversion event.

    // Using indexers
    sheet.Cells[0, 0].Value = "Test";
    
    // Using A1 notation ranges
    sheet.Range("A1").Value = "Test";
    
    // Using the Cells helper
    sheet.Cells.SetValue(0, 0, "Test");
    
    // Using commands
    sheet.Commands.ExecuteCommand(new SetCellValueCommand(0, 0, "Test"));
  6. Validate cell data

    main

    You can add validators to specific regions (like a ColumnRegion) using sheet.Validators.Add. There are two validation modes:

    1. Strict (isStrict: true): If the value fails validation, the editor will prevent the value from being set.
    2. Non-strict (isStrict: false): The value can be set during editing, but a validation error will be displayed when rendered.

    Note: Even with strict validation, programmatic changes to a cell can still result in a validation error being displayed.

    // Add a strict number validator to the first column (index 0)
    sheet.Validators.Add(new ColumnRegion(0), new NumberValidator(isStrict: true));
  7. Format cells and ranges

    main

    Formatting (like BackgroundColor, ForegroundColor, or TextAlign) can be applied to individual cells, ranges, or entire regions.

    Merging Formats: When you apply a format to a region, it is merged with existing formats. If a new format has non-null properties, only those specific properties are updated, preserving the rest of the existing style.

    // Set format on a range
    sheet.Range("A1:A2").Format = new CellFormat() { BackgroundColor = "red" };
    
    // Set format via command on a RowRegion
    sheet.Commands.ExecuteCommand(new SetFormatCommand(new RowRegion(10, 12), new CellFormat() { ForegroundColor = "blue" }));
    
    // Set format on a ColumnRegion
    sheet.SetFormat(sheet.Range(new ColumnRegion(5)), new CellFormat() { FontWeight = "bold" });
    
    // Set format on a single cell
    sheet.Cells[0, 0].Format = new CellFormat() { TextAlign = "center" };
  8. Configure cell types and editors

    main

    The Type property of a cell or range determines which renderer and editor are used. For example, setting a type to "boolean" will render a checkbox. This also assists in explicit type conversion when setting values.

    // Renders checkboxes for the specified range
    sheet.Range("A1:B5").Type = "boolean";
  9. Configure global helpers with setupGlobals()

    main

    The setupGlobals() function initializes essential global helpers required for BlazorDatasheet to function correctly. This includes setting up window.writeTextToClipboard for clipboard operations and window.setFocusWithTimeout for managing element focus. It also eagerly imports the highlighter module to prevent latency during the first edit operation. This function is idempotent and can be called multiple times without side effects.

    import { setupGlobals } from './BlazorDatasheet.lib.module.js';
    
    setupGlobals();
  10. Use lifecycle hooks for BlazorDatasheet initialization

    main

    BlazorDatasheet provides lifecycle hooks to ensure global helpers are configured at the correct stage of the Blazor application startup. Depending on your Blazor version and startup pattern, you should call one of the following exported functions:

    • beforeStart(): Use this for standard Blazor startup sequences.
    • beforeWebStart(): Use this for newer Blazor Web startup sequences.

    Both functions internally call setupGlobals() to prepare the environment.

    // For standard Blazor startup
    import { beforeStart } from './BlazorDatasheet.lib.module.js';
    
    // Call this during your app initialization
    beforeStart();
  11. Implement and use ICommand for sheet manipulation

    main

    The ICommand interface defines a contract for operations that can be executed on a Sheet. Commands allow you to encapsulate logic for manipulating data or state within the datasheet.

    To use a command, you must implement:

    • Execute(Sheet sheet): Performs the command's logic. Returns true if successful, false otherwise.
    • CanExecute(Sheet sheet): Determines if the command is currently valid to run on the provided sheet.

    ICommand also supports command chaining, allowing you to attach other commands to run automatically before or after a parent command.

    public class MyCustomCommand : ICommand
    {
        public bool CanExecute(Sheet sheet) => true;
    
        public bool Execute(Sheet sheet)
        {
            // Perform manipulation on the sheet
            return true;
        }
    
        // Implement chaining methods if needed
        public void AttachAfter(ICommand command) { /* ... */ }
        public void AttachBefore(ICommand command) { /* ... */ }
        public IReadOnlyList<ICommand> GetChainedAfterCommands() => new List<ICommand>();
        public IReadOnlyList<ICommand> GetChainedBeforeCommands() => new List<ICommand>();
        public void ClearChainedCommands() { /* ... */ }
        public void ClearChainedAfterCommands() { /* ... */ }
        public void ClearChainedBeforeCommands() { /* ... */ }
    }
  12. Chain commands using ICommand

    main

    You can create complex workflows by chaining ICommand instances. This allows a single command execution to trigger a sequence of other commands.

    • AttachAfter(ICommand command): Schedules a command to run immediately after the current command completes.
    • AttachBefore(ICommand command): Schedules a command to run immediately before the current command starts.
    • ClearChainedCommands(), ClearChainedAfterCommands(), and ClearChainedBeforeCommands(): Remove existing chained commands from the current command instance.