Telerik MVC grid with dynamic columns at run time from a collection or dictionary

After spending the last couple of days of searching, I am officially stuck. I am working on binding an object to a Telerik MVC 3 grid, but the trick is that it must have dynamically generated columns (not automatically generated). Three columns are known, the rest are unknown, and this is the hard part. Basically, it could be like these examples:

KnownColumn1 | KnownColumn2 | UnknownColumn1 | KnownColumn3 KnownColumn1 | KnownColumn2 | UnknownColumn1 | UnknownColumn2 | UnknownColumn3 | KnownColumn3 et al.

Because I put unknown columns on the list (I also tried the dictionary to get the column names), this has to do with complicated things for me when binding. My code is below:

Model (can be from zero to hundreds of lines, but this model is in the model of the List type, dynamic additions from 0 to 20 plus can also be added)

public class VendorPaymentsGLAccount { public string GeneralLedgerAccountNumber { get; set; } public string GeneralLedgerAccountName { get; set; } public string DisplayName { get { return string.Format("{0} - {1}", GeneralLedgerAccountNumber, GeneralLedgerAccountName); } } public Dictionary<string, double> MonthAmount { get; set; } public double Total { get { return MonthAmount.Sum(x => x.Value); } } public List<string> Columns { get; set; } public List<double> Amounts { get; set; } public VendorPaymentsGLAccount() { } } 

