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

Add to array property of grid dataSource

5 Answers 762 Views
Grid
This is a migrated thread and some comments may be shown as answers.
Jark Monster
Top achievements
Rank 1
Jark Monster asked on 10 Apr 2013, 05:57 PM
Build: 2012.3.1413

When my batch grid is saved, I am trying to add items to a list property that is present on the grid's dataSource.  I am getting an accurate quantity of items inserted into this list property, but I am not getting any of the data that should be present with it.  The strange thing is that the values DO appear to be present when I use firebug to look at what is being sent to the server, but the data disappears on the object on the controller side (except for the appropriate number of entries)

ItemModel
public class ItemModel
 {
     public ItemModel()
     {
         ServiceBookList = new List<ServiceModel>();
     }
 
     public List<ServiceModel> ServiceBookList { get; set; }
}
ServiceModel
public class ServiceModel
{
    public ServiceModel() { }
 
    public int RefServiceID { get; set; }
    public string Value { get; set; }
    public string Code { get; set; }
 
    public long EnterpriseProductServiceBookID { get; set; }
    public long AppUserProductServiceBookID { get; set; }
    public bool IsDeleted { get; set; }
}
Script
function SaveProductBook() {
       //This is called when the "Save" button on the parent grid is clicked
     var productBook = $("#AdminProductBookGrid").data("kendoGrid");
     SaveProductDetailViews(productBook); //Save items located in detail template
     productBook.dataSource.sync();
 }
   
function SaveProductDetailViews(itemGrid) {
     //For each row in grid, grab the latest data from the fields within the detail templates
     //and update the row's dataSource prior to sync
     $.each($("#AdminProductBookGrid tbody tr"), function (index, row) {
         var dataItem = itemGrid.dataItem(row);
         var detailRow = $(row).next();
 
         if (dataItem != null && detailRow.has('.k-detail-cell').length > 0) {
             SaveServiceDetailViews(dataItem, detailRow, type);
         }
     });
 }
 
function SaveServiceDetailViews(dataItem, detailRow, type) {
     var serviceGrid = $(detailRow).find("div[id=ServiceGrid_" + dataItem.uid + "]").data("kendoGrid");
    //data is the item's added in the detail template's grid for that parent row
    var data = serviceGrid.dataSource._data;
     for(var i = 0; i < data.length; i++) {      
           //Add to the parent row's dataSource list
           dataItem.ServiceBookList.push(data[i]);
     }
 }
DataSource state prior to grid sync() - From Firebug
See image below, but the 1 attribute that should be populated is RefServiceID, and it is. 

Result on POST -from Firebug

