Immediate grid with custom columns/rows
This example contains 2 custom column definitions: 1) to declare TypeId is an external key whose value is TypeName,
and whose values must be provided by an autocomplete. 2) to declare Price fas a colspan=2.
This examples handles also the possibility that some item models be a subclass containing futher properties.
For this reason a second row-type is defined whose type matches the subclass type, and whose column
definitions are inherited from the 0 index main row-type. Then, the new subclass specific MaintenanceYearlyRate column
is added and the Price colspan is overriden to 1 so that both fields fit into the Price column of the father class.
Column and row definitions may provide also custom edit/display templates defined in-line, with partial views and with ViewComponents.
Each control gives also the possibility to redefine its overall layout template. Thus, for instance, the grid control may be easily turned into
an ul/li list while keeping entirely its functionalities.
Please refer to this detail form example to see an in-line custom template definition.
Custom templates may be installed also globally by re-defining the partial
views with the default templates either in a controller specific folder or in the shared folder.
Important: All information contained in the external-key-remote
tag helper
on how to render external keys,
starting from version 2.1.0 may be specified also with data annotations,
thus simplifying the markup and increasing reusability of the code
@*antiforgery is compulsory *@ <form asp-antiforgery="true"> <div asp-validation-summary="All" class="text-danger"></div> <grid asp-for="Products.Data" type="Immediate" all-properties="true" mvc-controller="typeof(CGridsController)" row-id="immediate-custom-example" operations="user => Functionalities.FullInLine" class="table table-condensed table-bordered"> <column asp-for="Products.Data.Element().TypeId"> <external-key-remote display-property="Products.Data.Element().TypeName" items-value-property="Value" items-display-property="Display" items-url="@(Url.Action("GetTypes", "CGrids", new { search = "_zzz_" }))" dataset-name="product-types" url-token="_zzz_" max-results="20" /> </column> /> <column asp-for="Products.Data.Element().Price" colspan="2" /> <row-type asp-for="Products.Data.SubInfo<ProductMaintenanceViewModel>().Model" from-row="0"> <column asp-for="Products.Data.Element().Price" colspan="1" /> <column asp-for="Products.Data.SubElement<ProductMaintenanceViewModel>() .MaintenanceYearlyRate" /> </row-type> <toolbar zone-name="@LayoutStandardPlaces.Header"> <pager mode="CustomPages" class="pagination pagination-sm" max-pages="4" current-page="Products.Page" page-size-default="5" total-pages="Products.TotalPages" skip-url-token="_zzz_" url-default="@Url.Action("CustomServer", "CGrids", new {page="_zzz_" })" /> <button type="button" data-operation="add append 0" class="btn btn-sm btn-primary"> new product </button> <button type="button" data-operation="add append 1" class="btn btn-sm btn-primary"> new product with maintenance </button> </toolbar> </grid> </form>
/* DetailWidthsAsString in ColumnLayout specifies percentage width for different device screen widths, while WidthsAsString percentage width when shown in-line within a collection control. Oder specify the order columns are rendered. Higher Order come first. All setting, included Display/Name may be overriden by providing a column definition TagHelper within the control definition. */ public class ProductViewModel { public int? Id { get; set; } [MaxLength(128)] [ColumnLayout(DetailWidthsAsString = "100 50")] [Display(Name = "Name", Order = 400)] [Required] public string Name { get; set; } [ColumnLayout(DetailWidthsAsString = "60 30")] [Display(Name = "Price", Order = 300)] public decimal Price { get; set; } [Display(Name = "Cur", Order = 280)] [ColumnLayout(WidthsAsString = "10", DetailWidthsAsString = "40 20")] public Currency ChosenCurrency { get; set; } [ColumnLayout(DetailWidthsAsString = "30")] [Display(Name = "Av", Order = 230)] public bool Available { get; set; } [Display(Name = "Type", Order = 250)] public string TypeName { get; set; } [Display(Name = "Type", Order = 250)] [ColumnLayout(DetailWidthsAsString = "70")] public int? TypeId { get; set; } } /* All subclasses whose exact type is inferred at run-time by model binders/JSON deserializer must be decorated with the RunTimeType to prevent attacks consisting in the forced creation of dangerous types */ [RunTimeType] public class ProductMaintenanceViewModel: ProductViewModel, IUpdateConnections { [Display(Name = "Price/Year", Order = 299)] public decimal MaintenanceYearlyRate { get; set; } // IUpdateConnections specifies when to //update a connected entity contained in a property //with name prefix in the DB model public bool MayUpdate(string prefix) { return prefix == "Maintenance"; } } public class ProductViewModelDetail: ProductViewModel { [MaxLength(256)] [Display(Name = "Description", Order = 100)] [DisplayFormat(NullDisplayText = "no description available")] public string Description { get; set; } } /* All subclasses whose exact type is inferred at run-time by model binders/JSON deserializer must be decorated with the RunTimeType to prevent attacks consisting in the forced creation of dangerous types */ [RunTimeType] public class ProductMaintenanceViewModelDetail : ProductViewModelDetail, IUpdateConnections { [Display(Name = "Price/Year", Order = 299)] [ColumnLayout(DetailWidthsAsString = "30 15")] public decimal MaintenanceYearlyRate { get; set; } // IUpdateConnections specifies when to //update a connected entity contained in a property //with name prefix in the DB model public bool MayUpdate(string prefix) { return prefix == "Maintenance"; } }
public async Task<IActionResult> CustomServer(int? page) { int pg = page.HasValue ? page.Value : 1; if (pg < 1) pg = 1; var model = new ProductlistViewModel { Products = await Repository.GetPage<ProductViewModel>( null, q => q.OrderBy(m => m.Name), pg, 5) }; return View(model); } [HttpGet] public async Task<ActionResult> GetTypes(string search) { var res = search == null || search.Length < 3 ? new List<AutoCompleteItem>() : await (Repository as ProductRepository).GetTypes(search, 10); return Json(res); }
tfoot .pagination, thead .pagination { margin: 0; display: inline !important; } .full-cell { width: 100%; } table .input-validation-error { border: 1px solid red; background-color: mistyrose; }