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;
}
|
<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);
}
}
}
} |
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.
|
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;
}
|
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