Introduction
This article will guide you in rendering a Kendo dropdown withtin Kendo sub-grid using Editor Templates, ASP.NET MVC.
Background
Kendo sub-grid originally had a HTML dropdown instead of a Kendo dropdown.It also had a long running javascript code instead. I did browse around many articles to achieve it but everyone had something or the other missing from it / unclear, hence kind of learned it along the way!!.
The best part of this implementation being, when rendering for the first time, if the dropdown does not have any value saved already, it renders it blank, but once clicked on the gridcell, it turns into a dropdown for selection.
Furthermore, as soon as you navigate out from the grid cell, the editing mode ends and it returns back to the original last saved value from Database.
Using the code
In the following code, OrderGrid (grid) is the Main Kendo grid that houses another Kendo grid (grid2_#=OpportunityId) within.
In OrderGrid.cshtml
@(Html.Kendo().Grid<Models.OrderGridViewModel>()
.Name("grid")
.HtmlAttributes(new { style = "line-height:10px;" })
.Columns(columns =>
{
columns.Bound(m => m.VersionName).Title("Ver");
columns.Bound(m => m.OrderStatusText).Title("Status");
columns.Bound(m => m.StartDateTime).Title("Start Date");
columns.Bound(m => m.EndDateTime).Title("End Date");
})
.Selectable(s => s.Mode(GridSelectionMode.Single).Type(GridSelectionType.Cell))
.Events(events => events.DataBound("grid_dataBound"))
.Pageable()
.Sortable() .ClientDetailTemplateId("OrderDetailsAll")
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(5)
.Read(read => read.Action("Get", "Order"))
)
)
<script id="OrderDetailsAll" type="text/kendo-tmpl">
@(Html.Kendo().Grid<Models.OrderDetailAllViewModel>()
.Name("grid2_#=OpportunityId#")
.Events(e =>
{
e.DataBound("grid2_onDataBound");
e.Edit("onEdit");
})
.Editable(editable => editable.Mode(GridEditMode.InCell))
.Columns(columns =>
{
columns.Bound(m => m.IncludeInForecast).HtmlAttributes(new { Align = "center" }).Title("Include In Forecast?").ClientTemplate("\\# if (IncludeInForecast == true ) { \\#" + "Yes" +
"\\# }else { \\#" + "No" +
"\\# } \\#"); );
columns.Bound(m => m.SalesStrategyId).EditorTemplateName("SalesStrategies").Title("Sales Strategy").ClientTemplate("\\#=SalesStrategyName\\#"); })
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read.Action("GetDetailsAll", "Order", new { opportunityId = "#=OpportunityId#" }))
.Model(model =>
{
model.Field(x => x.IncludeInForecast).Editable(true);
model.Field(x => x.EditIncludeInForecast).Editable(false);
model.Field(x => x.SalesStrategyList).DefaultValue(ViewData["ssDefault"] as OMSWeb.Models.OrderDetailAllViewModel);
})
)
.ToClientTemplate())
</script>
Notice that for ClientTemplate we have ("\\#=SalesStrategyName\\#") - this is necessary to indicate that the property is for the sub-grid at one level down.
Note the EditorTemplateName (SalesStrategies) - Now add a new Folder - EditorTemplates at the same level as your page resides, within your Views folder and add a new cshtml file called "SalesStrategies.cshtml" within it.( e.g. my folder hierarchy was - \~OMSWeb\Views\Order\EditorTemplates\SalesStrategies.cshtml, OMSWeb is the Web Project)
@using Kendo.Mvc.UI
@using Utility;
@using System.Collections;
@model OMSWeb.Models.OrderDetailAllViewModel
@(Html.Kendo().DropDownListFor(m => m.SalesStrategyList)
.OptionLabel("-- Select Sales Strategy --")
.DataValueField("Value")
.DataTextField("Text")
.BindTo(ViewData["ssList"] as IEnumerable<List<SelectListItem>>)
.AutoBind(true)
.Name("SalesStrategyddList")
.Events(e =>
{
e.DataBound("onDataBound");
})
)
ViewModel (OrderDetailAllViewModel.cs)
(Please note that the model contains other relevant properties as well but have omitted it to keep it simpler and cleaner)
public class OrderDetailAllViewModel : BaseModel
{
public OrderDetailAllViewModel()
{
this.SalesStrategyList = new List<SelectListItem>();
}
public string SalesStrategyName { get; set; }
public int? SalesStrategyId { get; set; }
[UIHint("SalesStrategies")]
public List<SelectListItem> SalesStrategyList { get; set; }
}
Controller Code (in ~OMSWeb\Controllers\OrderController.cs), OrderBusinessService.cs
public ActionResult GetDetailsAll([DataSourceRequest] DataSourceRequest request,Guid opportunityId)
{
_iOrderBusinessService = new OrderBusinessService();
List<OrderDetailAllViewModel> mList = new List<OrderDetailAllViewModel>();
if(opportunityId != null)
{
mList = _iOrderBusinessService.GetOrderDetails(opportunityId).OrderBy(d => d.SubSystemId).ThenBy(d => d.StartDate).ToList(); }
DataSourceResult result = mList.ToDataSourceResult(request);
if (mList!= null && mList[0].SalesStrategyList.Any())
{
ViewData["ssList"] = mList[0].SalesStrategyList;
}
return Json(result);
}
public List<OrderDetailAllViewModel> GetOrderDetails(Guid opportunityId)
{
OrderDetailAllViewModel m;
List<OrderDetailAllViewModel> mList = new List<OrderDetailAllViewModel>();
ISalesStrategy ssMgr = new SalesStrategyManager();
List<OrderDetail> dList = _iOrderManager.GetOrderDetails(opportunityId, false);
foreach (OrderDetail d in dList)
{
m.SalesStrategyId = d.SalesStrategyId.HasValue ? d.SalesStrategyId.Value : 0; var ssItem = ssMgr.GetSalesStrategyById(m.SalesStrategyId.Value);
if(ssItem != null)
{
m.SalesStrategyName = string.IsNullOrEmpty(ssItem.SalesStrategyName) ? string.Empty : ssItem.SalesStrategyName;
}
else
{
m.SalesStrategyName = string.Empty; }
mList.Add(m);
}
return mList;
}
public JsonResult GetSalesStrategies([DataSourceRequest] DataSourceRequest request)
{
_iOrderBusinessService = new OrderBusinessService();
OrderDetailAllViewModel vm = new OrderDetailAllViewModel();
var salesStrategyList = _iOrderBusinessService.GetSalesStrategiesList();
ViewData["ssList"] = vm.SalesStrategyList;
return Json(salesStrategyList, JsonRequestBehavior.AllowGet);
}
Here the logic being, on the Databound event ofthe SalesStrategy dropdown, i'm making ajax call to get values from the database. Inorder to bind back to the saved values, existingValue
is used (the following code is written in OrderGrid.schtml)
<script type="text/javascript">
var dataItem,selectedOrderDetailId,existingValue,newSelectedValue,existingForecast,selectedProposalId,newIIFValue;
function onDataBound(e)
{
$.ajax({
url: "/Order/GetSalesStrategies",
type: "GET",
success: function (result)
{
if(result != null && result.length > 0)
{
$("#SalesStrategyddList").kendoDropDownList({
dataTextField: "Text",
dataValueField: "Value",
selectedIndex: <code>existingValue</code>, dataSource: result,
optionLabel: "-- Select Sales Strategy --"
});
$("#SalesStrategyddList").val(existingValue);
}
},
});
}
function onEdit(e)
{
selectedOrderDetailId = e.model.OrderDetailId;
<code>existingValue</code> = e.model.SalesStrategyId;
existingForecast = e.model.ForecastYesNo;
selectedGuid = e.model.OpportunityId;
selectedProposalId = e.model.ProposalId;
var isEditIIF = e.model.EditIncludeInForecast;
var orderDetailGrid = $('#grid2_' + selectedGuid).data("kendoGrid");
if (e.container[0].innerHTML.indexOf('Sales Strategy') >= 0) {
orderDetailGrid.table.on("change", "#SalesStrategyddList", onChange); }
else if (e.container[0].innerHTML.indexOf('Forecast') >= 0) {
if (isEditIIF)
{
$(this)[0].element[0].disabled = false;
orderDetailGrid.table.on("change", "#IncludeInForecast", onChange); }
else
{
$(this)[0].element[0].disabled = true;
}
}
}
function onChange(e)
{
var orderDetailGrid = $('#grid2_' + selectedGuid).data("kendoGrid");
if (e.target.name == "IncludeInForecast")
{
}
else if (e.target.name == "SalesStrategyddList")
{
newSelectedValue = e.target.value;
var dataToSend =
{
orderDetailId: selectedOrderDetailId,
selectedSalesStrategy: newSelectedValue
};
if (selectedOrderDetailId != 0 && newSelectedValue != 0)
{
$.ajax({
url: "/Order/UpdateSalesStrategy",
type: "POST",
data: dataToSend,
success: function (result)
{
if (result != null && result == "True")
{
orderDetailGrid.dataSource.page(1); }
},
});
}
}
}
</script>
Ajax call to Controller code to update SalesStrategy in Database (in OrderController.cs file)
[HttpPost]
public bool UpdateSalesStrategy(int orderDetailId, int selectedSalesStrategy)
{
_iOrderBusinessService = new OrderBusinessService(); return _iOrderBusinessService.UpdateSalesStrategy(orderDetailId, selectedSalesStrategy);
}
Points of Interest
In case where there is a Kendo Dropdown within a Kendo grid, onEdit event captures the saved value of the SalesStrategy, while onDatabound event of the Kendo dropdown (for SalesStrategy), make an ajax call to get the values from Database. I did browse through a lot of posts but did not find a better way to acccomplish this.
Please note - In one of the images (SalesStrategy_OnCellClick.jpg) the shows tooltip as well (on mouse hover in the SalesStrategy Grid Cell)- Please note i've NOT included that code in this post, will do so if anyone asks for / requires it.