# Render Cells Using a Custom Column

## Content

Custom columns provide programmatic control over cell rendering and direct access to the underlying row data through `row.DataItem`.
This approach is suitable for reusable rendering logic, event-driven interactions, and advanced rendering scenarios.
**Step 1: Create a Custom Column Class**
Extend `GridColumn` and override the cell rendering behavior.

```auto
public class CustomColumn : GridColumn
{
    [Parameter]
    public Action<object> OnEditClicked { get; set; }

    protected override RenderFragment GetCellContentRenderFragment(
        GridCellType cellType,
        GridRow row)
    {
        if (cellType == GridCellType.Cell)
        {
            return new RenderFragment(builder =>
            {
                builder.OpenElement(0, "button");

                builder.AddAttribute(
                    1,
                    "onclick",
                    EventCallback.Factory.Create<MouseEventArgs>(
                        this,
                        e => OnEditClicked?.Invoke(row.DataItem)));

                builder.AddContent(2, "Info");

                builder.CloseElement();
            });
        }

        return base.GetCellContentRenderFragment(cellType, row);
    }
}
```

**Step 2: Add the Custom Column to the Grid**

```auto
<FlexGrid ItemsSource="customers"
          AutoGenerateColumns="false">
    <FlexGridColumns>
        <GridColumn Binding="FirstName" />
        <GridColumn Binding="LastName" />

        <CustomColumn OnEditClicked="ShowEditPopup" />
    </FlexGridColumns>
</FlexGrid>
```

>type=note
> Note: Accessing data through `SelectedItem` or `SelectedIndex` is supported, but it depends on the current grid selection state and is not recommended for reusable rendering scenarios.

**Step 3: Handle the Click Event**
Access the row data directly through `row.DataItem`.

```auto
@code {
    void ShowEditPopup(object dataItem)
    {
        var customer = dataItem as Customer;

        // Process customer data
    }
}
```

## Complete Code Example

### Custom Column Class

```auto
public class CustomColumn : GridColumn
{
    [Parameter]
    public Action<object> OnEditClicked { get; set; }

    protected override RenderFragment GetCellContentRenderFragment(
        GridCellType cellType,
        GridRow row)
    {
        if (cellType == GridCellType.Cell)
        {
            return new RenderFragment(builder =>
            {
                builder.OpenElement(0, "button");

                builder.AddAttribute(1, "class", "btn btn-sm btn-primary");

                builder.AddAttribute(
                    2,
                    "onclick",
                    EventCallback.Factory.Create<MouseEventArgs>(
                        this,
                        e => OnEditClicked?.Invoke(row.DataItem)));

                builder.AddContent(3, "Info");

                builder.CloseElement();
            });
        }

        return base.GetCellContentRenderFragment(cellType, row);
    }
}
```

**Usage in the Page**

```auto
<FlexGrid ItemsSource="customers"
          IsReadOnly="true"
          AutoGenerateColumns="false">
    <FlexGridColumns>
        <GridColumn Binding="FirstName" Header="First Name" />
        <GridColumn Binding="LastName" Header="Last Name" />

        <CustomColumn OnEditClicked="ShowEditPopup" />
    </FlexGridColumns>
</FlexGrid>

@code {
    List<string> info = new();

    void ShowEditPopup(object dataItem)
    {
        var customer = dataItem as Customer;

        info.Add($"Customer: {customer.FirstName} {customer.LastName}");
    }
}
```

After completing these steps, FlexGrid displays standard data columns together with a custom button column generated through a `GridColumn` override.
Each button is associated directly with its corresponding row data through `row.DataItem`. When the button is clicked, the related `Customer` object is passed to the event handler.

## Add Loading Indicators and Popup Windows

Custom columns can be combined with loading indicators and popup windows to support asynchronous workflows and provide visual feedback during processing operations.

### Add a Loading Indicator

**Step 1: Reuse the Custom Column**
Reuse the existing `CustomColumn` implementation to maintain consistent row-level interaction behavior.
**Step 2: Add a Loading Indicator Container**
Wrap the FlexGrid inside a container that supports overlay rendering.

```auto
<div style="position:relative;">

    <FlexGrid ItemsSource="customers"
              AutoGenerateColumns="false">
        <FlexGridColumns>
            <GridColumn Binding="FirstName" />
            <GridColumn Binding="LastName" />

            <CustomColumn
                OnEditClicked="((d) => ShowEditPopup((Customer)d))" />
        </FlexGridColumns>
    </FlexGrid>

    @if (IsLoading)
    {
        <div style="
            position:absolute;
            left:50%;
            top:50%;
            transform:translate(-50%, -50%);
            background-color:#5252DF;
            border-radius:20px;
            padding:12px;">

            <p style="font-size:26px; color:white;">
                Loading...
            </p>
        </div>
    }
</div>
```

**Step 3: Implement Loading State Logic**
Introduce an `IsLoading` flag to control the loading overlay.

```auto
@code {
    bool IsLoading;

    async Task ShowEditPopup(Customer data)
    {
        IsLoading = true;

        StateHasChanged();

        await Task.Delay(3000);

        // Process customer data

        IsLoading = false;
    }
}
```

