Dynamic Data Binding in Grid without defining column in design time

I have an grid control in my blazor  web application. I would like to know how to set data dynamically in grid and Column should be change dynamically without defining column in markup and also I would like to know  how to set column width dynamically (Auto Colum width )



17 Replies 1 reply marked as answer

VN Vignesh Natarajan Syncfusion Team April 28, 2021 07:06 AM UTC

 
Thanks for contacting Syncfusion support.  
 
Query: “ would like to know how to set data dynamically in grid and Column should be change dynamically without defining column in markup and also I would like to know  how to set column width dynamically (Auto Colum width )” 
 
We have analyzed your query and we understand that you want to change the Grid data dynamically. And also you want to generate the columns dynamically with auto fitted column. We have achieved your requirement by using AutoFit feature of GridColumn and building the GridColumn dynamically. 
 
Refer the below code example.  
 
@typeparam TValue  
<SfGrid TValue="TValue" DataSource="@Lists" AllowPaging="true"> 
    <GridPageSettings PageSize="5"></GridPageSettings> 
    <GridColumns> 
        @foreach (var prop in typeof(TValue).GetProperties()) 
        { 
            <GridColumn Field="@prop.Name" IsPrimaryKey="@(prop.Name == "OrderID")" AutoFit="true" AllowEditing="@prop.CanWrite"></GridColumn> 
        } 
    </GridColumns> 
</SfGrid> 
 
@code{ 
    [Parameter] 
    public List<TValue> Lists { getset; } 
} 
 
[Index.Razor] 
 
<SfDataGrid TValue="Order" Lists="Orders"></SfDataGrid>
 
 
For your convenience we have prepared a sample which can be downloaded from below  
 
 
Refer our UG documentation for your reference 
 
 
Please get back to us if you have further queries.  
 
Regards, 
Vignesh Natarajan 


Marked as answer

KI KINS April 28, 2021 12:35 PM UTC

Thanks for quick support..

Can I create grid column without loop ??

Is there any properties like "AutoGenerateColumn=True" ..




VN Vignesh Natarajan Syncfusion Team April 29, 2021 04:05 AM UTC

Hi Ismail, 
 
Thanks for the update.  
 
Query: “Can I create grid column without loop ?? Is there any properties like "AutoGenerateColumn=True" .. 
 
Yes, we have provided support to AutoGenerate the Grid column based on the datasource type. Refer our UG documentation for your reference 
 
 
Note: Grid DataSource must be a generic class. AutoGenerated columns is not supported for ExpandoObject and DynamicObjects 
 
Please get back to us if you have further queries.  
 
Regards, 
Vignesh Natarajan  
 



JA James replied to Vignesh Natarajan October 24, 2021 10:44 PM UTC

The answer is great and I got it working, but how do I get the RowSelected from the @typeparam SearchClass?


You can see in the code at the bottom where I cannot get the id


@typeparam SearchClass


<SfGrid DataSource="@Data" TValue="SearchClass" AllowPaging="true" AllowFiltering="true" AllowSorting="true">

    <GridEvents RowSelected="@GetSelectedRecord" TValue="SearchClass"></GridEvents>

    <GridPageSettings PageCount="5"></GridPageSettings>

    <GridColumns>


        @foreach (var prop in typeof(SearchClass).GetProperties())

        {

            if (prop.Name == "id")

            {

                       <GridColumn Field="@prop.Name" IsPrimaryKey="true" ></GridColumn>

            }

            else

            {

                       <GridColumn Field="@prop.Name" ></GridColumn>

            }

        }


    </GridColumns>


</SfGrid>


@code{


[Parameter] public List<SearchClass> Data { get; set; }


private void GetSelectedRecord(RowSelectEventArgs<SearchClass> args)

{


// here I need the ID of the row clicked

// If I put args.Data.id it doesnt know what .id is


}

}



RS Renjith Singh Rajendran Syncfusion Team October 25, 2021 11:58 AM UTC

Hi James, 

