This is a migrated thread and some comments may be shown as answers.

hierarchy child detail bind to master data proper serialization

6 Answers 433 Views
Grid
This is a migrated thread and some comments may be shown as answers.
AGGELIKI
Top achievements
Rank 1
Iron
AGGELIKI asked on 09 Apr 2019, 10:56 AM

Hello Telerik,

We are using kendo controls for over the past 5 years, and we have recently noticed a bug with the hierarchy grid's child's which contain date columns and filters. We always initialize detail grids on detailnit of master grid, by passing the data which is rendered as a list from master model.

So, to give you an example. Imagine we have a master grid defined like below :

01.@(Html.AthenaParameterConfigGrid<StudentItineraryResult>()
02.                 .Name("studentItinerary")
03.                 .Columns(columns =>
04.                 {                   
05.                     columns.Bound(e => e.Person.Id).Hidden();
06.
08.                     columns.Bound(e => e.Person.FathersName).Width(200);
09.                     columns.Bound(e => e.Person.MothersName).Width(200);
10.                     columns.Bound(e => e.Person.Email).Width(200);
11.                   
23
24.                 })
25.                 .Scrollable(s => s.Enabled(true).Height("auto"))
26.                 .Sortable()
27.                 .Groupable(gr => gr.Enabled(false))
28.                 .HtmlAttributes(new { @class = "hide-vertical-scrollbar atn-tabstrip-grid atn-grid-dropdown-menu" })
29.                 .ClientDetailTemplateId("itineraries")
30.                 .AutoBind(false)
31.                 .DataSource(dataSource => dataSource
32.                     .Ajax()
33.                     .PageSize(10)
34.                     .ServerOperation(false)
35.                     .Model(model => model.Id(p => p.StudentId))
36.                     .Read(read => read.Action("StudentItinerariesSearch", "Student").Data("athena.studentItineraries.searchRequestData"))
37.                     .Events(events => events
38.                         .Error("athena.utilities.onErrorSearch").RequestEnd("athena.utilities.laddaStop")
39.                     )
40.                 )
41.                 .Events(events => events.DetailInit("athena.studentItineraries.detailInit"))
42.                 .Deferred())
And a detail template grid initialized again server side with no datasource read options configured, where BusStopArrivalTime and AtttendeeEventDate(and other fields that we dont show in this case) are proper DateTime fields.
<script id="itineraries" type="text/kendo-tmpl">
      @(Html.Kendo().TabStrip()
                        .Name("tabStrip_#=StudentId#")
                        .SelectedIndex(0)
                        .Animation(animation => animation.Open(open => open.Fade(FadeDirection.In)))
                        .Items(items =>
                        {
                            items.Add().Text(@Html.GetResource(common, "TabStripBasic")).Content(@<text>
                              @(Html.AthenaParameterConfigGrid<StudentItineraryFlat>()
                                    .Name("grid_#=StudentId#")
                                    .Columns(columns =>
                                    {
                                        columns.Bound(o => o.BusStopLabel).Hidden();
                                        columns.Bound(o => o.BusRouteLabel).Width(250);
                                        columns.Bound(o => o.BusStopName).Width(100);
                                        columns.Bound(o => o.AttendeeStatusUserString).Width(80);
                                        // TODO : filtering here does not want to work...
                                        columns.Bound(o => o.BusStopArrivalTime).Width(80).ClientTemplate("\\#= BusStopArrivalTime !=null ?  kendo.toString(kendo.parseDate(BusStopArrivalTime), 't') : '' \\#").Filterable(filterable => filterable.UI(GridFilterUIRole.TimePicker));
                                        // TODO : filtering here does not want to work...
                                        columns.Bound(o => o.AttendeeEventDate).Width(80).ClientTemplate("\\#= AttendeeEventDate !=null ? kendo.toString(kendo.parseDate(AttendeeEventDate), 'd') : '' \\#").Filterable(filterable => filterable.UI(GridFilterUIRole.DatePicker));
                                        columns.Bound(o => o.AttendeeCronLabel).Width(100);
                                        columns.Bound(o => o.AttendeeComment).Width(150);
                                        columns.Bound(o => o.ProfileOuSpecialtySemesterLabel).Width(200);
                                    })
                                    .AutoBind(false)                               
                                    .DataSource(dataSource => dataSource
                                        .Ajax()
                                        .PageSize(5)
                                         
                                        .ServerOperation(false)
 
                                        .Model(model =>
                                        {
                                            model.Id(c => c.AttendeeId);
 
                                        })
                                    )
                                    .Events(ev => ev.DataBinding("athena.studentItineraries.childDataBinding").DataBound("athena.studentItineraries.childDataBound"))
                                    .Sortable(x => x.Enabled(true))
                                    .Filterable(x => x.Enabled(true))
                                    .Pageable(pgb => pgb.Enabled(false))
                                    .Groupable(gr => gr.Enabled(false))
                                    .ToClientTemplate())
 
                          </text>
                                      );
                              }).Deferred()
                              .ToClientTemplate()
          )
  </script>

 

