BlazorT3_1200x303

You can use the Telerik DataGrid for Blazor with any data source you want just by leveraging the OnRead event.

If you’ve got the data you want the DataGrid for in Telerik UI for Blazor to manage in a List, then most of your work is done. But even if your data is in some other format, all you need is some code in the OnRead method to bind your data to the DataGrid.

By default, the Telerik DataGrid for Blazor is designed to work with a collection that implements the IList interface (or either of the interfaces it inherits from: ICollection and IEnumerable) and holds all the data to be displayed. “Out of the box,” the DataGrid doesn’t support other data sources.

Fortunately, the grid provides the OnRead event where you can convert the portion that you want to display in the grid (typically, a page’s worth of data) into the grid’s preferred format: a List. I’ll demonstrate the technique using a Dictionary, but the structure of the solution is the same for any data source that you might want to tie to the DataGrid.

For my case study, I’m going to assume that my data is in a Dictionary and that the key for the dictionary isn’t a property in the object. Instead, my Dictionary holds Employee objects where the key for is the date and time the object was retrieved. The Dictionary would be loaded with code like this:

Dictionary<int, Employee> empData = new Dictionary<int, Employee>();
emp = EmployeeRepostory.GetEmployee(492);
empData[DateTime.Now] = emp;

Setting Up for Non-List Data

The first step in having the DataGrid work with a “non-List” data source is to create an object and a collection that the grid can work with. For my example, I created this new class that has all the property from the Employee object and adds a new property to hold the key that the object is stored under (the current date and time in my example):

public class empGrid: Employee
{
public DateTime key {get; set;}
}

The second step is to tie a method to the grid’s OnRead event and convert the data from your “non-List” data source (my Dictionary) into a List of your new object.

The OnRead event is called when the grid is first loaded and after every data update (this ensures that the values displayed in the grid are up to date). In my case study, I’ll tie the OnRead event to a method in my code I’ve called ReadingRows.

However, if I do use the OnRead method, I also need to set the grid’s TotalCount property. In my case, I will have retrieved all of my Employee objects into my empData collection so I can use its Count method to set the grid’s TotalCount attribute. If your data source doesn’t have a Count or Length member, you can just tie the grid’s TotalCount attribute to a field and set that field to the number of items to be displayed.

I need to set a couple of other attributes on the grid, though. First, I’ll set the grid’s PageSize attribute to an int field in my code (which I’ve called pageSize). I’m also going to need to access the grid itself, so I set the grid’s @ref attribute to another field (which I’ve called theGrid). My theGrid field needs to be declared as TelerikGrid and tied to the type of object that I’ll be displaying (that would be my empGrid object).

That means that the markup for my grid now looks like this:

<TelerikGrid Data="@pageData" Pageable="true" PageSize="@pageSize"
                      @ref="theGrid"
                      OnRead="@ReadingRows" TotalCount="@empData.Count()">

The start of my code with the fields that I need to support that markup looks like this:

TelerikGrid<empGrid> theGrid;
IDictionary<DateTime, Employee> empData = new Dictionary<DateTime, Employee>();
IEnumerable<empGrid> pageData = new List<empGrid>();
int pageSize = 10;

In this code, empData is my “non-List” data source—for the rest of this post, I’ll call it my “underlying data source.” The pageData collection is the List that the grid will actually work with. It will hold a page’s worth of data, extracted from the underlying data source in the OnRead event as the user pages forward and backward through the data.

That EOF field is only required if you’re retrieving data as the user pages forward, rather than retrieving it all up front, so I’ll put off discussing it until later.

Crafting the OnRead Method

In the OnRead method, you have to do three things:

  1. Determine what page of the grid is displayed (how far through your data has the user paged)
  2. Grab a page’s worth of data from the underlying data source (my empData Dictionary)
  3. Convert that into the collection that the grid is bound to (my pageData List)

To grab “a page’s worth of data,” I need to know how many objects the grid is displaying in a page. Since I’m controlling the number of items on the grid with my own pageSize field, that’s easy to determine.

Determining the page the grid is displaying is a little trickier. Normally, you’d be able to retrieve the page number from the grid’s Page property. However, the first time the OnRead event method is called, the grid isn’t available… which tells you that you’re on page 1 of the grid.

Using the page number and the page size, I can determine how many objects I have to skip over to get to the objects for the current page. That calculation is at the start of my OnRead method:

void ReadingRows(GridReadEventArgs args)
{            
   int skip;
   if (theGrid == null)
   {
      skip = 0;
    }
    else
    {
       skip = (theGrid.Page - 1) * pageSize;
     }

The next step is to grab a pageSize’s worth of data from my underlying data source and load the collection that the grid is tied to with that data. Because my underlying data source is a Dictionary, I can just grab the right KeyValuePairs from the dictionary and create my empData object from them. This code loads the grid’s collection from my Dictionary:

pageData = empData.Skip(skip).Take(pageSize).Select(kvp => new empGrid
{
   key = kvp.Key,
   Id = kvp.Value.Id,
   Department = kvp.Value.Department,
   FullName = kvp.Value.FullName,
   HireDate = kvp.Value.HireDate
});

The code that you’ll need to load the pageData collection will differ, depending on the nature of your underlying data source.

Handling Updates, Deletes, and Inserts

Once you’ve attached a method to the OnRead event, you’ll also have to write your own code to handle updates, deletes, and inserts. That means handling (at the very least) the grid’s OnCreate, OnDelete, and OnUpdate events (for more on these events see my earlier post. You’ll also need to add to your grid:

  • A command bar to hold an Add button that calls a method to set up the grid to add a new item.
  • Column command buttons to handle both deleting rows and putting rows in edit mode (plus Save and Cancel buttons to be used in edit mode).

That means your grid’s markup will look something like this:

<TelerikGrid Data="@pageData" Pageable="true" PageSize="@pageSize"
                     @ref="theGrid"
                     OnRead="@ReadingRows" TotalCount="@empData.Count()"
                     OnCreate="@Creating"
                     OnDelete="@Deleting"
                     OnUpdate="@Updating">

            <GridToolBar>
                <GridCommandButton OnClick="e => Adding(e)" Icon="add">
                    Add Employee
                </GridCommandButton>
            </GridToolBar>

            <GridColumns>
                <GridCommandColumn>
                    <GridCommandButton Command="Edit" Icon="edit">Edit</GridCommandButton>
                    <GridCommandButton Command="Delete" Icon="delete">Delete</GridCommandButton>
                    <GridCommandButton Command="Save" Icon="save"
                                       ShowInEdit="true">Update</GridCommandButton>
                    <GridCommandButton Command="Cancel" Icon="cancel"
                                       ShowInEdit="true">Cancel</GridCommandButton>
                </GridCommandColumn>

The code that you’ll put in these methods just has to update your underlying data source (empData, in my example). Your OnRead method will be called after every update and you’ve already set that event up to refetch a page’s worth of the updated data from the underlying collection.

With a Dictionary, the code to handle deletes is very simple. The method is passed a GridCommandEventArgs object whose Item parameter holds a copy of the object in the row to be deleted. I just pass to the Dictionary’s Remove the property in object that acts as the Dictionary’s key value. For my empGrid object, that’s the property called key:

async void Deleting(GridCommandEventArgs e)
{
    empData.Remove(((empGrid)e.Item).key);
}

The update method is almost as simple because, again, the method is passed a GridCommandEventArgs object. I use the Item property to retrieve the updated object and replace it in my collection, using the key property to update the right object:

async Task Updating(GridCommandEventArgs e)
{
    empGrid emp = (empGrid) e.Item;
    empData[emp.key] = emp;
}

Supporting adding new items requires the most code. First, you need to respond to the user clicking on the Add button in the command bar by adding a blank row to the grid. That involves creating a GridState object, preparing a new empData object, and using that new empData object to set the state’s InsertedItem property. Once you’ve done that, you just need to update the grid with the new state.

That’s what this code does (and sets some default values on the new empData object along the way):

async Task Adding(GridCommandEventArgs e)
{
   GridState<empGrid> state = new GridState<empGrid>();
   state.InsertedItem = new empGrid
            {
                Id = empData.Values.Max(e => e.Id) + 1,
                HireDate = DateTime.Now,
                key = DateTime.Now
            };
   await theGrid.SetState(state);
}

When the user clicks the grid’s Save button after creating the new object, the grid’s OnCreate event is raised. As with deletes and updates, in the method you’ve attached to the OnCreate event, you update your underlying data source.

For my Dictionary, that consists of extracting the item from the GridCommandEventArgs’ Item property and adding it to my collection with an appropriate key. Because my “List-compatible” object—empGrid—inherits from the object in my underlying data source—Employee—I can get away with some implicit data conversion in this code:

async void Creating(GridCommandEventArgs e)
{
   empGrid emp = (empGrid)e.Item;
   empData.Add(DateTime.Now, emp);
}

And that’s the power of the OnRead event: The ability to fetch data to convert data from whatever format it’s in to a format the DataGrid will work with.


Peter Vogel
About the Author

Peter Vogel

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter also writes courses and teaches for Learning Tree International.

Related Posts

Comments

Comments are disabled in preview mode.