We are not clear about this exact requirement or problem you are facing. We need the following details to further proceed on this. Please get back to us with the following details to proceed further. 

  1. Share a detailed explanation of your complete requirement or the problem you are facing.
  2. Are you facing difficulties in fetching the ID property value of SearchClass model from args.Data based on the SearchClass model value bind to Grid?
  3. Or do you need to fetch the id attribute value of tr element which you clicked? If so, share the details regarding why you need to fetch the id attribute value?
  4. Share a video demo explaining your requirement.
  5. If possible share a simple issue reproducing sample based on your scenario.

Please get back to us if you need further assistance. 

Regards, 
Renjith R 



JA James replied to Renjith Singh Rajendran October 25, 2021 12:20 PM UTC


Page.razor

<HTMLSearchResults DataSourceTable="SearchResults_DataSourceTable" DataSourceList="SearchResults_DataSourceList" />


Page.razor.cs


        [Parameter] public DataTable SearchResults_DataSourceTable { get; set; }

        [Parameter] public List<System.Dynamic.ExpandoObject> SearchResults_DataSourceList { get; set; }


public void SetupControlForSearchResults()

        {

                DataTable dtSearchResults = MyClass.GetAll();

                List<System.Dynamic.ExpandoObject> lstObj = new List<System.Dynamic.ExpandoObject>();

                foreach (DataRow row in dtSearchResults.Rows)  {

                    System.Dynamic.ExpandoObject e = new System.Dynamic.ExpandoObject();

                    foreach (DataColumn col in dtSearchResults.Columns)

                    {

                        e.TryAdd(col.ColumnName, row.ItemArray[col.Ordinal]);

                    }


                    lstObj.Add(e);

                }

                SearchResults_DataSourceTable = dtSearchResults;

                SearchResults_DataSourceList = lstObj;

}


HTMLSearchResults.razor


@using System.Data

@using Syncfusion.Blazor.Grids

@inject NavigationManager NavManager

@try

{

    <SfGrid DataSource="@DataSourceList" TValue="System.Dynamic.ExpandoObject" AllowPaging="true" AllowFiltering="true" AllowSorting="true">

        <GridEvents RowSelected="@GetSelectedRecord" TValue="System.Dynamic.ExpandoObject" ></GridEvents>

        <GridPageSettings PageCount="5"></GridPageSettings>

        <GridColumns>

            @foreach (DataColumn col in DataSourceTable.Columns)

            {

                <GridColumn [email protected] />

            }

        </GridColumns>

    </SfGrid>

}

catch (Exception ex)

{

    throw ex;

}

@code {


    [Parameter] public DataTable DataSourceTable { get; set; }

    [Parameter] public List<System.Dynamic.ExpandoObject> DataSourceList { get; set; }


    public void GetSelectedRecord(RowSelectEventArgs<System.Dynamic.ExpandoObject> args)

    {

        // This is where I cannot get the ID for the RowSelected

    }

}



JA James replied to James October 25, 2021 12:44 PM UTC

I am sorry, I am having trouble pasting the code into this comment box, it keeps losing the code so I will add this extra reply to explain.


The code I just posted is a HTMLSearchResults.razor component which I want to reuse for every page in my website. I want to dynamically load the DataTable from the parent Page.razor and send in the 

DataTable DataSourceTable

List<System.Dynamic.ExpandoObject> DataSourceList


Everything works ok to build the grid dynamically with headers and data but the issue I am having is because I am using System.Dynamic.ExpandoObject for the RowSelected, it will never find the id of the row clicked.

As the code above states I have:

<GridEvents RowSelected="@GetSelectedRecord" TValue="System.Dynamic.ExpandoObject" ></GridEvents>


public void GetSelectedRecord(RowSelectEventArgs<System.Dynamic.ExpandoObject> args){

// here is where I cannot find the id of the row clicked

}



VN Vignesh Natarajan Syncfusion Team October 26, 2021 06:08 AM UTC

Hi James,  
 
Thanks for sharing the details.  
 
Query: “the issue I am having is because I am using System.Dynamic.ExpandoObject for the RowSelected, it will never find the id of the row clicked. 
 