So, in the detailInit of the master grid, we take the data from sender event(master grid), and we fill child's grid dataSource.data with this list of data like below:

function detailInit(e) {
     var grid = $("#grid_" + e.data.StudentId).data("kendoGrid");     
     if (e.data.HasAttendee) {   
         // feed child grid with master data;
         grid.dataSource.data(e.data.ItinerariesFlattened.toJSON());
 
     }
 }

So the problem here is, that our datetime fields as described above, are not working. After a bit research, i found that e.data.ItinerariesFlattened or e.data.ItinerariesFlattened.toJSON(), if i log the data to console i have an object like this 

....//other string fields
BusRouteEndDate: "/Date(1561237200000)/",
BusRouteStartDate: "/Date(1538341200000)/"
BusStopArrivalTime: "/Date(1554784800000)/"
...//other string fields here

So i realized that date time fields, come in a format that was serialized during the first request. I have found a work around where I parse the datetime fields and then i feed the grid, which results in proper working date time fields. As shown below: 
function detailInit(e) {
     var grid = $("#grid_" + e.data.StudentId).data("kendoGrid");     
     if (e.data.HasAttendee) {   
         // feed child grid with master data;
         var parsedData = e.data.ItinerariesFlattened.map(
             attendee => {
                 var model = attendee || {};
                 model.AttendeeEventDate = w.kendo.parseDate(attendee.AttendeeEventDate);
                 model.BusStopArrivalTime = w.kendo.parseDate(attendee.BusStopArrivalTime);
                 return model;
             }
         );
         grid.dataSource.data(parsedData);
 
     }
 }

So, after this try, where I hard 'coded' parsed the model fields i want, I have a dataSource where the date fields as described above, are proper date javascript object's, and not string like '"/Date(1538341200000)/"'. After this workaround, my date fields filtering works without a problem. But as i mentioned, I think that this way is hard coded and not easy to maintain, because we have always to check client side too, e.g if a property name will change, or if we want to add more date properties to the model.

So, is there any way I have proper bind my child grid from master's grids rendered data and all type of fields(maybe there are problems with other types too like boolean or smthn else dont know) ?

Thnx in advance

6 Answers, 1 is accepted

Sort by
0
AGGELIKI
Top achievements
Rank 1
Iron
answered on 09 Apr 2019, 11:32 AM
I want to clarify(because there is no edit post action), that the filter operations on date time columns does not work.
0
Alex Hajigeorgieva
Telerik team
answered on 11 Apr 2019, 08:11 AM
Hello, Aggeliki,

Thank you for the shared snippet.

The issue that you are experiencing stems from the data source data() method. When a developer utilizes the method, the data is assigned as is and it is the developer's responsibility to ensure it is in the correct format (and you are correct, the same goes for boolean and numerics):

https://docs.telerik.com/kendo-ui/api/javascript/data/datasource/methods/data

To take advantage of the Kendo UI DataSource build-in parsing, you should define a data source with a schema, model, fields type and a transport read action, something like this:

var dataSource = new kendo.data.DataSource({
    transport: {
        read: function (e) {
            e.success(e.data.ItenerariesFlattened.ToJSON());
        }
    },              
    schema: {
        model: {
 
            fields: {
                BusStopArrivalTime: { type: "date" },
                AttendeeEventDate : { type: "date" }
            }
        }
    }
});

Then, take the detail grid instance and use the setDataSource() method:

https://docs.telerik.com/kendo-ui/api/javascript/ui/grid/methods/setdatasource

Let me know in case further questions arise.

Kind Regards,
Alex Hajigeorgieva
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
AGGELIKI
Top achievements
Rank 1
Iron
answered on 12 Apr 2019, 01:16 PM

Hello Alex,

