Immediate detail-edit server grid

Pager has been cloned by setting copy-html="pager" and then pasted into a footer toolbar with <paste-html name="pager" />. Everything may be cloned by inserting it within a copy-html tag.
Name Price Cur Av
ink jet printer 100.00 $
laptop 1200.00
@*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(LiveExamples.Controllers.GridsController)"
          row-id="immediate-detail-edit-example"
          operations="user => Functionalities.FullDetail"
          class="table table-condensed table-bordered">
        <toolbar zone-name="@LayoutStandardPlaces.Header">
            <pager mode="CustomPages"
                   copy-html="pager"
                   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("DetailServer",
                               "Grids"new {page="_zzz_" })" />
            <button type="button" data-operation="add-detail append 0"
                    class="btn btn-sm btn-primary">
                new product
            </button>
        </toolbar>
        <toolbar zone-name="@LayoutStandardPlaces.Footer">
            <paste-html name="pager" />
        </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 SimpleProductViewModel 
{
    public int? Id { getset; }
    [MaxLength(128)]
    [ColumnLayout(DetailWidthsAsString = "100 50")]
    [Required]
    [Display(Name = "Name", Order = 400)]
    public string Name { getset; }
    [ColumnLayout(DetailWidthsAsString = "60 30")]
    [Display(Name = "Price", Order = 300)]
    public decimal Price { getset; }
    [Display(Name = "Cur", Order = 280)]
    [ColumnLayout(WidthsAsString = "10", DetailWidthsAsString = "40 20")]
    public Currency ChosenCurrency { getset; }
    [ColumnLayout(DetailWidthsAsString = "20")]
    [Display(Name = "Av", Order = 230)]
    public bool Available { getset; }
 
}   
 
public class SimpleProductViewModelDetailSimpleProductViewModel
{
    [MaxLength(256)]
    [Display(Name = "Description", Order = 100)]
    [ColumnLayout(DetailWidthsAsString = "80")]
    [DisplayFormat(NullDisplayText = "no description available")]
    public string Description { getset; }
}
public async Task<IActionResult> DetailServer(int? page)
{
    int pg = page.HasValue ? page.Value : 1;
    if (pg < 1) pg = 1;
 
    var model = new SimpleProductlistViewModel
    {
        Products = await Repository.GetPage<SimpleProductViewModel>(
        null,
        q => q.OrderBy(m => m.Name),
        pg, 5)
    };
    return View(model);
}
tfoot .paginationthead .pagination
 {
    margin0;
    displayinline !important;
}
 
.full-cell {
    width100%;
}
 
table .input-validation-error {
    border1px solid red;
    background-colormistyrose;
}

Fork me on GitHub