LoadChildOnDemand not working with CustomAdaptor

I have setup a Tree Grid, all works fine with SfDataAdapter, type ODataV4, but when I change it to use CustomAdapter, as per the sample code for Custom data adapter, it works but CANNOT load the tree in expanded state.

Whether I set LoadChildOnDemand as true or false, it initially loads with the tree as collapsed. It does respond to expand actions (triggered by mouse), but will not load expanded. It will load expanded if Adapter = ODataV4.

Have tried overriding both Read and ReadAsync Events, no difference. Here is my ReadAsync handler:

        public override async Task<object> ReadAsync(DataManagerRequest dm, string key = null) {

            IEnumerable<OrgUnitSummaryResult> DataSource;


            if (orgUnits == null) {
                try {
                    var test = await sODataClient.For<OrgUnit>().Function<OrgUnitSummaryResult>("GetSummary").ExecuteAsEnumerableAsync();
                    orgUnits = test.ToList();
                }
                catch (AccessTokenNotAvailableException exception) {
                    exception.Redirect();
                }
                catch (Exception e) {
                    string msg = e.Message;
                }
            }


            if (orgUnits == null) return(null);


            DataSource = orgUnits;


            // Searching
            if (dm.Search != null && dm.Search.Count > 0) {
                DataSource = DataOperations.PerformSearching(DataSource, dm.Search); }
            // Sorting
            if (dm.Sorted != null && dm.Sorted.Count > 0) {
                DataSource = DataOperations.PerformSorting(DataSource, dm.Sorted);
                await tg.ExpandAllAsync();
            }
            // Filtering
            if (dm.Where != null && dm.Where.Count > 0) {
                DataSource = DataOperations.PerformFiltering(DataSource, dm.Where, dm.Where[0].Operator); }


            int count = DataSource.ToList().Count();
            //Paging
            if (dm.Skip != 0) {
                DataSource = DataOperations.PerformSkip(DataSource, dm.Skip); }
            if (dm.Take != 0) {
                DataSource = DataOperations.PerformTake(DataSource, dm.Take); }


            var result = dm.RequiresCounts ? new DataResult() { Result = DataSource, Count = count } : (object)DataSource;
            return result;
        }



6 Replies

FS Farveen Sulthana Thameeztheen Basha Syncfusion Team February 9, 2022 02:45 PM UTC

Hi Phil, 

Query#:- when I change it to use CustomAdapter, as per the sample code for Custom data adapter, it works but CANNOT load the tree in expanded state 
 
Further analyzing your provided code, you have enabled LoadChildOnDemand as true in SFTreeGrid definition but not handled Children records on Read method. When the LoadChildOnDemand is enabled parent records are rendered in expanded state and we need to handle as like discussed in documentation and Demo links:- 


The custom data binding can be performed in the Tree Grid component by providing the custom adaptor class and you can override the Read or ReadAsync method of the DataAdaptor abstract class for handling LoadChildOnDemand. 

Refer to the code below:- 

<SfTreeGrid TValue="SelfReferenceData" AllowFiltering="true" LoadChildOnDemand="true"  Query="@GridQuery" HasChildMapping="isParent" IdMapping="TaskID" ParentIdMapping="ParentID" AllowSelection="true" GridLines="GridLine.None" Height="100%" Width="100%"   
            TreeColumnIndex="1" AllowPaging="true" AllowSorting="true"> 
    <SfDataManager AdaptorInstance="@typeof(CustomAdaptor)" Adaptor="Adaptors.CustomAdaptor"></SfDataManager> 
    .  .   .  
</SfTreeGrid> 
 