After the operation completes, the loading overlay is removed and normal grid interaction resumes.

>type=note
> Note: The loading overlay temporarily covers the grid interface while processing is in progress. This behavior prevents additional user interaction during the operation.

### Display Row Data in a Popup Window

`C1Window` can be used together with custom columns to display row-specific data or asynchronous processing results.
**Step 1: Add the Popup Window**

```auto
<FlexGrid ItemsSource="customers"
          AutoGenerateColumns="false">
    <FlexGridColumns>
        <CustomColumn
            OnEditClicked="((d) => ShowEditPopup((Customer)d))" />
    </FlexGridColumns>
</FlexGrid>

<C1Window @ref="window">
    <PopupHeader>Data</PopupHeader>

    <PopupContent>
        Loading...
    </PopupContent>
</C1Window>
```

Step 2: Implement Asynchronous Processing

```auto
@code {
    C1Window window;

    async Task ShowEditPopup(Customer data)
    {
        window.Open();

        await Task.Delay(3000);

        // Process data

        window.Close();
    }
}
```

**Complete Code Example**

```auto
<FlexGrid ItemsSource="customers"
          AutoGenerateColumns="false">
    <FlexGridColumns>
        <CustomColumn
            OnEditClicked="((d) => ShowEditPopup((Customer)d))" />
    </FlexGridColumns>
</FlexGrid>

<C1Window @ref="window">
    <PopupHeader>Data</PopupHeader>

    <PopupContent>
        Loading...
    </PopupContent>
</C1Window>

@code {
    C1Window window;

    async Task ShowEditPopup(Customer data)
    {
        window.Open();

        await Task.Delay(3000);

        // Process data

        window.Close();
    }
}
```

After completing these steps, FlexGrid supports asynchronous row-level interaction through popup windows and loading indicators.
When a row action button is clicked, the application opens a popup window or displays a loading overlay while the operation is processed asynchronously.
As a result, the interface provides responsive user feedback during long-running operations without blocking the application workflow.

***

### Toast Notification

Display a toast notification after grid rendering or data loading using [C1.Blazor.Input.C1Popup](/componentone/api/blazor/online-blazor/dotnet-api/C1.Blazor.Input/C1.Blazor.Input.C1Popup.html).
The following code example demonstrates the use of **C1Popup**:

```csharp
@using C1.Blazor.Grid
@using C1.Blazor.Input

@if (!_loadRequested)
{
    <button @onclick="Load">load data</button>
}
else
{
    <FlexGrid @ref="grid" ItemsSource="customers"
              IsReadOnly="true"
              AutoGenerateColumns="false"
              SelectionMode="GridSelectionMode.Row"
              Style="@("height:40vh")"
              ColumnHeaderStyle="@("background-color:#eee;color:black;font-weight:bold;")">
        <FlexGridColumns>
            <GridColumn Binding="FirstName" Header="First Name" />
            <GridColumn Binding="LastName" Header="Last Name" />
            <GridColumn Binding="Address" Header="Address" />
            <GridColumn Binding="City" Header="City" />
            <GridColumn Binding="PostalCode" Header="Postal Code" />
            <GridColumn Binding="Email" Header="Email" />

            <CustomColumn OnEditClicked="((d) => ShowEditPopup((Customer)d))" />

        </FlexGridColumns>
    </FlexGrid>
}
<C1Popup @ref="popupLoading" IsDarkOverlay="true">
    <PopupContent>
        <div class="toast-content">
            <span class="toast-icon">...</span>
            <div class="toast-text">
                <strong>Loading Data...</strong>
                <p>Please Wait</p>
            </div>
        </div>
    </PopupContent>
</C1Popup>
<C1Popup @ref="popupLoaded" IsDarkOverlay="true" IsDraggable="true">
    <PopupContent>
        <div class="toast-content">
            <span class="toast-icon">✔</span>
            <div class="toast-text">
                <strong>Data Loaded</strong>
                <p>@customers?.Count records loaded successfully.</p>
            </div>
        </div>
    </PopupContent>
</C1Popup>

@code {
    FlexGrid grid;
    ObservableCollection<Customer> customers;
    List<string> info = new();
    C1Popup popupLoading;
    C1Popup popupLoaded;
    
    bool _loadRequested;
    bool _dataFinishedLoading;
    
    async Task Load()
    {
        _loadRequested = true;
    
        popupLoading.Open();
      // emulates loading work
        await Task.Delay(3000);
        customers = new ObservableCollection<Customer>(Customer.Create(10000));
        popupLoading.Close();
        _dataFinishedLoading = true;
    }
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (_dataFinishedLoading)
        {
            _dataFinishedLoading = false;
            popupLoaded.Open();
            _ = AutoDismissToastAsync(5000);
        }
    }

    async Task AutoDismissToastAsync(int delayMs)
    {
        await Task.Delay(delayMs);
        popupLoaded.Close();
        StateHasChanged();
    }
}
```