Custom layout server grid

  • 16G Tablet
  • 1T High speed
  • 1T SSD
  • 3D Glasses
  • color laser printer

In this example we declared both a custom layout template, and a custom row template. Our custom row template is quite different from the default row template that renders several columns each representing an item property, because in this case each row just displays buttons to edit/delete the item and the result of calling ToString() on each data item. We redefiend ToString to display the item name (see ViewModel definition), but with a differnt ViewModel we may redefine it to provide a different short description of the data item.

Layout/Row templates implement an in-line ul/li list. Both tempates are defined as partial views. Layout template is declared with: layout-template="_InLineListLayout" in the grid tag, while row template is specified inside the main row definition (that is the grid tag itself) with the template specific tag: asp-template. asp-template is used to declare both column and row templates based on either partial views, view components, or inline as content of the asp-template tag itself. We defined just a Display row templates since the grid is edited through detail forms. You may find a custom column defined with an in-line template in this detail form example.

Both templates extract an options object from the ViewBag. The row template option object contains the RowType definition.

Both partials have a ViewModel of type object because they were designed to work with all ViewModel types. This way, we may use them with all grids. However, user may define also templates specific for a single type, to be used with a single grid. Layout templates usually doesn't use the ViewModel received, while row templates do use it to render items. When the model is declared as an object they may render data item properties by invoking await row.RenderColumn on each column definition contained in row.Columns.

In the layout template options.RenderTopContainerAttributes() and options.RenderRowsContainerAttributes() are obligatory respectively in the top container and in the immediate parent of all items.

In the row template row.RenderRowAttributes(Model) is obligatory in the item root tag. Buttons are rendered with currProvider.RenderButton just because it is easier, but any button with data-operation='delete' / data-operation='edit-detail' can do the job.

< span style="color:blue;">< form     asp - antiforgery < span style="color:blue;">= " true < span style="color:blue;">" >
     < div    asp - validation - summary < span style="color:blue;">= "All" class=< span style="color:blue;">"text-danger" ></ div < span style="color:blue;">>
     < grid    asp -for=< span style="color:blue;">"Products.Data"
           type < span style="color:blue;">= "Immediate"
           all - properties < span style="color:blue;">= " true < span style="color:blue;">"
           mvc - controller < span style="color:blue;">= " typeof (LiveExamples.Controllers.< span style="color:#2b91af;">GridsController) "
           row - id < span style="color:blue;">= " custom-layout-example"
           operations < span style="color:blue;">= "user =>  Functionalities .FullDetail < span style="color:blue;">"
           layout - template < span style="color:blue;">= " _InLineListLayout"
           >
         < asp - template    type < span style="color:blue;">= "Display"  use - partial < span style="color:blue;">= " _InLineListItem"/ >
         < toolbar    zone - name < span style="color:blue;">= " @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("CustomLayoutServer", 
                               "Grids"new {page="_zzz_" })" />
            <button type="button" data-operation="add-detail append 0" 
                    class="btn btn-sm btn-primary">
                new product
            </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 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 override string ToString()
        {
            return Name;
        }
 
    }  
@model Object
@using MvcControlsToolkit.Core.Templates
@*model receives the whole list, but usually we donìt neet it here*@
@{
    @*we need options instead*@
var options = ViewData["Options"as DefaultServerGridLayoutOptions;
 
    @*Get header toolbars if any*@
IEnumerable<string> headerToolbarsContent;
if (options.Toolbars != null)
{
    headerToolbarsContent = options.Toolbars
    .Where(m => m.Key == LayoutStandardPlaces.Header)
    .Select(m => m.Value);
}
else
{
    headerToolbarsContent = new List<string>();
}
 
}
 
<div @options.RenderTopContainerAttributes()>
    @foreach (var toolbar in headerToolbarsContent)
    {
        @*header-toolbar is not a standard name,
            just a class we have chosen to apply some style*@
        <div class="header-toolbar">
            @Html.Raw(toolbar)
        </div>
    }
    <ul @options.RenderRowsContainerAttributes() class="list-inline">
        @options.MainContent
    </ul>
 
</div>
@model Object
@using MvcControlsToolkit.Core.Templates
@inject Microsoft.Extensions.Localization.IStringLocalizerFactory loclizerFactory
@* model contains the row model we dont specify type, 
    so row template may be used with all ViewModels*@
@{
    var row = ViewData["Options"as RowType;
    @*get cuttent provider (default server controls provider) to render 
        buttons easily*@
    var currProvider = ViewContext.TagHelperProvider();
 
    @*get a localizer if a localization type has been 
        provided in the RowType TagHelper*@
    var localizer = row.GetLocalizer(loclizerFactory);
    @*get enabled operations for current user*@
    var buttons = row.RequiredFunctionalities(User);
}
<li  @row.RenderRowAttributes(Model)>
    @if ((buttons & Functionalities.ShowDetail) > 0)
    {
        @currProvider.RenderButton(StandardButtons.ShowDetail, null"btn-default btn-xs"null, localizer)
    }
    @if ((buttons & Functionalities.EditDetail) > 0)
    {
        @currProvider.RenderButton(StandardButtons.EditDetail, null"btn-default btn-xs"null, localizer)
    }
    @if ((buttons & Functionalities.Delete) > 0)
    {
        @currProvider.RenderButton(StandardButtons.Delete, null"btn-default btn-xs"null, localizer)
    }
 
    <span>@Model.ToString()</span>
</li>
public async Task<IActionResult> CustomLayoutServer(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);
}
.header-toolbar{
    margin5px 0 5px 0;
}
.header-toolbar .pagination
 {
    margin0;
    displayinline !important;
}
 
.list-inline li
{
    bordersolid 1px gray;
    border-radius:4px 4px;
    margin-left5px;
}

Fork me on GitHub