See image below, but same as DataSource (ie. RefServiceID should be the only attribute populate in the sub list items and it is.
***Issue is possible here because the full path of the data in the list is written strangely, but I'm not sure if it is correct.
Full path to RefServiceID (after push function) is:
models[0].ServiceBookList[0][RefServiceID]
I figure it should look list other arrays and be like this (but not sure):
models[0].ServiceBookList[0].RefServiceID

Controller values
See Image Below


In case my description was insufficient, I have also included a screenshot of how everything is laid out in the application.

Please let me know if further code is needed.  Also, I began this when there was no out of the box support for saving editable fields in a grid's detail template.  Please let me know if this has changed and how I might go about doing it in a supported way.

Thanks!

5 Answers, 1 is accepted

Sort by
0
Daniel
Telerik team
answered on 15 Apr 2013, 08:17 AM
Hello,

The dataSource will not automatically serialize the arrays in a format that can be processed by the model binder. You should either add the fields to the dataItem in the needed format or use the request data function to serialize the array e.g.

.Update(update=> update.Action("action", "controller").Data("serialize"))
function serialize(options) {       
    var data = options.models,
        bookList;
    for (var i = 0; i < data.length; i++) {
        bookList = data[i].ServiceBookList;
        for (var j = 0; j < bookList.length; j++) {
            for (var field in bookList[i]) {
                data[i]["ServiceBookList[" + j + "]." + field] = bookList[j][field];
            }               
        }
        delete data[i].ServiceBookList;
    }       
}

I am not sure if I understand correctly this question:
there was no out of the box support for saving editable fields in a grid's detail template The detail Grid can be configured to be editable and save the changes to the server as in any other case. If you need to save the detail Grid data with the master Grid request then updating the parent model will be needed.
Regards,
Daniel
the Telerik team
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
0
Jark Monster
Top achievements
Rank 1
answered on 16 Apr 2013, 07:06 PM
I have verified that this does indeed work.  Thank you!

To clarify my earlier remark, I was more specifically told that an editable form was not natively supported in that I couldn't rely on the "Save Changes" button to save the form as well.

I have encountered another issue however that is loosely related.

When the Save button is clicked, I loop through all of the grid rows and see if the detail template exists.  If it does, I loop through the data in the detail template grid.  When editing one row, this works perfectly.  However, my ability to detect previously open detail templates (ie. The html has been added to the view as a <tr>) is compromised when I open multiple detail templates.  I have narrowed it down to the read function on the detail template grid, and I believe when it posts the json result back, I am 'breaking' jquery's ability to reference other row detail templates.

Here is the parent grid detail template code:
<script id="EnterpriseProductDetailsAdmin" type="text/kendo-tmpl">
    @(Html.Kendo().TabStrip()
        .Name("Details_#=uid#") //Name is important to 'detect' detail template existence
        .SelectedIndex(0)
        .Items(items =>
        {
           items.Add().Text("Services")
               .Content
                    (@<div id="ItemGridDetail_ServiceContainer">
                     @Html.Partial("Admin/AdminServices")</div>);
        })
        .ToClientTemplate()
    )
</script>

Here is my code from the grid in the detail template:
@(Html.Kendo().Grid<BlueGrace.BlueShip.MVC.Models.ServiceModel>()
    .Name("ServiceGrid_#=uid#")
    .Columns(columns =>
    {
        columns.ForeignKey(item => item.RefServiceID,
                (System.Collections.IEnumerable)ViewBag.RefServiceListing, "RefServiceID", "Value");
        columns.Bound(item => item.Code).Hidden(true);
        columns.Command(command =>
        {
            command.Destroy();
        });
    })
    .ToolBar(toolbar =>
    {
        toolbar.Create();
    })
    .Editable(editable =>
    {
        editable.Mode(GridEditMode.InCell).CreateAt(GridInsertRowPosition.Top);
        editable.DisplayDeleteConfirmation(false);
    })
    .Sortable()
    .Pageable()
    .Scrollable()
    .DataSource(dataSource => dataSource
        .Ajax()
        .Batch(true)
        .ServerOperation(false)
        .Model(model =>
        {
            model.Id(i => i.ID);
        })
        .Read(read => read.Action("GetServices", "Home", new { ID = "#=ID#" }))
    )
    .Pageable()
    .ToClientTemplate()
)
Here is the code I use to 'detect' the detail template:
function SaveProductBook() {
    var productBook = $("#ProductGrid").data("kendoGrid");
    SaveProductDetailViews(productBook);
    //productBook.dataSource.sync();  //Will call this to save
}
 
function SaveProductDetailViews(productBook) {
    for(var i = 0; i < productBook.dataSource._data.length; i++) {
        var dataItem = productBook.dataSource._data[i];
        var detailTemplate = $("#Details_" + dataItem.uid);
                   //NOTE: This is the DIV surround the detail template's tabstrip
        console.log("dataItem");
        console.log(dataItem);
        console.log(dataItem.uid);
        console.log("template");
        console.log(detailTemplate);
        if(detailTemplate.length > 0) {
            //if it exists, then proceed with serializing service grid to parent dataItem
            SaveServiceDetailViews(dataItem, detailTemplate);
        }
    }
}
When I have a parent grid with 2 rows in it, and after it loads, I open both detail templates and click "Save Changes" on the parent grid, I get this result from my console.log() functions and only the first row's detail template data is serialized:  SEE ATTACHED IMAGE

IF I comment out the read method on the detail template grid, this issue goes away, but obviously, I don't 'get' my data, so there's definitely an issue still.  Thanks for the help so far, and I hope this is a small issue.
0
Daniel
Telerik team
answered on 18 Apr 2013, 02:20 PM
Hello,

Sorry, there is a mistake in the code from my previous reply. Please try with the following code instead:

function serialize(options) {      
    var data = options.models,
        bookList;
    for (var i = 0; i < data.length; i++) {
        bookList = data[i].ServiceBookList;
        for (var j = 0; j < bookList.length; j++) {
            for (var field in bookList[j]) {
                data[i]["ServiceBookList[" + j + "]." + field] = bookList[j][field];
            }              
        }
        delete data[i].ServiceBookList;
    }      
}
Regards,
Daniel
the Telerik team
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
0
Jark Monster
Top achievements
Rank 1
answered on 18 Apr 2013, 05:45 PM
Thank you for the correction.

I have gotten this working except for one issue:  When I edit the child grid in 2 or more rows, I lose the 'connection' to all but the last detail template opened.

I believe that the read method's POST might be the culprit.  I feel this is so for a couple of reasons:
  1. I can reference the elements within the detail template of the last row that was expanded.***
  2. When I comment out the read method, though I get no data in my subgrid, I can reference the elements within each opened detail template
***NOTE: I found this by trying to reference the elements of the subgrid using jQuery while trying to serialize the detail template's onto the parent grid's dataItem during my overridden save function.

Here is the code where I'm unable to detect multiple open detail templates:
for(var i = 0; i < productBook.dataSource._data.length; i++) {
    var dataItem = productBook.dataSource._data[i];
    var detailTemplate = $("#Details_" + dataItem.uid);
         //This comes back empty on all rows except the LAST
         //row that had its detail template opened
 
    if(detailTemplate.length > 0) {
        //This code is only reached by the 'last' detail template opened
        SaveServiceDetailViews(dataItem, detailTemplate);
    }
}
Is there a better way to detect if a given row has its detail template expanded?
0
Daniel
Telerik team
answered on 22 Apr 2013, 02:52 PM
Hi,

ServiceBookList will be observable array so using the push method will trigger the change event and the master grid will be redrawn. You could initialize a JavaScript array in order to avoid triggering the event:

function SaveServiceDetailViews(dataItem, detailRow, type) {
    var serviceGrid = $(detailRow).find("div[id=ServiceGrid_" + dataItem.uid + "]").data("kendoGrid");
    var data = serviceGrid.dataSource._data;
    data.ServiceBookList = [];
    for(var i = 0; i < data.length; i++) {     
          //Add to the parent row's dataSource list
          dataItem.ServiceBookList.push(data[i]);
    }
 }
Regards,
Daniel
the Telerik team
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
Tags
Grid
Asked by
Jark Monster
Top achievements
Rank 1
Answers by
Daniel
Telerik team
Jark Monster
Top achievements
Rank 1
Share this question
or