I have a Kendo UI Grid that is being used to filter, group, and display aggregates. This data is then exported to Excel via the toolbar. It works very well and we like the way that the default Export looks.
However the number of rows can be quite large, in some cases climbing above 150,000. This causes the page to become unresponsive, but only with grouping (sometimes two groups can be nested) and aggregates involved. If the columns are not grouped then the performance is acceptable.
Is there a way to improve the performance of the Excel export when it is using group headers and aggregates?
We would like to avoid using the server-side export as an alternative because the export's appearance can vary based on the user's interaction with the grid and will be unpredictable unless the grid options can somehow be parsed at the controller and be used to build the Excel sheet the way it does on the client side.
As per document : ASP.NET MVC jQuery Support - Telerik UI for ASP.NET MVC
It says the MVC 2023.1.117.0 support JQuery 3.6.1, but when i tried to use JQuery 3.6.1 on MVC Example demo , the page won't load the component.
Do i need to setting another thing to upgrade jquery ?
[HttpPost]
public ActionResult ExportMultipleGridsFile(List<GridExportData<dynamic>> grids)
{
try
{
var exportStream = new MemoryStream();
using (IWorkbookExporter workbookExporter = SpreadExporter.CreateWorkbookExporter(SpreadDocumentFormat.Xlsx, exportStream, SpreadExportMode.Create))
{
foreach (var grid in grids)
{
var columnsData = JsonConvert.DeserializeObject<IList<ExportColumnSettings>>(HttpUtility.UrlDecode(grid.Model));
var removableColumns = new List<ExportColumnSettings>();
// Remove columns with invalid width or field
foreach (var column in columnsData)
{
if (column.Width == null || column.Width.Equals(new Unit()))
{
column.Width = new Unit("100px"); // Default width
}
if (string.IsNullOrWhiteSpace(column.Field))
removableColumns.Add(column);
}
columnsData = columnsData.Except(removableColumns).ToList();
dynamic options = JsonConvert.DeserializeObject(HttpUtility.UrlDecode(grid.Option));
var worksheetName = options.title.ToString();
if (worksheetName.Length > 31)
{
worksheetName = worksheetName.Substring(0, 31);
}
worksheetName = Regex.Replace(worksheetName, @"[:\/\\\?\*\[\]]", "_");
using (IWorksheetExporter worksheetExporter = workbookExporter.CreateWorksheetExporter(worksheetName))
{
// Attempt to deserialize the data
var data = Retrieve();
//var data = JsonConvert.DeserializeObject<List<IntakeReport>>(HttpUtility.UrlDecode(grid.Data));
// Check if data is null or empty
if (data == null || !data.Any())
{
// Log or handle the issue as necessary
throw new InvalidOperationException("Data is null or empty after deserialization.");
}
// Add headers with validation
using (IRowExporter headerRow = worksheetExporter.CreateRowExporter())
{
foreach (var column in columnsData)
{
if (!string.IsNullOrEmpty(column.Title))
{
using (var cellExporter = headerRow.CreateCellExporter())
{
cellExporter.SetValue(column.Title);
}
}
}
}
// Add rows with validation
foreach (var item in data)
{
using (IRowExporter row = worksheetExporter.CreateRowExporter())
{
foreach (var column in columnsData)
{
var cellValue = GetCellValue(item, column.Field);
using (var cellExporter = row.CreateCellExporter())
{
if (cellValue != null)
{
// Remove newline characters
var cleanedValue = cellValue.ToString().Replace("\n", " ").Replace("\r", " ");
cellExporter.SetValue(cleanedValue);
}
else
{
// Optionally handle null values
cellExporter.SetValue("string Empty");
}
}
}
}
}
}
}
workbookExporter.Dispose();
}
// Reset the stream position before sending it
exportStream.Seek(0, SeekOrigin.Begin);
// Return the file with the correct MIME type and filename
return File(exportStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "CombinedExport.xlsx");
}
catch (Exception ex)
{
// Log the exception message (if logging is configured)
// Example: _logger.LogError(ex, "Export failed");
// Rethrow the exception to handle it further up the call stack
throw;
}
}
private object GetCellValue(dynamic item, string fieldName)
{
// Check if the item is a dictionary, which is common in dynamic objects like ExpandoObject or JSON objects
if (item is IDictionary<string, object> dictionary)
{
// Try to get the value from the dictionary
return dictionary.TryGetValue(fieldName, out var value) ? value : null;
}
// If not a dictionary, use reflection to get the property
var property = item.GetType().GetProperty(fieldName);
// Ensure the property exists and return its value; otherwise return null
return property != null ? property.GetValue(item, null) : null;
}
After upgrading Kendo MVC from v2021.2.616 to v2024.2.514 (KendoUIProfessional), the dropdownlist "SeriesType" on the grid is not firing the onChange event anymore. I saw an error message in the browser console.
Can you guys suggest a migration solution for it? Thanks!
Browser console error:
cshtml
@(Html.Kendo().Grid<MySeriesModel>().Name("drpSeries")
.Columns(column =>
{
column.Bound(model => model.SeriesName).HtmlAttributes(new { style = "font-weight: bold" }).Title("No.").Width(40);
column.ForeignKey(c => c.SeriesType, (IEnumerable<SelectListItem>)ViewBag.LstSeriesType, "value", "text").HtmlAttributes(new { style = "text-align: left", onChange = "onChangeSeriesType('#=ID#'); setSerieXml();" }).Title("Series Type").MinScreenWidth(60);
column.Bound(model => model.SeriesTitle).Title("Series Title").HtmlAttributes(new { onChange = "setSerieXml();" }).MinScreenWidth(120).Encoded(false);
column.Bound(model => model.Axis).Title("Axis").ClientTemplate("#=getAxisActionLink(ID,Axis)#").MinScreenWidth(120);
column.ForeignKey(c => c.Y_Format, (IEnumerable<SelectListItem>)ViewBag.LstSeriesFormat, "value", "text").HtmlAttributes(new { style = "text-align: left", onChange = "onChangeFormat(this);" }).Title("Format").MinScreenWidth(150);
column.ForeignKey(c => c.Y_Axis, (IEnumerable<SelectListItem>)ViewBag.ListSerriesYAxis, "value", "text").HtmlAttributes(new { style = "text-align: left", onChange = "onChangeYAxis(this,'#=ID#');" }).Title("Y Axis Scale").Width(100);
column.Bound(x => x.ID).ClientTemplate("<button class= 'k-button' type='button' onclick=onRemoveSeries('#=ID#')>" + "Remove" + "</button>").HtmlAttributes(new { style = "text-align: center" }).Title("").Width(120);
})
.DataSource(dataSource => dataSource
.Ajax()
.ServerOperation(false)
.Model(model =>
{
model.Id(item => item.ID);
model.Field(item => item.ID).Editable(false);
model.Field(item => item.SeriesName).Editable(false);
model.Field(item => item.Axis).Editable(false);
model.Field(item => item.SeriesTitle).Editable(false);
})
.Read(read => read.Action("SeriesList_DataSource", "Dashboard").Data("getDataForDgdSeries_Read"))
)
.Events(e =>
{
e.DataBound("onDataBoundDgdSeries");
})
.Resizable(x => x.Columns(true))
.AutoBind(true)
.Scrollable(x => x.Enabled(true))
.Editable(editable => editable.Mode(GridEditMode.InCell))
.Selectable(selectable => selectable.Mode(GridSelectionMode.Single))
.HtmlAttributes(new { style = "overflow: auto; width:100%", @class = "form-group" })
)
JS
function onChangeSeriesType(id) {
drpSeries = $('#drpSeries').data('kendoGrid');
dataSource = drpSeries.dataSource;
var lstSerieRow = dataSource.data();
if (lstSerieRow[id - 1].Axis.length > 0) {
lstSerieRow[id - 1].SeriesTitle = "";
lstSerieRow[id - 1].Axis = "";
dataSource.fetch();
}
}
Model
public class MySeriesModel
{
public string ID { get; set; }
public string SeriesName { get; set; }
public string AxisName { get; set; }
public string Axis { get; set; }
[AllowHtml]
[UIHint("GridDropDownList")]
public string SeriesType { get; set; }
public string SeriesTitle { get; set; }
public string X_Ordinate { get; set; }
public string X_Format { get; set; }
[AllowHtml]
[UIHint("GridDropDownList")]
public string Y_Format { get; set; }
[AllowHtml]
[UIHint("GridDropDownList")]
public string Y_Ordinate { get; set; }
[AllowHtml]
[UIHint("GridDropDownList")]
public string Y_OrdinateTo { get; set; }
public string TOOLTIP_FORMAT { get; set; }
[AllowHtml]
[UIHint("GridDropDownList")]
public string Y_Axis { get; set; }
}
controller
public ActionResult RenderSeriesConfig(string viewName)
{
List<SelectListItem> seriesType = GetDashboadChartStyleTypeList();
string arrSeriesType = "";
for (int i = 0; i < seriesType.Count; i++)
{
arrSeriesType += seriesType[i].Text.ToString() + "," + seriesType[i].Value.ToString() + ";";
}
ViewBag.LstSeriesType = seriesType;
ViewBag.ArrSeriesType = arrSeriesType;
ViewBag.LstSeriesAxis_Ordinate = GetLstFieldNamesSelectListItem(viewName);
ViewBag.LstSeriesFormat = GetFormatShortList();
ViewBag.ListSerriesYAxis = GetYAxisSettingList();
return PartialView("_ChartDetail_Series");
}
A bug has been introduced recently where the calendar icon for DatePicker does not display
I can duplicate this bug using your REPL
https://demos.telerik.com/aspnet-core/datepicker
From here I add after line 9
.HtmlAttributes(new { @style = "width: 150px;"})
As you can see this results in no calendar to pick the date
If you make it even wider some of the icon becomes visible
.HtmlAttributes(new { @style = "width: 200px;"})
And at 250 it is fully visible
.HtmlAttributes(new { @style = "width: 250px;"})
We do this across our app using an extension method and it was previously working.
Hi,
I showing a grid where some columns has username information. when a user is inactive then the username has a strike trough styling by class
This column is a a ForeignKey with ClientTemplate for displaying and the EditorTemplateName to control the editing
what shows a dropdown with Template and ValueTemplate on the UIHint schtml.
And event databound to add a class for the strike trough styling.
So my grid column and dropdwon during edit are all having styling for inactive users
But when I click the filter icon the dropdown has to do the same
Where do I do that?
I cannot find any template or something else to control the ForeignKey bound dropdown.
Can someone help me with this styling problem?
Thanks
Summary
Struggling with this issue for weeks now. Please need help!
Goal - Stop the wizard from going to next step when there is a custom validation
Issue : The custom validation "Min age has to be 16 years " shows up on Validation blur , but does not stop the propagation to the next step.
validator.validate() comes up as true in both form and div approach
First approach
1. Addded a kendo wizard with a .Tag("div") tag so that 4 steps in there show up as individual forms
@(
Html.Kendo().Wizard()
.Name("LicenseWizard")
.Tag("form")
.Steps(s =>
{
s.Add<WizardViewModel>()
.Title("County")
.ClassName("CountyClass")
.Form(f => f
.Name("FormCounty")
.FormData(Model)
... There are 4 steps in total
2. Used this in the Onselect step method
function onSelect(e) {
// Get the current step's index
var stepIndex = e.step.options.index;
// Get the current step's container element
var container = e.sender.currentStep.element;
if (stepIndex < currentStep) {
e.preventDefault();
}
else {
// Validate Party One
if (stepIndex == 2) {
var form = $("#LicenseWizard-1").find('form');
var validator = form.data("kendoValidator");
alert('validator.validate()' + validator.validate());
// Validate given input elements
if (!validator.validate()) {
// Prevent the wizard's navigation
e.preventDefault();
}
Shows true and goes to next step
Second approach
1. Use the .Tag("form")
@(
Html.Kendo().Wizard()
.Name("LicenseWizard")
.Tag("div")
.Steps(s =>
{
s.Add<WizardViewModel>()
.Title("County")
.ClassName("CountyClass")
.Form(f => f
.Name("FormCounty")
.FormData(Model)
.Items(items =>
2. In the
// Validate
if (stepIndex == 2) {
var form = $("#LicenseWizard-1").find('form');
var validator = form.data("kendoValidator");
// Instantiate a validator for the container element
// Get the validator's reference
var validator = $(container).kendoValidator().data("kendoValidator");
alert('validator.validate()' + validator.validate());
// Validate given input elements
if (!validator.validate()) {
// Prevent the wizard's navigation
e.preventDefault();
Even this returns true
$(function () {
$("#LicenseWizard").kendoValidator({
rules: {
requiredIfValue: function (input) {
//debugger;
if (input.is("[data-val-requiredifvalue]") && !input.val()) {
....
}
minagedate: function (input) {
if (input.is("[id ='PartyOne.Pty1DateOfBirth']") || input.is("[id ='PartyTwo.Pty2DateOfBirth']")) {
var value = $(input).val();
...............//
//console.log('todayMinus16' + todayMinus16);
var result = (todayMinus16 >= birthdate);
return result;
}
Question : minagedate validate on blur works . When I click on next , on Step 2 , the container in the approach when i use a div tag on wizard is the form that I get manually and in the second approach it is container that I get using the step method.
I defined rules at the main wizard level ----
$(function () {
$("#LicenseWizard").kendoValidator({
how can make the validator.validate() false in my steps when there is something on the custom rules (that also show up on validate on blur) ?
I skipped 2024.1.319 so can't confirm if this behavior exists in that version.
Attached is screenshot of the first page (with blacked out redacted information). The 2024.1.130-exporttopdf-grid.png show expected/as is function of exporting a grid of data to pdf.
2024.2.514-exporttopdf-grid.png shows what the same data and grid. There is an "Exporting..." overlay message and the last row of data appears to be repeated and offset.
Is this a bug in 2024.2.514?