We have analyzed your query and we suggest you to achieve your requirement using the RowData property of RowSelected event of Grid. We have passed the selected record details in the RowSelected event as an argument. It will be inform of ExpandoObject and it can be accessed by converting it to dynamic object.  
 
Refer the below code example.  
 
@try 
{ 
    <SfGrid DataSource="@DataSourceList" TValue="System.Dynamic.ExpandoObject" AllowPaging="true" AllowFiltering="true" AllowSorting="true"> 
        <GridEvents RowSelected="@GetSelectedRecord" TValue="System.Dynamic.ExpandoObject"></GridEvents> 
        <GridPageSettings PageCount="5"></GridPageSettings> 
        <GridColumns> 
            @foreach (DataColumn col in DataSourceTable.Columns) 
            { 
                <GridColumn Field=@col.ColumnName /> 
            } 
        </GridColumns> 
    </SfGrid> 
} 
catch (Exception ex) 
{ 
    throw ex; 
} 
@code { 
    [Parameter] public DataTable DataSourceTable { getset; } 
    [Parameter] public List<System.Dynamic.ExpandoObject> DataSourceList { getset; } 
    public void GetSelectedRecord(RowSelectEventArgs<System.Dynamic.ExpandoObject> args) 
    { 
        var data = args.Data as dynamic; 
        Console.WriteLine(data.CustomerID);         
    } 
} 
 
 
Kindly refer the below sample for your reference 
 
 
Please get back to us if you have further queries.  
 
Regards, 
Vignesh Natarajan  



JA James replied to Vignesh Natarajan October 27, 2021 01:38 AM UTC

You are a star, thank you very much.


Support from syncfusion is amazing, not only do you answer the question, you also give a working example... very much appreciated.


Thank you for your time in replying


Regards

James



VN Vignesh Natarajan Syncfusion Team October 27, 2021 03:29 AM UTC

Hi James,  

Thanks for the update..  

We are glad to hear that you have achieved your requirement using our solution.  

Kindly get back to us if you have further queries.  

Regards, 
Vignesh Natarajan  



KK Kerry Kerschbaumer replied to Vignesh Natarajan February 23, 2022 03:31 PM UTC

Hi Vignesh,

This example is already helpful and I got it to work.

However, my problem has one complexity more as I don't need a "template" grid working for multiple tables, but the columns of the table being dynamic itself.

My data object looks like this:

    public class Effort
    {
        public int Id { get; set; } //just for db index
        public int Task { get; set; } //id of items in task table
        public int Role { get; set; } //id of items in roles table
        public string tName { get; set; } //name of tasks in task table (show as row title)
        public string rName { get; set; } //name of roles in role table (show as column title)
        public double eHrs { get; set; } //the value for every task/role table cell
    }
....
<DynTable TValue="Effort" Lists="efforts"></DynTable>
....
List<Effort> efforts = new List<Effort>();
 


Currently the table shows one row for each tasks/role combination instead of the matrix between tasks and roles. 

Would that also be possible? I know that needs a second generic loop for the columns, but am not sure whether the mapping to the effort values would be correct?


Thanks a lot,

Klaus.



RN Rahul Narayanasamy Syncfusion Team February 24, 2022 01:55 PM UTC

Hi Klaus, 

Greetings from Syncfusion. 

From your shared details, we suspect that you want to show the value for the column from the other table data based on the common field in the both tables (foreignkey value). If yes, we have already have the support for this requirement.  

Find the below documentation link for your reference. 


Whether did you want to achieve something like the below requirement? Also, we need some details regarding your requirement. Please share the below details which will be helpful to validate and provide a better solution. 

  • Could you please check whether did you want to achieve the same requirement from your end?
  • Whether did you want to achieve the above requirement with dynamic data(ExpandoObject data)?
  • Share your exact requirement.

Regards, 
Rahul 



KK Kerry Kerschbaumer replied to Rahul Narayanasamy February 24, 2022 08:58 PM UTC

Hi Rahul,

Thanks for your reply.