View (the section that commented was trying to use a dictionary)

 <fieldset> <legend>General Ledger Account Spend History</legend> @if (Model.VendorPaymentsGLAccounts != null) { <br /> @(Html.Telerik().Grid(Model.VendorPaymentsGLAccounts) .Name("Grid") .Columns(columns => { columns.Bound(gl => gl.DisplayName).Title("General Ledger Account").Width(200).Filterable(false).Sortable(false); //foreach (var month in Model.VendorPaymentsGLAccounts[0].MonthAmount) //{ // //columns.Bound(gl => gl.MonthAmount[month.Key.ToString()].ToString()).Title(month.Key.ToString()).Width(100).Filterable(false).Sortable(false); // //columns.Template(v => Html.ActionLink(v.VoucherID, "VoucherSummary", new { id = v.VoucherID, bu = v.BusinessUnitID, dtt = v.InvoiceDate.Ticks })).Title("Voucher").Width(100); // columns.Template(gl => Html.ActionLink(gl.MonthAmount[month.Key.ToString()].ToString(), "VoucherSummary")).Title(month.Key.ToString()).Width(100); //} for (int i = 1; i <= (Model.VendorPaymentsGLAccounts[0].Columns.Count() - 1); i++) { string colTemp = Model.VendorPaymentsGLAccounts[0].Columns[i - 1]; columns.Template(gl => gl.Amounts[i - 1]).Title(colTemp).Width(100); } columns.Template(gl => String.Format("{0:C}", gl.Total)).Title("Total"); }) .Sortable() .Pageable() .Filterable() .Footer(true)) } else { <br /> @:There are no records that match your selected criteria. } </fieldset> 

Using the dictionary approach, I managed to correctly form the columns with the correct header text, but the values ​​for the columns (in my testing there were only 2 columns) were the same. Can anyone help with this? This seems to be a strange problem. Just trying to figure out how to do it right.

Update: here is a screenshot using a dictionary approach that shows the problem. The column headings are correct, but the values ​​are the same for both dynamic columns.

Problem Screenshot

+6
source share
5 answers

Using dynamically defined columns using Telerik grid control can be difficult. But in your case, this is basically a typical closure trap.

In the next loop, the compiler will bind each instance of gl => gl.Amounts[i - 1] to the variable i and evaluate it later:

 for (int i = 1; i <= (Model.VendorPaymentsGLAccounts[0].Columns.Count() - 1); i++) { string colTemp = Model.VendorPaymentsGLAccounts[0].Columns[i - 1]; columns.Template(gl => gl.Amounts[i - 1]).Title(colTemp).Width(100); } 

In fact, it is evaluated after the completion of the cycle. Thus, i will always have a value that will end the loop.

The fix is ​​to use a temporary variable:

 for (int i = 1; i <= (Model.VendorPaymentsGLAccounts[0].Columns.Count() - 1); i++) { string colTemp = Model.VendorPaymentsGLAccounts[0].Columns[i - 1]; int columnIndex = i - 1; columns.Template(gl => gl.Amounts[columnIndex]).Title(colTemp).Width(100); } 
+7
source

I had the same problem, and I had many hours, and I made many attempts from different help lines. But even in this case, it was not so difficult to solve.

And for this reason, and also to have another working example here, I will also provide my solution!

Information:. This only works in my place using the IList model. Other collections also caused problems!

 @model IList<CBS.Web.Models.Equipment.EquipmentViewModel> @(Html.Telerik().Grid(Model) .Name("Grid") .DataKeys(keys => { keys.Add(m => m.ID); }) .DataBinding(dataBinding => { dataBinding.Ajax() // renders the grid initially .Select("EquipmentGrid", "Equipment"); }) .Columns(columns => { // Equipment IDs columns.Bound(m => m.ID).Hidden(true); columns.Bound(m => m.Name).Title("Equipments").Width(200); // Every item (EquipmentViewModel) of the Model has the same Count of Fields for (int i = 0; i < (Model[0].Fields.Count()); i++) { // Name of the column is everytime same as in Model[0] string columnName = Model[0].Fields.ElementAt(i).FieldDefinition.Name; // Constructs i-counted columns, dynamically on how much // Fields are owned by an Equipment. But note, that all Equipment-items // in the Model must have the same Count and disposal of Fields! columns.Template(m => m.Fields .Where(f => f.FieldDefinition.Name == columnName) .Where(f => f.EquipmentId == m.ID).First().Value) .Title(columnName) .Width(columnName.Length * 8); // * 8 was the optimal lenght per character } }) .ClientEvents(events => events.OnRowSelect("onRowSelected")) .Selectable() .Resizable(resizing => resizing.Columns(true)) .Pageable() .Scrollable() .Groupable() .Filterable() ) 

Controller:

 public ActionResult EquipmentGrid(Guid id) { var belongingEquipments = _equipmentRepository.GetNotDeleted() .OrderBy(e => e.Name).ToList() .Where(e => e.RevisionId == id); List<EquipmentViewModel> equVMList = new List<EquipmentViewModel>(); for (int i = 0; i < belongingEquipments.Count(); i++) { var equVM = new EquipmentViewModel { ID = belongingEquipments.ElementAt(i).ID, Name = belongingEquipments.ElementAt(i).Name, RevisionId = belongingEquipments.ElementAt(i).RevisionId, EquipmentTypeId = belongingEquipments.ElementAt(i).EquipmentTypeId, Fields = SortFields(belongingEquipments.ElementAt(i).Fields.ToList()) }; equVMList.Add(equVM); } return PartialView("EquipmentGrid", equVMList); } 

Models:

 namespace CBS.Web.Models.Equipment { public class EquipmentViewModel { public Guid ID { get; set; } public string Name { get; set; } public Guid RevisionId { get; set; } public Guid EquipmentTypeId { get; set; } public virtual ICollection<FieldEntity> Fields { get; set; } } } 

Fielddefinition

 namespace CBS.DataAccess.Entities { public class FieldDefinitionEntity : EntityBase { [Required] public virtual Guid EquipmentTypeId { get; set; } public virtual EquipmentTypeEntity EquipmentType { get; set; } [Required(AllowEmptyStrings = false)] public virtual string Name { get; set; } public virtual int Numbering { get; set; } [Required] public virtual Guid TypeInformationId { get; set; } public virtual TypeInformationEntity TypeInformation { get; set; } public virtual ICollection<FieldEntity> Fields { get; set; } } } 

Field

 namespace CBS.DataAccess.Entities { public class FieldEntity : EntityBase { [Required] public virtual Guid EquipmentId { get; set; } public virtual EquipmentEntity Equipment { get; set; } [Required] public virtual Guid FieldDefinitionId { get; set; } public virtual FieldDefinitionEntity FieldDefinition { get; set; } public virtual string Value { get; set; } } } 
+2
source

I dynamically bind columns at runtime with reflection:

 @model IEnumerable<object> @using System.Collections @using System.Collections.Generic @using System.Reflection; @(Html.Telerik().Grid(Model) .Name("Grid") .Columns(columns => { Type t = Model.GetType().GetGenericArguments()[0]; foreach (var prop in t.GetProperties()) { if (IsCoreType(prop.PropertyType)) { columns.Bound(prop.PropertyType, prop.Name); } } }) .DataBinding(binding => binding.Ajax() .Select("SelectMethod", "SomeController") ) .Sortable() .Pageable() .Filterable() .Groupable() ) @functions{ public bool IsCoreType(Type type) { if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { return IsCoreType(type.GetGenericArguments()[0]); } return !(type != typeof(object) && Type.GetTypeCode(type) == TypeCode.Object); } } 
+2
source

Here is a workaround:

 @(Html.Telerik().Grid(Model.Users) .Name("Grid") .Columns(columns => { columns.GenerateCustomColumns(columnSettings); }).DataBinding(dataBinding => dataBinding.Ajax().Select("_getusers", "home")) .Scrollable(scrolling => scrolling.Enabled(true).Height("auto")) .Pageable(paging => paging.Enabled(true) .PageSize(10, new int[] { 5, 10, 20, 50, 100, 500 }) .Position(GridPagerPosition.Both) .Total(Model.Users.Count) .Style(GridPagerStyles.PageSizeDropDown | GridPagerStyles.NextPreviousAndNumeric) .PageTo(1)) .Filterable(filtering => filtering.Enabled(true)) .Reorderable(reordering => reordering.Columns(true)) .NoRecordsTemplate(" ") .EnableCustomBinding(true) 

)

// Extension method for dynamic column parsing

 public static class TelerikMvcGridColumnHelper { public static void GenerateCustomColumns<T>(this GridColumnFactory<T> columns,List<GridCustomColumnSettings> settings) where T:class { if (settings != null) { settings.ForEach(column => { var boundedColumn = columns.Bound(column.Member); if (column.ClientFooterTemplate != null) boundedColumn.ClientFooterTemplate(column.ClientFooterTemplate); if (!string.IsNullOrEmpty(column.Width)) boundedColumn.Width(column.Width); }); } } } 

// Column settings class

 public class GridCustomColumnSettings : GridColumnSettings { public string ClientFooterTemplate { get; set; } } 
+1
source

I made this easy way. NOTE. The following solution also works in ajax edit mode (and not just in a read-only grid):

when ViewModels:

 public class PriceSheetEditGridViewModel { public IEnumerable<PriceSheetRowViewModel> Rows { get; set; } public IEnumerable<PriceSheetColumnViewModel> Columns { get; set; } } 

public class PriceSheetColumnViewModel {public int Id {get; set; } public string Title {get; set; }}

 public class PriceSheetRowViewModel { public int RowNo { get; set; } public string Description { get; set; } public double?[] Prices { get; set; } } 

the view may be like this (part of the view.cshtml ... file):

  .... @model PriceSheetEditGridViewModel ... columns.Bound(o => o.Description ).Width(150); int i = 0; foreach (var col in Model.Columns) { columns .Bound(model => model.Prices).EditorTemplateName("PriceSheetCellPrice").EditorViewData(new { ColumnId = i }) .ClientTemplate("<span><#=Prices ? jsHelper.addCommas(Prices[" + i.ToString() + "]):null#></span>") .Title(col.Title).Width(80); i++; } .... 

and the PriceSheetCellPrice.cshtml editor template file (in the shared \ editortemplates folder):

  @model decimal? @(Html.Telerik().NumericTextBox() .Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty)+"["+ViewBag.ColumnId+"]") .InputHtmlAttributes(new { style = "width:100%" }) }) .EmptyMessage("") .DecimalDigits(0) .DecimalSeparator(",") .MinValue(0) .Value((double?) Model) ) 
0
source

Source: https://habr.com/ru/post/920426/


All Articles