@code{ 
    public static List<SelfReferenceData> TreeData { get; set; } 
    public Query GridQuery { get; set; } 
    protected override void OnInitialized() 
    { 
        TreeData = SelfReferenceData.GetTree().ToList(); 
        this.GridQuery = new Query().AddParams("loadchildondemand", true);  //pass AddParams to DataAdaptor class 
 
    } 
 
 
 
    // Implementing custom adaptor by extending the DataAdaptor class 
    public class CustomAdaptor : DataAdaptor 
    { 
        // Performs data Read operation 
        public override object Read(DataManagerRequest dm, string key = null) 
        { 
            .   .  . 
 
 
 
           int count = DataSource.Cast<SelfReferenceData>().Count(); 
 
            if (dm.Skip != 0) 
            { 
                //Paging 
                DataSource = DataOperations.PerformSkip(DataSource, dm.Skip); 
            } 
            if (dm.Take != 0) 
            { 
                DataSource = DataOperations.PerformTake(DataSource, dm.Take); 
            } 
           if (dm.Params.ContainsKey("loadchildondemand") && dm.Params["loadchildondemand"].Equals(true))    //Based on AddParams we have handled the child records and return data based on that 
            { 
                var GroupData = SelfReferenceData.tree.ToList().GroupBy(rec => rec.ParentID) 
                           .Where(g => g.Key != null).ToDictionary(g => g.Key?.ToString(), g => g.ToList()); 
                foreach (var Record in TreeData.ToList()) 
                { 
                    if (GroupData.ContainsKey(Record.TaskID.ToString())) 
                    { 
                        var ChildGroup = GroupData[Record.TaskID.ToString()]; 
                        if (ChildGroup?.Count > 0) 
                            AppendChildren(ChildGroup, Record, GroupData);   // appending the child records for the respective parent records 
                    } 
 
                } 
                return dm.RequiresCounts ? new DataResult() { Result = TreeData, Count = TreeData.Count } : (object)DataSource; 
           } 
            else { 
                return dm.RequiresCounts ? new DataResult() { Result = DataSource, Count = count } : (object)DataSource;  //when loadChildOnDemand false return data with parentrecords 
 
 
            } 
        } 
    } 
 
    public static void AppendChildren(List<SelfReferenceData> ChildRecords, SelfReferenceData ParentItem, Dictionary<string, List<SelfReferenceData>> GroupData) 
    { 
 
        string TaskId = ParentItem.TaskID.ToString(); 
        var index = TreeData.IndexOf(ParentItem); 
        foreach (var Child in ChildRecords) 
        { 
            string ParentId = Child.ParentID.ToString(); 
            if (TaskId == ParentId) 
            { 
                if (TreeData.IndexOf(Child) == -1) 
                    (TreeData).Insert(++index, Child); 
                if (GroupData.ContainsKey(Child.TaskID.ToString())) 
                { 
                    var DeepChildRecords = GroupData[Child.TaskID.ToString()]; 
                    if (DeepChildRecords?.Count > 0) 
                        AppendChildren(DeepChildRecords, Child, GroupData); 
                } 
            } 
        } 
 
    } 

Please get back to us if you need any further assistance. 
 
Regards, 
Farveen sulthana T 



PH Phil Holmes February 10, 2022 12:11 PM UTC

Thanks Farveen,

Do I read this code sample correctly, that it is taking a dataset with sub-trees of data in the 'tree' field, then it is appending those child records into the 'top-level' list so the children are in order after the parent records, as they would appear in an expanded tree, so that child records are actually in the data TWICE??? i.e. once in the child datasets, but also repeated at the top level of the list?????

This would be nuts!

Interestingly, the DataManager can obviously handle self referencing datasets in all but the CustomAdapter mode. And even the CustomAdapter mode behaves as expected when you expand the nodes of the tree, i.e. it populates the children of the node when clicked, even without this complicated recursive workaround.

If my description of the operation of this code in para 1 is correct, isn't this a workaround for a bug, or incomplete functionality? Are we really suggesting that for the DataManager to work in CustomMode as per other modes, one has to duplicate data in the source set, so that it will work?

Please let me know if I have this wrong.



FS Farveen Sulthana Thameeztheen Basha Syncfusion Team February 14, 2022 03:53 AM UTC

Hi Phil, 

We are working on your query with high Priority and share you further details by 14th February 2022. 

Regards, 
Farveen sulthana T 



FS Farveen Sulthana Thameeztheen Basha Syncfusion Team February 14, 2022 03:42 PM UTC

Hi Phil, 

From our last update, we suspect that you have misunderstood the explanation. While using Remote Data/Custom Binding, all root nodes has been rendered in collapsed state at initial load based on LoadOnDemand concept which is default behavior On expanding the root node, the child nodes will be loaded from the remote server. 

When a root node is expanded, its child nodes are rendered and are cached locally, such that on consecutive expand/collapse actions on root node, the child nodes are loaded from the cache instead from the remote server. 

So you can use the same code to render Child nodes on Expanding. Refer to the code below:- 
public class CustomAdaptor : DataAdaptor 
    { 
        // Performs data Read operation 
        public override object Read(DataManagerRequest dm, string key = null) 
        { 
            IEnumerable<SelfReferenceData> DataSource = TreeData; 
            if (dm.Search != null && dm.Search.Count > 0) 
            { 
                // Searching 
                DataSource = DataOperations.PerformSearching(DataSource, dm.Search); 
            } 
            if (dm.Sorted != null && dm.Sorted.Count > 0) 
            { 
                // Sorting 
                DataSource = DataOperations.PerformSorting(DataSource, dm.Sorted); 
            } 
            if (dm.Where != null && dm.Where.Count > 0) 
            { 
                // Filtering 
                DataSource = DataOperations.PerformFiltering(DataSource, dm.Where, dm.Where[0].Operator); 
            } 
            int count = DataSource.Cast<SelfReferenceData>().Count(); 
            if (dm.Skip != 0) 
            { 
                //Paging 
                DataSource = DataOperations.PerformSkip(DataSource, dm.Skip); 
            } 
            if (dm.Take != 0) 
            { 
                DataSource = DataOperations.PerformTake(DataSource, dm.Take); 
            } 
            return dm.RequiresCounts ? new DataResult() { Result = DataSource, Count = count } : (object)DataSource; 
        } 


Refer to the documentation link:- 

In your query you need to render all childrecords to be in Expanded state at Initial render while using Custom binding. To achieve this requirement we suggest LoadChildOnDemand concept. When the LoadChildOnDemand is enabled parent records are rendered in expanded state.  

Refer to the documentation and Demo links:- 

Please get back to us if you are facing any difficulties on this. 

Regards, 
Farveen sulthana T 



PH Phil Holmes February 16, 2022 08:54 AM UTC

Thanks Farveen,

Well your explanation did involve a whole lot of code that seemed to be taking a nice flat self-referencing dataset, and recursively inserting in same data into a hierarchical tree, so not sure if I misunderstood there or not. You didn't answer that point.

But I do take from your further explanation, something not quite said. The purpose here is to just support a fully expanded tree, using CustomAdapter and a simple self-referencing dataset, as works with other adapters. It seems that by commenting out the line you have highlighted, one achieves that outcome as the CustomAdapter calls 1st with filter ParentId == null, and if we ignore this, the self-referencing dataset populates as per the other Adapters, and as one would expect.

Of course this issue here would then be if you want to support filtering in the CustomAdapter. As this is not my use case here, I'm going to leave this for you or others just now.

So in summary, simply commenting out this line (highlighted) works i.e. don't support filtering. A better outcome would be better.

Thanks,

Phil



FS Farveen Sulthana Thameeztheen Basha Syncfusion Team February 17, 2022 04:35 PM UTC

Hi Phil, 

Thanks for your update. 

Yes you can use without PerformFiltering as per your use case. But if you want to perform Filter action, along with PerformFiltering method  you need to use LoadChildOnDemand concept(for expanding the nodes on RemoteData).  

Please get back to us if you need any further assistance. 

Regards, 
Farveen sulthana T 


Loader.
Up arrow icon