Yes, I do have the column headers in one table and row headers in another table. I have no issue with showing those headers, but how to dynamically create the table structure.

I already succeeded now to use ExpandoObject to create the structure, but am stuck now at update features due to th e read/only definition of the dictionary structure.

Would it help to convert the ExpandoObject to a list first or use a json string instead as table data?


Thanks

Klaus.



VN Vignesh Natarajan Syncfusion Team February 25, 2022 10:24 AM UTC

Hi Klaus,  
 
Thanks for the update.  
 
Query: “I have no issue with showing those headers, but how to dynamically create the table structure. I already succeeded now to use ExpandoObject to create the structure, but am stuck now at update features due to the read/only definition of the dictionary structure.” 
 
We understand that you are facing issue while binding the datasource to Grid in form of ExpandoObject. But we need some more details the issue you are facing. So kindly share the following details.  
 
  1. Share the details about the Grid datasource.
  2. Also share the details on how you have bound data to Grid.
  3. Share the Grid code example.
  4. Are you facing any script while performing CRUD / Update action.
  5. If possible share a simple sample to validate the reported query further at our end.
 
Above requested details will be very helpful for us t validate the reported query further at our end and provide solution as early as possible.  
 
Regards, 
Vignesh Natarajan 



KK Kerry Kerschbaumer replied to Vignesh Natarajan February 25, 2022 05:30 PM UTC

Hi, 

thanks for your reply.

I think most of your questions are answered when looking at the code of the razor page:


@if (dynList == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <SfGrid TValue="ExpandoObject" DataSource="@dynList" AllowPaging="false" Toolbar="@ToolbarItems" AllowSorting="true" EnableAltRow="true" AllowGrouping="false" AllowTextWrap="true" Height="500">
        <GridEvents OnActionComplete="EffortsActionHandler" OnActionFailure="ActionFailureHandler" TValue="ExpandoObject"></GridEvents>
        <GridEditSettings AllowAdding="false" AllowDeleting="false" AllowEditing="true" AllowEditOnDblClick="true" ></GridEditSettings>
        <GridColumns>
            @foreach (var prop in cols)
            {
                @if (prop.Name == "Task")
                {
                    <GridColumn Field="@prop.Name" Width="250" IsPrimaryKey="true" AllowEditing="@prop.CanWrite" TextAlign="TextAlign.Left"></GridColumn>
                }
                else
                {
                    <GridColumn Field="@prop.Name" Width="120" Format="N2" AllowEditing="@prop.CanWrite" TextAlign="TextAlign.Right">
                       <EditTemplate>
                            <SfNumericTextBox TValue="Object" @bind-Value="@((context as Object).FirstOrDefault(x => x.Key == prop.Name).Value)" Min=0 ShowSpinButton=false></SfNumericTextBox>
                        </EditTemplate>
                    </GridColumn>
                }
            }
        </GridColumns>
    </SfGrid>
    <p></p>
}