Ok, I understand that using the data source data() method, need's the data to get parsed. So, as I already posted I wanted to avoid this custom parsing. I have tried during the r&d phase of that problem to create a client-side data source and set it to the child grid like you mention, but I had some problems(Actually I was never defining a transport read function, because there was never an actual read request, and as I understand there is not here too, we just 'emulate' that there is a read event).

So, after applying your solution with some modification's in order to work, I have some questions.

First of all, me detailInit  event function now is :

function detailInit(e) {
       var grid = $("#grid_" + e.data.StudentId).data("kendoGrid");
       if (!e.data.HasAttendee) return;
       // get grid server render options in order to dynamically map the schema model fields
       const fields = grid.getOptions().dataSource.schema.model.fields;
       var childDataSource =  new kendo.data.DataSource({
           transport: {
               read: function (e) {
                   e.success(e.data.toJSON());
               }
           },
           schema: {
               model: {
                   fields: fields
               }
           }
       });
       // set grid new data source
       grid.setDataSource(childDataSource);
       // read grid with provided data
       grid.dataSource.read(e.data.ItinerariesFlattened);
   }

 

As you see in code, after initializing a new kendo data source, i set it to child grid, and then i read that grid providing the data from the master row record. So, transport success is triggered and feed's the grid with the data provided serialized. Also , I have changed my server-side grid definition, in the datasource configuration options to be like this:

// other grid configuration
.DataSource(ds =>ds
                                          .Custom()
                                          .ServerPaging(false)
                                          .ServerSorting(false)
                                          .ServerFiltering(false)
                                          .Schema(sc => sc.Model(m => m.Id(model => model.AttendeeId)))
                                          // need to declare a dummy transport read request in order kendo
                                          // has complete proper data source configuration
                                          .Transport(tr => tr.Read(r => r.Url("")))
                                      )
// other grid configuration options

As you notice, i have changed my dataSource options, from ajax to Custom, defining again the schema model.Id, and a Transport read option with a 'fake' url. As i comment out on code, that was required in order to get a proper grid configuration with custom datasource.

My questions on my applied solutions are :

  • Can i get grid options like on js snippet, in order to get the schema.model.fields that are already populated, and not declaring them one-by-one; Is it safe or its gonna produce any side effects;
  • Are there gonna be any side effects from this applied solution? 

Thnx in advance,
Aggeliki

0
Alex Hajigeorgieva
Telerik team
answered on 15 Apr 2019, 01:12 PM
Hello, Aggeliki,

Thank you for your feedback.

I believe we can further improve the current detailInit logic as well as the detail template and avoid a couple of lines of code. 

<script id="itineraries" type="text/kendo-tmpl">
/* data source can be only ServerOperation(false) since it will be replaced upon initialization */
  .DataSource(d=>d.Ajax().ServerOperation(false))
</script>

The DetailInit handler can be simplified. Actually, there is no need for a transport read function, I am sorry if I have misled you, the data source will correctly process its internally assigned data, too:

<script>
 function onDetailInit(e) {
   var grid = $("#grid_" + e.data.StudentId).data("kendoGrid");
   const fields = grid.getOptions().dataSource.schema.model.fields;
   var iteneraries = e.data.ItenerariesFlattened.toJSON();
   var childDataSource = new kendo.data.DataSource({
       data: iteneraries,
       schema: {
         model: {
          fields: fields
         }
       }
   });
   // set grid new data source and read it
   grid.setDataSource(childDataSource);
   grid.dataSource.read();
 }
</script>

Give this a try and let me know in case you have further questions.

Regards,
Alex Hajigeorgieva
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
AGGELIKI
Top achievements
Rank 1
Iron
answered on 22 Apr 2019, 02:03 PM

Hello again Alex , 

Yes, I believe this is much simpler and cleaner approach from the previous one, which works too. No need for transport configuration and and no fake read actions. Solution works well, finally i got my non-string type filters to work on a client side initialized child grid from it's master data. 

Thank you again,

Aggeliki

 


0
Alex Hajigeorgieva
Telerik team
answered on 23 Apr 2019, 05:34 AM
Hi, Aggeliki,

I am pleased that the solution is helpful. Thank you for the positive feedback.

Kind Regards,
Alex Hajigeorgieva
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
Tags
Grid
Asked by
AGGELIKI
Top achievements
Rank 1
Iron
Answers by
AGGELIKI
Top achievements
Rank 1
Iron
Alex Hajigeorgieva
Telerik team
Share this question
or