5
votes

Update I have managed to get the form rendering (see image) with values and editor templates.

Sorry for blacked out labels. NDA stuff. Sorry for blacked out labels. NDA requirements...

This now works across multiple rows because I am using the uid of the row to give the detail template a unique name doing this:

@(Html.Kendo().TabStrip()
    .Name("Details_#=uid#")

I've updated the source below to the latest version and have included a list of issues that I'd still love help with.

Outstanding issues:

  • Serializing the detail template along with the grid when I save changes
  • Assign labels and validation rules using Data Annotations on the View Model (This doesn't seem to work since I can't seem to get Html helpers working. Advice would be great!

Orignal Post text (Source code has been updated)

I am trying to create a Batch Grid where each item contains a detail template.

Each detail template contains a tabstrip that I want to hold extra form data within.

Right now, I have the standard batch grid working, but I can't seem to get the information on screen for each item AND have it be editable. Also, the detail template breaks when more than one row is present. The template still renders, but buttons are non-functional because both templates have the same id understandably breaking the user's ability to interact, but I'm unsure of how to ensure unique identifiers for each row template (maybe use the parent uid somehow?)

I am also unsure of how to serialize this form data once I have completed this first step, but I can make a separate question for that should it prove necessary.

Grid

@(Html.Kendo().Grid(Model.ItemModelList)
.Name("ItemGrid")
.Columns(columns =>
{
    //Other columns omitted for brevity
    columns.Bound(i => i.Description).Width(100);
    columns.Command(command =>
    {
        command.Destroy();
    }).Width(60);
})
.ClientDetailTemplateId("ItemDetails")
.ToolBar(toolbar =>
{
    toolbar.Create();
    toolbar.Save();
})
.Editable(editable => editable.Mode(GridEditMode.InCell).CreateAt(GridInsertRowPosition.Bottom))
.Pageable()
.Sortable()
.Scrollable()
.Resizable(resize => resize.Columns(true))
.DataSource(dataSource => dataSource
    .Ajax()
    .Batch(true)
    .ServerOperation(false)
    .Events(events => 
    {
        events.Error("ItemGrid_ErrorHandler");
    })
    .Model(model =>
    {
        model.Id(i => i.ItemModelID);
        model.Field(i => i.DimensionUOM).DefaultValue("in");
        model.Field(i => i.WeightUOM).DefaultValue("lbs");
    })
    .Create(create => create.Action("CreateProducts", "ItemGrid"))
    .Read(read => read.Action("GetProducts", "ItemGrid"))
    .Update(update => update.Action("UpdateProducts", "ItemGrid"))
    .Destroy(destroy => destroy.Action("DeleteProducts", "ItemGrid"))
)

)

Detail Template

<script id="ItemDetails" type="text/kendo-tmpl">

@(Html.Kendo().TabStrip()
    .Name("Details_#=uid#")
    .SelectedIndex(0)
    .Items(items =>
    {
        items.Add().Text("test").Content(@<div>      
            <table id="testForm">
                <tr>
                    <td>TEST</td>
                </tr>
                <tr>
                </tr> 
            </table> 
        </div>);

        items.Add().Text("test2").Content(@<div>      
            <table id="test2Form">
                <tr>
                    <td><label>A</label></td>
                    <td><label>B</label></td>
                    <td><label>C</label></td>
                    <td><label>D</label></td>
                    <td><label>E</label></td>
                </tr>
                <tr>
                    <td>
                        <input class="k-textbox" value="#=objectA#">
                    </td>
                    <td>
                        @(Html.Kendo().DropDownList()
                            .Name("objectB")
                            .Value("#=objectB#")
                            .DataTextField("Text")
                            .DataValueField("Value")
                            .BindTo(new SelectList((System.Collections.IEnumerable)ViewBag.objectBListing, "Value", "Value"))
                            .ToClientTemplate()
                        )
                    </td>
                    <td>
                        @(Html.Kendo().DropDownList()
                            .Name("objectC")
                            .Value("#=objectC#")
                            .DataTextField("Text")
                            .DataValueField("Value")
                            .BindTo(new SelectList((System.Collections.IEnumerable)ViewBag.objectCListing, "Value", "Value"))
                            .ToClientTemplate()
                        )
                    </td>
                    <td><input class="k-textbox" value="#=objectD#"></td>
                    <td><input class="k-textbox" value="#=objectE#"></td>
                </tr>
            </table> 
        </div>);
    })
    .ToClientTemplate()
)  

1

1 Answers

0
votes
  1. You should create a model for your extra information and add it as a property of your ItemModelList like this:

    public class BaseMode
    {
        public string UID { get; set; } // Create your own UID, distinguished from Telerik UID by casing.
    }
    
    public class ExtraInfoModel : BaseModel
    {
        [DisplayName("Object A")]
        [Required] // For example
        public string ObjectA { get; set; }
    
        [DisplayName("Object B")]
        [UIHint("DropDownList")]
        public int? ObjectB { get; set; }
    
        [DisplayName("Object C")]
        public int? ObjectC { get; set; }
    
        public ExtraInfoModel(string uid)
        {
            this.UID = uid;
        }
    }
    
    public class ItemModelList : BaseModel
    {
        public ExtraInfoModel ExtraInfo { get; set; }
    
        public ItemModelList()
        {
            this.UID = Guid.NewGuid().ToString(); // Not sure about syntax, please review.
            this.ExtraInfo = new ExtraInfoModel(this.UID); // Guarantee ExtraInfo.UID is the same as parent UID when you get model from DB.
        }
    }
    
  2. Create a partial view for your detail extra info, using a kendo grid in your second tab:

    @model ExtraInfoModel
    
    @(Html.Kendo().TabStrip()
        .Name("Details_#=UID#")
        .SelectedIndex(0)
        .Items(items =>
        {
            items.Add().Text("test").Content(@<text>
                <div>      
                    <table id="testForm">
                        <tr>
                            <td>TEST</td>
                        </tr>
                        <tr></tr> 
                    </table> 
                </div>
            </text>);
    
            items.Add().Text("test2").Content(@<text>
                @(Html.Kendo().Grid<ExtraInfoModel>()
                    .Name("gvDetail_#=UID#")
                    .Columns(c =>
                    {
                        c.Bound(m => m.ObjectA).ClientTemplate(Html.Kendo().TextBox()
                            .Name("ObjectA")
                            .HtmlAttributes(new { id = "ObjectA_#=UID#" })
                            .Value(Model.AgencyCode)
                            .ToClientTemplate()
                            .ToHtmlString());
                        c.Bound(m => m.ObjectB).ClientTemplate(Html.Kendo().DropDownList()
                            .Name("ObjectB")
                            .HtmlAttributes(new { id = "ObjectB_#=UID#" })
                            .Value((Model != null && Model.ObjectB.HasValue) ? Model.ObjectB.ToString() : string.Empty)
                            .OptionLabel("Select B...")
                            .BindTo(ViewBag.Data)
                            .ToClientTemplate()
                            .ToHtmlString());
    
                         // Config ObjectC same as ObjectB.
                    })
                    .BindTo(new ExtraInfoModel[] { Model }) // Your detail grid has only one row.
                )
            </text>);
        })
    )
    
  3. In your main view page, using server detail template instead of client detail template. I suggest to use server template because I'm using server binding in step 2. Of course, you can change it to ajax binding or local binding (by defining event OnDetailExpand)

    @Html.Kendo().Grid(Model.ItemModelList)
        ...
        .DetailTemplate(@<text>
            @Html.Partial("YourPartialName", item.ExtraInfo) // item stands for ItemModelList, which is the binding data item.
        </text>)
    
  4. And the last one, for your first issue, serializing extra info data on save, we should handle change event of each extra info property to set value and dirty flag to master data item. Remember that kendo grid in batch editing only submits dirty data items. Back to step 2:

                        c.Bound(m => m.ObjectB).ClientTemplate(Html.Kendo().DropDownList()
                            .Name("ObjectB")
                            .HtmlAttributes(new { id = "ObjectB_#=UID#" })
                            .Value((Model != null && Model.ObjectB.HasValue) ? Model.ObjectB.ToString() : string.Empty)
                            .OptionLabel("Select B...")
                            .BindTo(ViewBag.Data) 
                            .Events(e => e.Change("onChangeObjectB")) // Added this line
                            .ToClientTemplate()
                            .ToHtmlString());
    
    <script type="text/javascript">
        function onChangeObjectB(e) {
            var selectedValue = this.value();
    
            var sender = e.sender;
            if (sender != undefined && sender.length > 0) {
                var detailRow = sender.closest(".k-detail-row");
                var masterRow = detailRow.prev();
                var mainGrid = $("#ItemGrid").data("kendoGrid");
                if (mainGrid != null) {
                    var masterRowIndex = masterRow.parent().find(".k-master-row").index(masterRow);
    
                    var dataItem = mainGrid.dataSource._data[masterRowIndex];
                    dataItem.ObjectB = selectedValue;
                    dataItem._dirty = true;
                }
            }
        }
    </script>
    

    And the save action will work as normal.