@code {
    List dynList = null;
    dynamic propEff = new ExpandoObject();
    List<PTask> tasks;
    List<Role> roles;
    List<colProp> cols = new List<colProp>();
    private List<string> ToolbarItems = new List<string>() { "Edit", "Update", "Cancel" };

    public class colProp
    {
        public string Name { get; set; }
        public bool CanWrite { get; set; }
    }

    protected override async Task OnInitializedAsync()
    {
        tasks = await db.PTask.ToListAsync();
        roles = await db.Roles.ToListAsync();
        int ix = 1; int iy = 1;
        string tName = ""; string rName = "";

        //### Create dynamic template for table headers
        dynamic propEff = new ExpandoObject();
        ((IDictionary<String, Object>)propEff).Add("tID", 0);
        ((IDictionary<String, Object>)propEff).Add("Task", "");
        cols.Add(new colProp { Name = "Task", CanWrite = false });
        foreach (Role pRole in roles)
        {
            ((IDictionary<String, Object>)propEff).Add(pRole.NAME, 0);
            cols.Add(new colProp { Name = pRole.NAME, CanWrite = true });
        }

        //### Get DB values into table object
        dynList = db.GetDynEfforts().Result.ToList();

        //### Create dummy entries if db is empty
        if (dynList == null)
        {
            //### Create new dynamic object
            dynList = new List<ExpandoObject>();

            foreach (PTask pTask in tasks)
            {
                ix = 1;
                tName = tasks.Where(t => t.Id == iy).FirstOrDefault().NAME;
                dynamic dynEff = new ExpandoObject();
                ((IDictionary<String, Object>)dynEff).Add("tID", iy);
                ((IDictionary<String, Object>)dynEff).Add("Task", tName);
                foreach (Role pRole in roles)
                {
                    rName = roles.Where(r => r.Id == ix).FirstOrDefault().NAME;
                    ((IDictionary<String, object>)dynEff).Add(pRole.NAME, 0);
                    ix++;
                }
                dynList.Add(dynEff);
                await db.SetDynEfforts(dynEff);
                dynEff = null;
                iy++;
            }
        }
    }


    public async void EffortsActionHandler(ActionEventArgs<ExpandoObject> args)
    {
        if (args.RequestType != Syncfusion.Blazor.Grids.Action.BeginEdit)
        {
            if (!db.SetDynEfforts(args.Data).Result) throw new InvalidOperationException("Error during DB save operation");
        }
    }


The input and output db data looks like this:

Id PTask Role eHrs

1 1 1 0

2 1 2 3

3 1 3 0

4 1 4 4

5 1 5 0

6 1 6 0


The db Interface is realised through the db.GetDynEfforts and db.SetDynEfforts and works correctly.
Problematic is especially the "@bind-Value" param in the EditTemplate for the numeric editor (which is necessary as it needs to edit decimal values, which I understand is not possible with the regular number format cell).


Thanks

Klaus.



RN Rahul Narayanasamy Syncfusion Team February 28, 2022 01:47 PM UTC

Hi Klaus, 

Thanks for the update. 

We are happy to hear that you achieved your requirement by yourself. Please get back to us if you need further assistance. 

Regards, 
Rahul  



KK Kerry Kerschbaumer February 28, 2022 06:45 PM UTC

Dear SyncFusion Team,

I found now an (already existing) suitable description in SF documentation regarding using binding to a ExpandoObject (Incl. this video: https://www.youtube.com/watch?v=Xhaw3DdHmJk&t=448s)

That actually helped a lot and I got the hole solution working now with the following components:

1) a dynamic object with respective methods to use for read & update:

    public class EffortItem : DynamicObject
{
public Dictionary EffortDict = new Dictionary();

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name;
return EffortDict.TryGetValue(name, out result);
}

public override bool TrySetMember(SetMemberBinder binder, object value)
{
EffortDict[binder.Name] = value;
return true;
}

public override IEnumerable GetDynamicMemberNames()
{
return this.EffortDict?.Keys;
}

public void Add(string Key, object Value)
{
EffortDict.Add(Key, Value);
}

public object Get(string Key)
{
var ret = (EffortDict.TryGetValue(Key, out object result)) ? result : null;
return ret;
}
}


2) Database interface methods to load and store the dynamic object data from/into database:

        //##################################
//### Converter for Efforts data
public async Task> GetDynEfforts()
{
List dynList = new List();

try
{
//### Get DB data
var efforts = await this.Efforts.ToListAsync();
var tasks = await this.PTask.ToListAsync();
var roles = await this.Roles.ToListAsync();
int ix = 1; double myEff = 0.0;
string tName = "";

if (efforts.Count == 0) return null;

//### Create dynamic object
dynList = Enumerable.Range(1, tasks.Count).Select((iy) =>
{
ix = 1;
tName = tasks.Where(t => t.Id == iy).FirstOrDefault().NAME;
EffortItem dynEff = new EffortItem();
dynEff.Add("tID", iy);
dynEff.Add("Task", tName);
foreach (Role pRole in roles)
{
myEff = efforts.First(e => e.PTask == iy && e.Role == ix).eHrs;
dynEff.Add(pRole.NAME, (double) myEff);
ix++;
}
return dynEff;
}).Cast().ToList();

return dynList;


} catch (Exception args)
{
MessagingCenter.Send(new AlertContent("Get Data Error", args.Message + "/" + args.InnerException, "fas fa-exclamation-triangle"), "AlertNotification");
return null;
}
}

