Hi folks, I have been trying to figure out why my KendoUI grid will not call the controller when attempting to batch save. As part of my troubleshooting, I created a number of ActionResults with different parameters and breakpointing in each one, hoping that I would hit one of these breakpoints. No matter what method that I use in my view to call the controller to save, nothing happens.
I have two cascading Kendo DropDownLists (which work fine), and upon the user selecting an item in the second DropDownList, I populate a KendoUI grid via JSON. The user can then fill in the New Rate field to whatever they want, and finally click a save button to batch update. The user can increase (and later decrease, when I implement it) a percentage across the board via a TextBox and button.
I come from the ASPX and WinForms world, so jQuery and javascript in general are not my strong suits.
Here is the pertinent source code. I added some javascript alerts, to help troubleshoot. Note that the Kendo and jQuery scripts are referenced in a layout page as follows:
<script src="@Url.Content("~/Scripts/jquery-1.8.3.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/kendo.web.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/kendo.aspnetmvc.min.js")" type="text/javascript"></script>
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MyReloWorks.Service.Models.ServiceManager;
using System.ComponentModel.DataAnnotations;
namespace MRW_MVC.Areas.SM.Models
{
public class SFRRatesVM
{
[Key]
public int SFRID { get; set; }
public int ModeID { get; set; }
public int DistanceID { get; set; }
public int MeasurementID { get; set; }
[Display(Name = "Distance (Miles)")]
public string Distance { get; set; }
public decimal CurrentRate { get; set; }
[Display(Name = "Effective Date")]
public DateTime EffectiveDT { get; set; }
public decimal NewRate { get; set; }
}
}
Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MRW_MVC.Controllers;
using MyReloWorks.Service.Services;
using MRW_MVC.Areas.SM.Models;
using Kendo.Mvc.Extensions;
using Kendo.Mvc.UI;
using MyReloWorks.Service.Models.ServiceProvider;
using MyReloWorks.Service.Models.ServiceManager;
namespace MRW_MVC.Areas.SM.Controllers
{
[Authorize(Roles = "SM-Manager-HHG")]
public class SFREntryController : AuthenticationController
{
private ISMService _smSerivce;
private IShipmentService _shipmentService;
public SFREntryController(ISMService smService, IShipmentService shipmentService)
{
_smSerivce = smService;
_shipmentService = shipmentService;
}
public ActionResult Index()
{
return View();
}
public JsonResult GetCascadeModes()
{
var modeList = _shipmentService.GetMode("HHG");
return Json(modeList.Select(m => new { ModeID = m.ModeID, ModeName = m.ModeDesc }), JsonRequestBehavior.AllowGet);
}
public JsonResult GetCascadeMeasurements(string modes)
{
var modeID = Convert.ToInt32(modes);
var measurementList = _smSerivce.GetMeasurementsByModeID(modeID);
return Json(measurementList.Select(m => new { MeasurementID = m.MeasurementID, MeasurementName = m.MeasurementValue }), JsonRequestBehavior.AllowGet);
}
public JsonResult GetRates([DataSourceRequest]DataSourceRequest request, string mode, string measurement)
{
IList<SFRRatesVM> vm = new List<SFRRatesVM>();
if (mode != "" && measurement != "")
{
var modeID = Convert.ToInt32(mode);
var measurementID = Convert.ToInt32(measurement);
var rates = _smSerivce.GetSFRRatesByModeIDAndMeasurementID(modeID, measurementID);
//place data into the ViewModel's format
foreach (SFRDTO rate in rates)
{
var temp = new SFRRatesVM();
{
temp.SFRID = rate.SFRID;
temp.DistanceID = rate.DistanceID;
temp.ModeID = rate.ModeID;
temp.MeasurementID = rate.MeasurementID;
temp.Distance = rate.DistanceInfo.DistanceDesc;
temp.CurrentRate = rate.Rate;
temp.EffectiveDT = rate.EffectiveDT;
vm.Add(temp);
}
}
}
DataSourceResult result = vm.ToDataSourceResult(request);
return Json(result, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public ActionResult UpdateRates()
{
throw new NotImplementedException();
}
public ActionResult UpdateRates(Models.SFRRatesVM request)
{
throw new NotImplementedException();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateRates([DataSourceRequest] DataSourceRequest request, IEnumerable<SFRRatesVM> vm)
{
//TODO: handle multiple modes
if (vm != null && ModelState.IsValid)
{
//get the old rates
var oldRates = _smSerivce.GetLiftVanRates();
// for each DistanceID, check the old rate to the "new" rate, and update accordingly
foreach (SFRRatesVM rate in vm)
{
var temp = new SFRDTO();
{
temp = oldRates.Where(r => r.SFRID == rate.SFRID).FirstOrDefault();
if (temp.Rate != rate.NewRate)
{
_smSerivce.UpdateSFRRate(temp, rate.NewRate);
}
}
}
}
return Json(ModelState.ToDataSourceResult());
}
[HttpPost]
public ActionResult UpdateRates(IEnumerable<SFRRatesVM> vm)
{
//TODO: handle multiple modes
if (vm != null && ModelState.IsValid)
{
//get the old rates
var oldRates = _smSerivce.GetLiftVanRates();
// for each DistanceID, check the old rate to the "new" rate, and update accordingly
foreach (SFRRatesVM rate in vm)
{
var temp = new SFRDTO();
{
temp = oldRates.Where(r => r.SFRID == rate.SFRID).FirstOrDefault();
if (temp.Rate != rate.NewRate)
{
_smSerivce.UpdateSFRRate(temp, rate.NewRate);
}
}
}
}
return Json(ModelState.ToDataSourceResult());
}
public ActionResult GenerateReport()
{
return View();
}
}
}
View:
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Service Management - Manage SFR Rates</h2>
<label for="modes">Modes:</label>
@(Html.Kendo().DropDownList()
.Name("modes")
.OptionLabel("Select a mode..")
.DataTextField("ModeName")
.DataValueField("ModeID")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetCascadeModes", "SFREntry");
});
})
)
<label for="measurements">Measurements:</label>
@(Html.Kendo().DropDownList()
.Name("measurements")
.OptionLabel("Select a measurement...")
.DataTextField("MeasurementName")
.DataValueField("MeasurementID")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetCascadeMeasurements", "SFREntry")
.Data("filterMeasurements");
})
.ServerFiltering(true);
})
.Events(e => e.Change("measurementChanged"))
.Enable(false)
.AutoBind(false)
.CascadeFrom("modes")
)
<br />
<br />
@(Html.Kendo().Grid<MRW_MVC.Areas.SM.Models.SFRRatesVM>()
.Name("myGrid")
.HtmlAttributes(new { style = "width:500px" })
.Columns(columns => {
columns.Bound(p => p.SFRID).Hidden();
columns.Bound(p => p.Distance).Width(25);
columns.Bound(p => p.CurrentRate)
.Format("{0:C}")
.Width(25)
.HtmlAttributes(new { style = "text-align: right" });
columns.Bound(p => p.EffectiveDT)
.Format("{0:d}")
.Width(25);
columns.Bound(p => p.NewRate)
.Format("{0:C}")
.Width(25)
.HtmlAttributes(new { style = "text-align: right" });
})
.Editable(editable => editable.Mode(GridEditMode.InCell))
.Resizable(resize => resize.Columns(true))
.AutoBind(false)
.Events(events =>
{
events.SaveChanges("onSaveChanges");
})
.ToolBar(toolbar=> toolbar.Save())
.DataSource(dataSource => dataSource
.Ajax()
.Events(events => events.Error("error_handler"))
.Batch(true)
.Model(model =>
{
model.Id(p => p.SFRID);
model.Field(p => p.Distance).Editable(false);
model.Field(p => p.CurrentRate).Editable(false);
model.Field(p => p.EffectiveDT).Editable(false);
model.Field(p => p.NewRate).Editable(true);
})
.Update(update => update.Action("UpdateRates", "SFREntry").Data("getGridData"))
.Create(create => create.Action("UpdateRates", "SFREntry").Data("getGridData"))
.Read(read => read.Action("GetRates", "SFREntry").Data("getModeAndMeasurement"))
)
)
<br />
<button id="btnGo" type="button">Go</button>
<br />
@Html.Label("Adjust Rates (By %)")
<br />
<input type="text" id="txtRate" size="100" />
@Html.RadioButton("Adjust", true)Down
@Html.RadioButton("Adjust", false)Up
<br />
<button id="btnAdjust" type="button">Adjust</button>
<script type="text/javascript">
function filterMeasurements() {
return {
modes: $("#modes").val()
};
}
function getModeAndMeasurement() {
var measurement = $("#measurements").val();
var mode = $("#modes").val();
return {
mode: mode,
measurement: measurement
};
}
function measurementChanged() {
var grid = $("#myGrid").data("kendoGrid");
grid.dataSource.read();
}
function error_handler(e) {
if (e.errors) {
var message = "Errors:\n";
$.each(e.errors, function (key, value) {
if ('errors' in value) {
$.each(value.errors, function () {
message += this + "\n";
});
}
});
alert(message);
}
}
$(function () {
$("#btnGo").click(function () {
alert('starting POST');
$.ajax({
url: '@Url.Action("UpdateRates ", "SFREntry", Request.RequestContext.RouteData.Values)',
type: 'POST',
cache: false,
traditional: true,
dataType: "json",
contentType: "application/json",
data: kendo.stringify({
vm: $("#myGrid").data("kendoGrid").dataSource.view().toJSON()
}),
success: function () { window.alert('saved'); },
error: function() { window.alert('failed'); }
});
alert('ending POST');
});
});
function getGridData() {
var grid = $("#myGrid").data("kendoGrid");
var vm = grid.dataSource.view();
alert(vm);
return vm;
}
//test to see if the save event fires
function onSaveChanges(e) {
var grid = $("#myGrid").data("kendoGrid");
var data = grid.dataSource.view();
var count = data.length;
alert('In onSaveChanges');
alert(count);
}
//fill in the New Rates column with the old rate, multiplied by amount in txtRate
$(function () {
$("#btnAdjust").click(function () {
var adjustment = $("#txtRate").val() / 100;
var grid = $("#myGrid").data("kendoGrid");
var data = grid.dataSource.view();
var count = data.length;
for (var i = 0; i < count; i++) {
var currentRow = data[i];
var currentRate = currentRow.CurrentRate;
currentRow.NewRate = (currentRate + (currentRate * adjustment));
};
grid.refresh();
});
});
</script>
Thanks!
--Dan
I have two cascading Kendo DropDownLists (which work fine), and upon the user selecting an item in the second DropDownList, I populate a KendoUI grid via JSON. The user can then fill in the New Rate field to whatever they want, and finally click a save button to batch update. The user can increase (and later decrease, when I implement it) a percentage across the board via a TextBox and button.
I come from the ASPX and WinForms world, so jQuery and javascript in general are not my strong suits.
Here is the pertinent source code. I added some javascript alerts, to help troubleshoot. Note that the Kendo and jQuery scripts are referenced in a layout page as follows:
<script src="@Url.Content("~/Scripts/jquery-1.8.3.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/kendo.web.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/kendo.aspnetmvc.min.js")" type="text/javascript"></script>
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MyReloWorks.Service.Models.ServiceManager;
using System.ComponentModel.DataAnnotations;
namespace MRW_MVC.Areas.SM.Models
{
public class SFRRatesVM
{
[Key]
public int SFRID { get; set; }
public int ModeID { get; set; }
public int DistanceID { get; set; }
public int MeasurementID { get; set; }
[Display(Name = "Distance (Miles)")]
public string Distance { get; set; }
public decimal CurrentRate { get; set; }
[Display(Name = "Effective Date")]
public DateTime EffectiveDT { get; set; }
public decimal NewRate { get; set; }
}
}
Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MRW_MVC.Controllers;
using MyReloWorks.Service.Services;
using MRW_MVC.Areas.SM.Models;
using Kendo.Mvc.Extensions;
using Kendo.Mvc.UI;
using MyReloWorks.Service.Models.ServiceProvider;
using MyReloWorks.Service.Models.ServiceManager;
namespace MRW_MVC.Areas.SM.Controllers
{
[Authorize(Roles = "SM-Manager-HHG")]
public class SFREntryController : AuthenticationController
{
private ISMService _smSerivce;
private IShipmentService _shipmentService;
public SFREntryController(ISMService smService, IShipmentService shipmentService)
{
_smSerivce = smService;
_shipmentService = shipmentService;
}
public ActionResult Index()
{
return View();
}
public JsonResult GetCascadeModes()
{
var modeList = _shipmentService.GetMode("HHG");
return Json(modeList.Select(m => new { ModeID = m.ModeID, ModeName = m.ModeDesc }), JsonRequestBehavior.AllowGet);
}
public JsonResult GetCascadeMeasurements(string modes)
{
var modeID = Convert.ToInt32(modes);
var measurementList = _smSerivce.GetMeasurementsByModeID(modeID);
return Json(measurementList.Select(m => new { MeasurementID = m.MeasurementID, MeasurementName = m.MeasurementValue }), JsonRequestBehavior.AllowGet);
}
public JsonResult GetRates([DataSourceRequest]DataSourceRequest request, string mode, string measurement)
{
IList<SFRRatesVM> vm = new List<SFRRatesVM>();
if (mode != "" && measurement != "")
{
var modeID = Convert.ToInt32(mode);
var measurementID = Convert.ToInt32(measurement);
var rates = _smSerivce.GetSFRRatesByModeIDAndMeasurementID(modeID, measurementID);
//place data into the ViewModel's format
foreach (SFRDTO rate in rates)
{
var temp = new SFRRatesVM();
{
temp.SFRID = rate.SFRID;
temp.DistanceID = rate.DistanceID;
temp.ModeID = rate.ModeID;
temp.MeasurementID = rate.MeasurementID;
temp.Distance = rate.DistanceInfo.DistanceDesc;
temp.CurrentRate = rate.Rate;
temp.EffectiveDT = rate.EffectiveDT;
vm.Add(temp);
}
}
}
DataSourceResult result = vm.ToDataSourceResult(request);
return Json(result, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public ActionResult UpdateRates()
{
throw new NotImplementedException();
}
public ActionResult UpdateRates(Models.SFRRatesVM request)
{
throw new NotImplementedException();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateRates([DataSourceRequest] DataSourceRequest request, IEnumerable<SFRRatesVM> vm)
{
//TODO: handle multiple modes
if (vm != null && ModelState.IsValid)
{
//get the old rates
var oldRates = _smSerivce.GetLiftVanRates();
// for each DistanceID, check the old rate to the "new" rate, and update accordingly
foreach (SFRRatesVM rate in vm)
{
var temp = new SFRDTO();
{
temp = oldRates.Where(r => r.SFRID == rate.SFRID).FirstOrDefault();
if (temp.Rate != rate.NewRate)
{
_smSerivce.UpdateSFRRate(temp, rate.NewRate);
}
}
}
}
return Json(ModelState.ToDataSourceResult());
}
[HttpPost]
public ActionResult UpdateRates(IEnumerable<SFRRatesVM> vm)
{
//TODO: handle multiple modes
if (vm != null && ModelState.IsValid)
{
//get the old rates
var oldRates = _smSerivce.GetLiftVanRates();
// for each DistanceID, check the old rate to the "new" rate, and update accordingly
foreach (SFRRatesVM rate in vm)
{
var temp = new SFRDTO();
{
temp = oldRates.Where(r => r.SFRID == rate.SFRID).FirstOrDefault();
if (temp.Rate != rate.NewRate)
{
_smSerivce.UpdateSFRRate(temp, rate.NewRate);
}
}
}
}
return Json(ModelState.ToDataSourceResult());
}
public ActionResult GenerateReport()
{
return View();
}
}
}
View:
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Service Management - Manage SFR Rates</h2>
<label for="modes">Modes:</label>
@(Html.Kendo().DropDownList()
.Name("modes")
.OptionLabel("Select a mode..")
.DataTextField("ModeName")
.DataValueField("ModeID")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetCascadeModes", "SFREntry");
});
})
)
<label for="measurements">Measurements:</label>
@(Html.Kendo().DropDownList()
.Name("measurements")
.OptionLabel("Select a measurement...")
.DataTextField("MeasurementName")
.DataValueField("MeasurementID")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetCascadeMeasurements", "SFREntry")
.Data("filterMeasurements");
})
.ServerFiltering(true);
})
.Events(e => e.Change("measurementChanged"))
.Enable(false)
.AutoBind(false)
.CascadeFrom("modes")
)
<br />
<br />
@(Html.Kendo().Grid<MRW_MVC.Areas.SM.Models.SFRRatesVM>()
.Name("myGrid")
.HtmlAttributes(new { style = "width:500px" })
.Columns(columns => {
columns.Bound(p => p.SFRID).Hidden();
columns.Bound(p => p.Distance).Width(25);
columns.Bound(p => p.CurrentRate)
.Format("{0:C}")
.Width(25)
.HtmlAttributes(new { style = "text-align: right" });
columns.Bound(p => p.EffectiveDT)
.Format("{0:d}")
.Width(25);
columns.Bound(p => p.NewRate)
.Format("{0:C}")
.Width(25)
.HtmlAttributes(new { style = "text-align: right" });
})
.Editable(editable => editable.Mode(GridEditMode.InCell))
.Resizable(resize => resize.Columns(true))
.AutoBind(false)
.Events(events =>
{
events.SaveChanges("onSaveChanges");
})
.ToolBar(toolbar=> toolbar.Save())
.DataSource(dataSource => dataSource
.Ajax()
.Events(events => events.Error("error_handler"))
.Batch(true)
.Model(model =>
{
model.Id(p => p.SFRID);
model.Field(p => p.Distance).Editable(false);
model.Field(p => p.CurrentRate).Editable(false);
model.Field(p => p.EffectiveDT).Editable(false);
model.Field(p => p.NewRate).Editable(true);
})
.Update(update => update.Action("UpdateRates", "SFREntry").Data("getGridData"))
.Create(create => create.Action("UpdateRates", "SFREntry").Data("getGridData"))
.Read(read => read.Action("GetRates", "SFREntry").Data("getModeAndMeasurement"))
)
)
<br />
<button id="btnGo" type="button">Go</button>
<br />
@Html.Label("Adjust Rates (By %)")
<br />
<input type="text" id="txtRate" size="100" />
@Html.RadioButton("Adjust", true)Down
@Html.RadioButton("Adjust", false)Up
<br />
<button id="btnAdjust" type="button">Adjust</button>
<script type="text/javascript">
function filterMeasurements() {
return {
modes: $("#modes").val()
};
}
function getModeAndMeasurement() {
var measurement = $("#measurements").val();
var mode = $("#modes").val();
return {
mode: mode,
measurement: measurement
};
}
function measurementChanged() {
var grid = $("#myGrid").data("kendoGrid");
grid.dataSource.read();
}
function error_handler(e) {
if (e.errors) {
var message = "Errors:\n";
$.each(e.errors, function (key, value) {
if ('errors' in value) {
$.each(value.errors, function () {
message += this + "\n";
});
}
});
alert(message);
}
}
$(function () {
$("#btnGo").click(function () {
alert('starting POST');
$.ajax({
url: '@Url.Action("UpdateRates ", "SFREntry", Request.RequestContext.RouteData.Values)',
type: 'POST',
cache: false,
traditional: true,
dataType: "json",
contentType: "application/json",
data: kendo.stringify({
vm: $("#myGrid").data("kendoGrid").dataSource.view().toJSON()
}),
success: function () { window.alert('saved'); },
error: function() { window.alert('failed'); }
});
alert('ending POST');
});
});
function getGridData() {
var grid = $("#myGrid").data("kendoGrid");
var vm = grid.dataSource.view();
alert(vm);
return vm;
}
//test to see if the save event fires
function onSaveChanges(e) {
var grid = $("#myGrid").data("kendoGrid");
var data = grid.dataSource.view();
var count = data.length;
alert('In onSaveChanges');
alert(count);
}
//fill in the New Rates column with the old rate, multiplied by amount in txtRate
$(function () {
$("#btnAdjust").click(function () {
var adjustment = $("#txtRate").val() / 100;
var grid = $("#myGrid").data("kendoGrid");
var data = grid.dataSource.view();
var count = data.length;
for (var i = 0; i < count; i++) {
var currentRow = data[i];
var currentRate = currentRow.CurrentRate;
currentRow.NewRate = (currentRate + (currentRate * adjustment));
};
grid.refresh();
});
});
</script>
Thanks!
--Dan