Grid with in-line filter

This examples, differs from, the Grid with query windows example only in the filter form being in-line in the web page instead of opening with a query button. Please, refer to that example for more explanations on how to set-up queries.

This example explains just how to put in-line query windows. Basically, instead of using query TagHelpers in a grid toolbar, we add a query-inline TagHelper in the pace where we would like the query form to appear.

Since in this case information can't be inherited from the grid (since it is not an ancestor TagHelper), we must pass the property containing the QueryDescriprion object in the asp-for TagHelper attribute, and the collection where to apply the query to its collection-for attribute. Query window specific toolbars may be placed inside the TagHelper itself, while the RowType definition must be imported by the grid.

Grid may export all its row definitions, by assigning them a name with the rows-cache-key grid attribute. While, the query-inline TagHelper may import them by accepting the same name in its row-collection-name attribute.

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

 
name package type unit price discontinued supplier
Gustaf's Knäckebröd 24 - 500 g pkgs. 21.00 PB Knäckebröd AB
Ikura 12 - 200 ml jars 31.00 Tokyo Traders
Inlagd Sill 24 - 250 g jars 19.00 Svensk Sjöföda AB
Ipoh Coffee 16 - 500 g tins 46.00 Leka Trading
Jack's New England Clam Chowder 12 - 12 oz cans 9.65 New England Seafood Cannery
@model LiveExamples.Viemodels.FoodListViewModel
@{
    ViewData["Title"] = "Grid with in-line filter";
    if (Model.Query.AttachedTo == null)
    {
        Model.Query.AttachEndpoint(Url.Action("IndexInLine""Food"));
    }
}
...
...
...
 
<grid asp-for="Products.Data"
      type="Immediate"
      all-properties="true"
      mvc-controller="typeof(LiveExamples.Controllers.GridsController)"
      row-id="readonly-query"
      operations="user => Functionalities.ReadOnly | Functionalities.GroupDetail"
      query-for="Query"
      sorting-clauses="2"
      enable-query="true"
      rows-cache-key="export-for-query"
      query-grouping-type="typeof(FoodViewModelGrouping)"
      class="table table-condensed table-bordered">
    <column asp-for="Products.Data.Element().SupplierId">
        <external-key-remote display-property="Products.Data
                                 .Element().SupplierCompanyName"
                             items-value-property="Value"
                             items-display-property="Display"
                             items-url="@(Url.Action("GetSuppliers",
                           "Food"new { search = "_zzz_" }))"
                             dataset-name="suppliers"
                             url-token="_zzz_"
                             max-results="20" />
    </column>
    <row-type asp-for="Products.Data.SubInfo<FoodViewModelGrouping>().Model"
              from-row="0">
        <column asp-for="Products.Data
                .SubElement<FoodViewModelGrouping>().SupplierIdCount" />
            <column asp-for="Products.Data
                    .SubElement<FoodViewModelGrouping>().PackageCount" />
    </row-type>
    <toolbar zone-name="@LayoutStandardPlaces.Header">
        <pager class="pagination pagination-sm"
               max-pages="4"
               page-size-default="5"
               total-pages="Products.TotalPages" />
        &nbsp;
        <query type="Sorting" />
        <query type="Grouping" />
    </toolbar>
</grid>
 
<query-inline type="Filtering" asp-for="Query"
              collection-for="Products.Data"
              row-collection-name="export-for-query" />
...
...
...
@section Scripts {
<link href="~/lib/awesomplete/awesomplete.css" rel="stylesheet" />
<script src="~/lib/mvcct-controls/mvcct.controls.min.js"></script>
<script src="~/lib/mvcct-controls/modules/mvcct.controls.ajax.min.js"></script>
<script src="~/lib/awesomplete/awesomplete.min.js"></script>
<script src="~/lib/mvcct-controls/modules/mvcct.controls.autocomplete.min.js"></script>
<script src="~/lib/mvcct-controls/modules/mvcct.controls.serverGrid.min.js"></script>
<script src="~/lib/mvcct-odata/dest/global/mvcct.odata.min.js"></script>
<script src="~/lib/mvcct-controls/modules/mvcct.controls.query.min.js"></script>
}
public class FoodViewModel
{
    public int? Id { getset; }
 
    [QueryStringLength(64, MinimumLength = 2) Required, 
        Display(Name ="name")]
    public string ProductName { getset; }
 
    [QueryStringLength(32, MinimumLength = 2), Required,
        Display(Name = "package type")]
    public string Package { getset; }
    [Range(0, 1000), Query,
        Display(Name = "unit price")]
    public decimal UnitPrice { getset; }
    [Display(Name = "discontinued")]
    public bool IsDiscontinued { getset; }
    [Query,
        Display(Name = "supplier")]
    public int SupplierId { getset; }
    [Query,
        Display(Name = "supplier")]
    public string SupplierCompanyName { getset; }
 
}
[RunTimeType]
public class FoodViewModelGroupingFoodViewModel
{
    public int SupplierIdCount { getset; }
    public int PackageCount { getset; }
 
 
}
public class FoodControllerServerCrudController<FoodViewModelFoodViewModelint?>
{
    public FoodController(FoodRepository repository, 
            IStringLocalizerFactory factory, IHttpContextAccessor accessor, 
            IWebQueryProvider queryProvider) :
        base(factory, accessor)
    {
            
        Repository = repository;
        this.queryProvider = queryProvider;
    }
 
    public IWebQueryProvider queryProvider { getprivate set; }
 
    public async Task<IActionResult> IndexInLine()
    {
        var query = queryProvider.Parse<FoodViewModel>();
 
        int pg = (int)query.Page;
        var grouping = query.GetGrouping<FoodViewModelGrouping>();
 
        var model = new FoodListViewModel
        {
            Query = query,
            Products =
                grouping == null ?
                    await Repository.GetPage(
                        query.GetFilterExpression(),
                        query.GetSorting() ??
                            (q => q.OrderBy(m => m.ProductName)),
                        pg, 5)
                    :
                    await Repository.GetPageExtended(
                        query.GetFilterExpression(),
                        query.GetSorting<FoodViewModelGrouping>() ??
                            (q => q.OrderBy(m => m.ProductName)),
                        pg, 5,
                        query.GetGrouping<FoodViewModelGrouping>())
        };
 
        return View(model);
    }
 
    [HttpGet]
    public async Task<ActionResult> GetSuppliers(string search)
    {
        var res = search == null || search.Length < 3 ?
            new List<AutoCompleteItem>() :
            await (Repository as FoodRepository).GetSuppliers(search, 10);
        return Json(res);
    }
}
tfoot .paginationthead .pagination 
    {
        margin0;
        displayinline !important;
    }
public class FoodRepository : DefaultCRUDRepository<ApplicationDbContextFood>
{
    private ApplicationDbContext db;
    public  FoodRepository(ApplicationDbContext db):
        base(db, db.Foods)
    {
        this.db = db;
    }
    public async Task<IEnumerable<AutoCompleteItem>> 
        GetSuppliers(string search, int maxpages)
    {
        return (await DefaultCRUDRepository.Create(db, db.Suppliers)
                .GetPage<AutoCompleteItem>(m => m.Display.StartsWith(search),
                m => m.OrderBy(n => n.Display), 1, maxpages))
                .Data;
    }
    static FoodRepository()
    {
        DefaultCRUDRepository<ApplicationDbContextSupplier>
            .DeclareProjection(m => new AutoCompleteItem
            {
                Display = m.CompanyName,
                Value = m.Id
            });
    }

Fork me on GitHub