public async Task SetDynEfforts(EffortItem updEfforts)
{
//### Get DB data
Efforts myEffort = null;
var roles = Roles.ToList();
double myEff = 0; int tID;

//### Update db data from dynamic object
try
{
tID = (int)updEfforts.Get("tID");
foreach (Role pRole in roles)
{
var ok = double.TryParse(updEfforts.Get(pRole.NAME).ToString(), out myEff);
if (!ok) myEff = 0;
myEffort = Efforts.Where(e => (e.PTask == tID && e.Role == pRole.Id)).FirstOrDefault();

if (myEffort != null)
{
myEffort.eHrs = myEff;
this.Efforts.Update(myEffort);
} else
{
myEffort = new Efforts
{
PTask = tID,
Role = pRole.Id,
eHrs = myEff
};
this.Efforts.Add(myEffort);
}
}

await this.SaveChangesAsync();
return true;
} catch
{
return false;
}
}

3) the Razor page to dynamically show the table in 2 dynamic dimensions and CRUD handling events:

@if (dynList == null)
{

Loading...

}
else

{
   <SfGrid DataSource="@dynList" AllowPaging="false" Toolbar="@ToolbarItems" AllowSorting="true" EnableAltRow="true" AllowGrouping="false" AllowTextWrap="true" Height="500">
        <GridEvents OnActionComplete="EffortsActionHandler" OnActionFailure="ActionFailureHandler" TValue="EffortItem"></GridEvents>
        <GridEditSettings AllowAdding="false" AllowDeleting="false" AllowEditing="true" AllowEditOnDblClick="true"></GridEditSettings>
        <GridColumns>
            @foreach (var colName in dynList[0].GetDynamicMemberNames())
            {
                @switch (colName) {
                    case "Task":
                        <GridColumn Field="@colName" Width="250" IsPrimaryKey="true" AllowEditing="false" TextAlign="TextAlign.Left"></GridColumn>
                        break;
                    case "tID":
                        break;
                    default:
                    <GridColumn Field="@colName" Width="120" Format="N2" AllowEditing="true" TextAlign="TextAlign.Right">
                    </GridColumn>
                        break;
                    }
             }
        </GridColumns>
    </SfGrid>
    <p></p>
}

@code {
List dynList { get; set; } = null;
dynamic propEff = new ExpandoObject();
List tasks;
List roles;
private List ToolbarItems = new List() { "Edit", "Update", "Cancel" };

protected override async Task OnInitializedAsync()
{
tasks = await db.PTask.ToListAsync();
roles = await db.Roles.ToListAsync();
int ix = 1;
string tName = ""; string rName = "";

//### Get DB values into table object
dynList = await db.GetDynEfforts();

//### Create dummy entries if db is empty
if (dynList == null)
{
//### Create new dynamic object
dynList = new List();

dynList = Enumerable.Range(1, tasks.Count).Select((iy) =>
{
ix = 1;
tName = tasks.Where(t => t.Id == iy).FirstOrDefault().NAME;
EffortItem dynEff = new EffortItem();
dynEff.Add("tID", iy);
dynEff.Add("Task", tName);
foreach (Role pRole in roles)
{
dynEff.Add(pRole.NAME, (double)0.0);
ix++;
}
return dynEff;
}).Cast().ToList();

//### Save to db as initial setup
foreach (EffortItem dynEff in dynList) db.SetDynEfforts(dynEff);
}
}

public async void EffortsActionHandler(ActionEventArgs args)
{
if (args.RequestType == Syncfusion.Blazor.Grids.Action.Save)
{
//if (!db.SetDynEfforts(args.Data).Result) throw new InvalidOperationException("Error during DB save operation");
var ret = db.SetDynEfforts(args.Data);
}
}

Loader.
Up arrow icon