Grid Filtering

Grid with filter row

  you can search multiple columns at the same time (try 'pizza la')

In this demo you can filter the grid using the textbox outside the grid, and using the Filter row.
Using the textbox you can search by multiple columns by typing multiple words separated by space.
The filter row generates a dropdown for each column that it receives data from the data source (DataFunc or Url), and we can also specify a custom editor for the filter column.
We also take into account the order in which the user has selected the filters, so the filter dropdowns/multiselect will also get filtered based on the previous selected filter.
The filtering code is under your control so for example on the meals column we query the data and select the rows that contain all the selected meals, but you could change it to select the ones that contain any of the selected meals.
GridDemo/GridFilterClientData.cshtml
<div class="bar">
@Html.Awe().TextBox("txtGridFrowSrc").Placeholder("search ...").CssClass("searchtxt")
<span class="hint">&nbsp; you can search multiple columns at the same time (try 'pizza la')</span>
</div>
@(Html.Awe().Grid("GridFrow")
.Height(390)
.Reorderable()
.Resizable()
.Parent("txtGridFrowSrc", "search", false)
.DataFunc("loadLunchesGrid")
.Mod(o => o.Main().ColumnsAutohide().FilterRow(keyupsel: ".awe-txt")) // Person textbox has .awe-txt css class
.Columns(
new Column {Bind = "Id", Width = 100 }.Mod(o => o.Filter("search:")),

new Column {Bind = "Person"}
.Mod(o => o.Filter(Html.Awe().TextBox("Person").Placeholder("Person").ClearButton())),

new Column { Bind = "Food", ClientFormatFunc = "site.imgFood", MinWidth = 200 }
.Mod(o => o.Filter(
Html.Awe().AjaxRadioList("Food")
.DataFunc("foodGetData")
.Odropdown(d => d.ItemFunc("site.imgFoodItem")
.CaptionFunc("site.imgFoodCaption")
.ClearBtn()))),

new Column { Bind = "Date", ClientFormat = ".(DateStr)", Width = 170 },
new Column { Bind = "CountryName", Header = "Country" },

new Column { ClientFormat = ".(MealsStr)", MinWidth = 200, Header = "Meals", Grow = 1.7 }

// define valProp, by default Column.Bind is used (which we don't have for this column)
.Mod(o => o.Filter(valProp: "Meals", type: FilterType.Multiselect)),

new Column { Bind = "ChefName", Header = "Chef"}))
<script>
function loadLunchesGrid(sgp) {
// cache storage used by this demo (cstorg.js), it will load data on first call,
// for no cache you can replace cstorg.get with $.get
return $.when(cstorg.get('@Url.Action("GetLunches", "Data")')).then(function(lunches) {
return getGridData(sgp, lunches);
});
}

function getGridData(sgp, lunches) {
var where = awef.where, select = awef.select, contains = awef.scont, loop = awef.loop;
var distLstFunc = utils.distLstFunc;
var gp = utils.getGridParams(sgp, ['meals']); // meals is array
var data = lunches;

// outside textbox search
if (gp.search) {
var words = gp.search.toLowerCase().split(" ");

data = where(lunches, function (o) {
var matches = 0;
loop(words, function (w) {
if (contains(o.Food, w) || contains(o.Person, w) || contains(o.MealsStr, w) || contains(o.CountryName, w)
|| contains(o.DateStr, w) || contains(o.ChefName, w)) matches++;
});

return matches === words.length;
});
}

// data used to populate some of the filter row dropdowns
var frowData = {};

// filter rules, will be applied in order ( gp.forder ),
// because we want to get the data for the dropdowns in order of filter application
var filterRules = {
Person: function() {
if (gp.person) {
data = where(data, function (o) { return contains(o.Person, gp.person) });
}
},
Food: function() {
var items = utils.distItmsFunc('Food')(data);

frowData.Food = [{k: '', c: 'any', url: 'pasta.png'}].concat(select(items, function(o) { return { k: o.Food, c: o.Food, url: o.FoodPic } }));

if (gp.food) {
data = where(data, function (o) { return o.Food === gp.food });
}
},
Meals: function() {
if (gp.meals) {
data = where(data, function(o) {
// check that each of gp.meals is present in o.Meals

var mids = select(o.Meals, function(m) { return m.Id; });

var hasAll = true;
loop(gp.meals, function(mid) {
hasAll = hasAll && awef.vcont(mid, mids);
});

return hasAll;
});
}

// get data after querying this time, to filter the meals dropmenu as well
var distMeals = [];
var udict = {};
loop(data, function(o) {
loop(o.Meals, function(m) {
if (!udict[m.Id]) {
distMeals.push(m);
udict[m.Id] = 1;
}
});
});

frowData.Meals = select(distMeals, function(m) { return { k: m.Id, c: m.Name }; });
},
CountryName: function() {
frowData.CountryName = toKeyContent(distLstFunc('CountryName')(data), 'any');
if (gp.countryName) {
data = where(data, function(o) { return o.CountryName === gp.countryName });
}
},
ChefName: function(){
frowData.ChefName = toKeyContent(distLstFunc('ChefName')(data).sort(), 'any');
if (gp.chefName) {
data = where(data, function (o) { return o.ChefName === gp.chefName });
}
},
Date: function() {
frowData.Date = toKeyContent(distLstFunc('Year')(data).sort(), 'all years');
if (gp.date) {
data = where(data, function (o) { return o.Year.toString() === gp.date });
}
}
};

// apply rules in order
utils.applyFilters({
rules: filterRules,
gp: gp
});

function toKeyContent(arr, emptyn) {
var res = [];

if (emptyn) res.push({ k: '', c: emptyn });

awef.loop(arr, function (item) {
res.push({ k: item, c: item });
});

return res;
}

var model = utils.gridModelBuilder(
{
key: "Id",
gp: gp,
items: data,

// replace default group header value for Date column
getHeaderVal: { Date: function (o) { return o.DateStr; } },
tag: { frow: frowData }
});

return model;
}

$(function() {
$('#txtGridFrowSrc').keyup(function() {
$('#GridFrow').data('api').load();
});
});

function foodGetData() {
var tag = this.f.closest('.awe-grid').data('o').lrs.tg;
return tag.frow['Food'];
}
</script>
Awesome/DataController.cs
public IActionResult GetLunches()
{
return Json(Db.Lunches.Take(200).Select(o => new
{
o.Id,
o.Person,
o.Food,
o.FoodPic,
o.Location,
o.Price,
CountryName = o.Country.Name,
ChefName = o.Chef.FirstName + " " + o.Chef.LastName,
o.Date,
o.Date.Year,
DateStr = o.Date.ToShortDateString(),
Meals = o.Meals.Select(m => new { m.Id, m.Name }),
MealsStr = string.Join(", ", o.Meals.Select(m => m.Name))
}));
}

Grid with filter row multiple editors


We can add more than one editor in one filter cell, and in this demo we added additional operator dropdowns and clear all values in cell buttons. Custom css is used to make the Op dropdowns (operator equals/less than etc.) take less space in the cell. o-op class on the dropdowns tells filter row to ignore its value for adding o-hv class to the cell, so if you only select a value in an op dropdown (less than/equals etc.), but not in another editor in the cell (without o-op) you won't get the little box-shadow under the cell.
o-clrAll class tells the filter row that clicking on the button with this class should clear all input values in this cell.
Shared/Demos/GridFilterMultiClientData.cshtml
@{
var clrAll = "<button type='button' class='awe-btn o-clrAll awe-clrbtn'><i class='awe-icon awe-icon-x'></i></button>";
}
@(Html.Awe().Grid("GridFrowMulti")
.CssClass("compact")
.Height(390)
.ColumnWidth(200) // default min column width
.Reorderable()
.Resizable()
.DataFunc("loadLunchesGridMulti")
.Mod(o => o.Main().ColumnsAutohide().FilterRow(keyupsel: ".awe-txt:not(.awe-dtp)")) // Person textbox has .awe-txt css class
.Columns(
new Column {Bind = "Id", Width = 100 }.Mod(o => o.Filter("search:")),

new Column {Bind = "Person"}
.Mod(o => o.Filter(Html.Awe().TextBox("Person").Placeholder("Person").ClearButton())),
new Column { Bind = "Price" }
.Mod(o => o.Filter(Html.Awe().TextBox("Price").Placeholder("Price").Numeric())
.Filter(Html.Awe().Odropdown("PriceOp").CssClass("o-op").DataFunc("priceOpData"))
.Filter(clrAll)),

new Column { Bind = "Date", ClientFormat = ".(DateStr)", Width = 230 }
.Mod(o => o.Filter(Html.Awe().DatePicker("Date").Placeholder("Date").ChangeYear().ChangeMonth())
.Filter(Html.Awe().Odropdown("DateOp").CssClass("o-op").DataFunc("dateOpData"))
.Filter(clrAll)),

new Column { Bind = "CountryName", Header = "Country" }
.Mod(o => o.Filter(type: FilterType.Multichk, valProp: "Country", clearBtn: false)
.Filter(Html.Awe().Odropdown("CountryOp").CssClass("o-op").DataFunc("countryOpData"))
.Filter(clrAll)),

new Column { ClientFormat = ".(MealsStr)", Header = "Meals", Grow = 1.7 }

// define valProp, by default Column.Bind is used (which we don't have for this column)
.Mod(o => o.Filter(valProp:"Meals", type: FilterType.Multiselect)),

new Column { Bind = "ChefName", Header = "Chef"}))
<style>
.compact .o-op .o-cptn {
display: none;
}

.compact .o-op {
width: 2em;
flex-shrink: 0;
}

/*filter row cell with value*/
.compact .o-frow td.o-hv {
box-shadow: inset 0 -1px 0 #b1b2b2;
}
</style>
<script>
function priceOpData() {
return [{k: '', c: 'equals'},
{ k: '>', c: 'is greater' },
{ k: '<', c: 'is less' }];
}

function dateOpData() {
return [{ k: '', c: 'equals' },
{ k: '<', c: 'before' },
{ k: '>', c: 'after' }];
}

function countryOpData() {
return [{ k: '', c: 'include' },
{ k: 'exclude', c: 'exclude' }];
}

function loadLunchesGridMulti(sgp) {
// cache storage used by this demo (cstorg.js), it will load data on first call
// for no cache you can replace cstorg.get with $.get
return $.when(cstorg.get('@Url.Action("GetLunches", "Data")')).then(function(lunches) {
return getGridDataMulti(sgp, lunches);
});
}

function getGridDataMulti(sgp, lunches) {
var where = awef.where, select = awef.select, contains = awef.scont, loop = awef.loop;
var distLstFunc = utils.distLstFunc;
var jsonToDate = utils.toDate; // json date str to date
var gp = utils.getGridParams(sgp, ['country', 'meals']); // country, meals are arrays
var data = lunches;

// data used to populate some of the filter row dropdowns
var frowData = {};

// filter rules, will be applied in order ( gp.forder ),
// because we want to get the data for the dropdowns in order of filter application
var filterRules = {
Person: function() {
if (gp.person) {
data = where(data, function (o) { return contains(o.Person, gp.person) });
}
},
Price: function() {
if (gp.price) {
var op = function(o) { return awef.seq(o.Price, gp.price); }

if (gp.priceOp === '>') {
op = function (o) { return o.Price > gp.price; }
}
else if (gp.priceOp === '<') {
op = function (o) { return o.Price < gp.price; }
}

data = where(data, op);
}
},
Date: function () {
if (gp.date) {
var pdate = awem.toDate(gp.date); // parse str to date with datepicker default dateformat

var op = function (o) { return jsonToDate(o.Date).valueOf() === pdate.valueOf(); }

if (gp.dateOp === '>') {
op = function(o) {
return jsonToDate(o.Date) > pdate;
}
}
else if (gp.dateOp === '<') {
op = function (o) { return jsonToDate(o.Date) < pdate; }
}

data = where(data, op);
}
},
Meals: function() {
if (gp.meals) {
data = where(data, function(o) {
// check that each of gp.meals is present in o.Meals

var mids = select(o.Meals, function(m) { return m.Id; });

var hasAll = true;
loop(gp.meals, function(mid) {
hasAll = hasAll && awef.vcont(mid, mids);
});

return hasAll;
});
}

// get data after querying this time, to filter the meals dropmenu as well
var distMeals = [];
var udict = {};
loop(data, function(o) {
loop(o.Meals, function(m) {
if (!udict[m.Id]) {
distMeals.push(m);
udict[m.Id] = 1;
}
});
});

frowData.Meals = select(distMeals, function(m) { return { k: m.Id, c: m.Name }; });
},
Country: function() {
frowData.Country = toKeyContent(distLstFunc('CountryName')(data));

if (gp.country) {

// include
var op = function(o) {
var res = false;
loop(gp.country,
function(c) {
if (c === o.CountryName) res = true;
});

return res;
}

if (gp.countryOp === 'exclude') {
op = function(o) {
var res = true;
loop(gp.country,
function(c) {
if (c === o.CountryName) res = false;
});

return res;
}
}

data = where(data, op);
}
},
ChefName: function(){
frowData.ChefName = toKeyContent(distLstFunc('ChefName')(data).sort(), 'any');
if (gp.chefName) {
data = where(data, function (o) { return o.ChefName === gp.chefName });
}
}
};

// apply rules in order
utils.applyFilters({
rules: filterRules,
gp: gp
});

function toKeyContent(arr, emptyn) {
var res = [];

if (emptyn) res.push({ k: '', c: emptyn });

awef.loop(arr, function (item) {
res.push({ k: item, c: item });
});

return res;
}

var model = utils.gridModelBuilder(
{
key: "Id",
gp: gp,
items: data,

// replace default group header value for Date column
getHeaderVal: { Date: function (o) { return o.DateStr; } },
tag: { frow: frowData }
});

return model;
}
</script>
Awesome/DataController.cs
public IActionResult GetLunches()
{
return Json(Db.Lunches.Take(200).Select(o => new
{
o.Id,
o.Person,
o.Food,
o.FoodPic,
o.Location,
o.Price,
CountryName = o.Country.Name,
ChefName = o.Chef.FirstName + " " + o.Chef.LastName,
o.Date,
o.Date.Year,
DateStr = o.Date.ToShortDateString(),
Meals = o.Meals.Select(m => new { m.Id, m.Name }),
MealsStr = string.Join(", ", o.Meals.Select(m => m.Name))
}));
}

Grid filter row with server side filtering


Filter row using server side filtering, similar to the client grid filtering demo, except all the data filtering is done on the server.
GridDemo/Filtering.cshtml
@(Html.Awe().Grid("GridFrow2")
.Url(Url.Action("DinnersFilterGrid", "GridDemo"))
.Height(350)
.Reorderable()
.Mod(o => o.Main().FilterRow())
.Columns(
new Column{Bind = "Id", Width = 75, Groupable = false}
.Mod(o => o.Filter("search:")),

new Column{Bind = "Name"}
.Mod(o => o.Filter(Html.Awe().TextBox("Name").ClearButton())),

new Column{Bind = "Chef.FirstName,Chef.LastName", ClientFormat = ".(ChefName)", Header = "Chef"}
.Mod(o => o.Filter(valProp: "Chef")),

new Column{Bind = "Date"},

new Column { ClientFormat = ".(Meals)", Header = "Meal"}
.Mod(o => o.Filter(valProp: "Meal", type: FilterType.Multiselect)),

new Column{ Bind = "Organic"}))
Demos/Grid/GridDemoController.cs

public IActionResult DinnersFilterGrid(GridParams g, string[] forder, int? date, string name, int? chef, int[] meal, bool? organic)
{
forder = forder ?? new string[] { };
var query = Db.Dinners.AsQueryable();
var filterRules = new Dictionary<string, Action>();
var frow = new DinnerFrow();

filterRules.Add("Name", () =>
{
if (name != null)
{
query = query.Where(o => o.Name.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0);
}
});

filterRules.Add("Date", () =>
{
var list = new List<KeyContent>
{
new KeyContent("", "all years")
};

list.AddRange(AweUtil.ToKeyContent(query.Select(o => o.Date.Year).Distinct().OrderBy(o => o)));

frow.Date = list.ToArray();

if (date.HasValue)
{
query = query.Where(o => o.Date.Year == date);
}
});

filterRules.Add("Chef", () =>
{
frow.Chef = query.Select(o => o.Chef).Distinct().Select(o => new KeyContent(o.Id, o.FirstName + " " + o.LastName)).ToArray();

if (chef.HasValue)
{
query = query.Where(o => o.Chef.Id == chef);
}
});

filterRules.Add("Meal", () =>
{
if (meal != null)
{
query = query.Where(o => meal.All(mid => o.Meals.Select(cm => cm.Id).Contains(mid)));
}

// get data after querying this time, to filter the meals dropmenu as well
frow.Meal = query.SelectMany(o => o.Meals).Distinct().OrderBy(o => o.Id).Select(o => new KeyContent(o.Id, o.Name)).ToArray();
});

filterRules.Add("Organic", () =>
{
var list = new List<KeyContent> { new KeyContent("", "all") };
list.AddRange(query.Select(o => o.Organic).Distinct().Select(o => new KeyContent(o, o ? "Yes" : "No")));

frow.Organic = list.ToArray();

if (organic != null)
{
query = query.Where(o => o.Organic == organic);
}
});

// apply rules present in forder (touched by the user)
foreach (var prop in forder)
{
if (filterRules.ContainsKey(prop))
{
filterRules[prop]();
}
}

// apply the rest
foreach (var pair in filterRules.Where(o => !forder.Contains(o.Key)))
{
pair.Value();
}

return Json(new GridModelBuilder<Dinner>(query, g)
{
KeyProp = o => o.Id,
Map = MapToGridModel,
Tag = new { frow = frow }
}.Build());
}

Filter grid using parent controls


The grid can be filtered by adding parent controls to it, when a parent control triggers the change event, the grid (child) will reload and the parent values will be sent as parameters to the data function (or url in this case).
GridDemo/Filtering.cshtml
<div class="bar">
@Html.Awe().TextBox("txtPerson").Placeholder("search for person ...").CssClass("searchtxt")
@Html.Awe().TextBox("txtFood").Placeholder("search for food ...").CssClass("searchtxt")
@Html.Awe().AjaxRadioList("selCountry").DataFunc(Url.Action("GetCountries", "Data").CacheGetFunc()).Odropdown().HtmlAttributes(new { style = "min-width:15em;" })
</div>

@(Html.Awe().Grid("Grid1")
.Mod(o => o.Main())
.Columns(
new Column { Bind = "Id", Width = 75, Groupable = false, Resizable = false },
new Column { Bind = "Person" },
new Column { Bind = "Food", ClientFormatFunc = "imgFood", MinWidth = 200 },
new Column { Bind = "Country.Name", ClientFormat = ".(CountryName)", Header = "Country" },
new Column { Bind = "Date", Width = 120 }.Mod(o => o.Autohide()),
new Column { Bind = "Location" }.Mod(o => o.Autohide()),
new Column { Bind = "Chef.FirstName,Chef.LastName", ClientFormat = ".(ChefName)", Header = "Chef" })
.Url(Url.Action("GetItems", "LunchGrid"))
.Reorderable()
.Resizable()
.Height(350)
.Parent("txtPerson", "person")
.Parent("txtFood", "food")
.Parent("selCountry", "country"))
<script>
var root = '@Url.Content("~/Content/Pictures/Food/")';
function imgFood(itm) {
return '<img src="' + root + itm.FoodPic + '" class="food" />' + itm.Food;
}
</script>
Awesome/Grid/LunchGridController.cs
public class LunchGridController : Controller
{
public IActionResult GetItems(GridParams g, string person, string food, int? country)
{
food = (food ?? "").ToLower();
person = (person ?? "").ToLower();

var list = Db.Lunches
.Where(o => o.Food.ToLower().Contains(food) && o.Person.ToLower().Contains(person))
.AsQueryable();

if (country.HasValue) list = list.Where(o => o.Country.Id == country);

return Json(new GridModelBuilder<Lunch>(list, g)
{
KeyProp = o => o.Id,// needed for Entity Framework | nesting | tree | api
Map = o => new
{
o.Id,
o.Person,
o.Food,
o.FoodPic,
o.Location,
o.Price,
Date = o.Date.ToShortDateString(),
CountryName = o.Country.Name,
ChefName = o.Chef.FirstName + " " + o.Chef.LastName
}
}.Build());
}
}

Grid filtering using a popup to enter the search criteria


Using a popupForm and the grid api to filter the grid. The search criteria is built inside the popup, and when the Ok button is clicked, the post function executes, this is where the grid api.load function is called.
Note: we used aweui to initialize the popupForm, and a formBuilder util class to build its content (just like in the aweui grid filtering demo); an alternative would be to use the PopupForm helper and instead the formBuilder we could set the Url to point to an action that returns a partial view with the search criteria form.
GridDemo/Filtering.cshtml
<div class="bar">
<button type="button" class="awe-btn" onclick="awe.open('filterGrid', {}, event)">Filter</button>
<button type="button" class="awe-btn" onclick="clearFilter()">Clear Filter</button>
</div>
@(Html.Awe().Grid("GridPopupFilter")
.Mod(o => o.Main())
.Columns(
new Column { Bind = "Id", Width = 75, Groupable = false, Resizable = false },
new Column { Bind = "Person" },
new Column { Bind = "Food", ClientFormatFunc = "imgFood", MinWidth = 200 },
new Column { Bind = "Country.Name", ClientFormat = ".(CountryName)", Header = "Country" },
new Column { Bind = "Date", Width = 120 }.Mod(o => o.Autohide()),
new Column { Bind = "Location" }.Mod(o => o.Autohide()),
new Column { Bind = "Chef.FirstName,Chef.LastName", ClientFormat = ".(ChefName)", Header = "Chef" })
.Url(Url.Action("LunchGridFilter", "Data"))
.Reorderable()
.Resizable()
.Height(350))
<script>
var filterModel = {};

function clearFilter() {
filterModel = {};
filterGrid();
}

function filterGrid() {
var api = $('#GridPopupFilter').data('api');
api.load({
params: filterModel
});
}

$(function () {
aweui.initPopupForm({
id: 'filterGrid',
title: 'Filter grid',
height: 0,
dropdown: true,
loadOnce: true,
setCont: function (sp, o) {
var b = formBuilder({ model: filterModel, sp: sp });
b.add({ prop: 'person', func: aweui.txt });
b.add({ prop: 'food', func: aweui.txt });
b.add({
prop: 'country',
func: aweui.radioList,
opt: {
url: '@Url.Action("GetCountries", "Data")',
odropdown: true
}
});
b.add({ prop: 'location', func: aweui.txt });
b.add({ prop: 'date', func: aweui.datepicker });
b.add({
prop: 'chef',
func: aweui.lookup,
opt: {
getUrl: '@Url.Action("GetItem","ChefLookup")',
searchUrl: '@Url.Action("Search", "ChefLookup")'
}
});
b.applyToPopup(o);
},
postFunc: function (sp) {
var vm = utils.getParams(sp);
filterModel = vm;
filterGrid();
return {};
}
});
});
</script>
Awesome/DataController.cs
public IActionResult LunchGridFilter(
GridParams g,
string person,
string food,
string location,
int? country,
int? chef,
DateTime? date)
{
food = (food ?? "").ToLower();
person = (person ?? "").ToLower();
location = (location ?? "").ToLower();

var list = Db.Lunches
.Where(o => o.Food.ToLower().Contains(food)
&& o.Person.ToLower().Contains(person)
&& o.Location.ToLower().Contains(location))
.AsQueryable();

if (country.HasValue) list = list.Where(o => o.Country.Id == country);
if (chef.HasValue) list = list.Where(o => o.Chef.Id == chef);
if (date.HasValue) list = list.Where(o => o.Date == date);

return Json(new GridModelBuilder<Lunch>(list, g)
{
KeyProp = o => o.Id,// needed for Entity Framework | nesting | tree | api
Map = o => new
{
o.Id,
o.Person,
o.Food,
o.FoodPic,
o.Location,
o.Price,
Date = o.Date.ToShortDateString(),
CountryName = o.Country.Name,
ChefName = o.Chef.FirstName + " " + o.Chef.LastName
}
}.Build());
}

Filter by all columns

  you can search multiple columns at the same time (try 'pizza tavern')

Filtering by multiple columns at once from one textbox, you can type multiple keywords separated by space and the grid will get filtered.
The grid data is loaded once from the server after it is stored in the 'lunches' variable. The function defined in DataFunc can either return the grid model or a promise which will return the grid model later, and in this case on the first load we return a promise ($.get).
GridDemo/Filtering.cshtml
<div class="bar">
@Html.Awe().TextBox("txtsearch").Placeholder("search ...").CssClass("searchtxt")
<span class="hint">&nbsp; you can search multiple columns at the same time (try 'pizza tavern')</span>
</div>

@(Html.Awe().Grid("GridClientData")
.DataFunc("loadGridClientData")
.Height(350)
.Mod(o => o.Main())
.Resizable()
.Reorderable()
.Parent("txtsearch", "search", false)
.Columns(
new Column { Bind = "Id", Width = 75, Groupable = false, Resizable = false },
new Column { Bind = "Person" },
new Column { Bind = "Food" },
new Column { Bind = "Location" },
new Column { Bind = "Date", Width = 120 },
new Column { Bind = "CountryName", Header = "Country" },
new Column { Bind = "ChefName", Header = "Chef" }))
<script>
function loadGridClientData(sgp) {
// cache storage used by this demo (cstorg.js), it will load data on first call
return $.when(cstorg.get('@Url.Action("GetLunches", "Data")')).then(function(lunches) {
return getGridClientData(sgp, lunches);
});
}

function getGridClientData(sgp, lunches) {
var where = awef.where, contains = awef.scont, loop = awef.loop;
var gp = utils.getGridParams(sgp);
var list = lunches;

if (gp.search) {
var words = gp.search.toLowerCase().split(" ");

list = where(lunches, function (o) {
var matches = 0;
loop(words, function (w) {
if (contains(o.Food, w) || contains(o.Person, w) || contains(o.Location, w) || contains(o.CountryName, w)
|| contains(o.DateStr, w)
|| contains(o.ChefName, w)) matches++;
});

return matches === words.length;
});
}

function map(o) { return { Id: o.Id, Person: o.Person, Food: o.Food, Location: o.Location,
Date: o.DateStr, CountryName: o.CountryName, ChefName: o.ChefName }; };


return utils.gridModelBuilder(
{
key:"Id",
gp: gp,
items: list,
map:map,
// replace default group header value for Date column
getHeaderVal:{ Date: function(o){ return o.DateStr; } }
});
}

$(function() {
$('#txtsearch').keyup(function() {
$('#GridClientData').data('api').load();
});
});
</script>

Grid outside filter row


Reusing the same data source (url) as for the Grid with filter row (server side data), except in this demo we're using the controls outside of the grid to filter the demo.
Just like in the filter row demos the filter controls get their data from the grid model.
Shared/Demos/GridFilterOutside.cshtml
<div id="outfrow" class="frowpnl awe-il" style="margin-right: 1em">
@*context will add a prefix to all html ids*@
@using (Html.Awe().BeginContext("frowOfr"))
{

@*html input name is used to match filter rule name*@
<label>
Name:<br /> @Html.Awe().TextBox("Name").ClearButton().Placeholder("name")
</label>
<label>
Chef:<br /> @Html.Awe().Odropdown("Chef", o => o.ClearBtn()).DataFunc("filterData('Chef')")
</label>
<label>
Year:<br /> @Html.Awe().Odropdown("Date", o => o.ClearBtn()).DataFunc("filterData('Date')")
</label>
<label>
Meals:<br />
@(Html.Awe().AjaxCheckboxList("Meal")
.Multiselect(o => o.ClearBtn())
.DataFunc("filterData('Meal')"))
</label>
<label>
Organic:<br /> @Html.Awe().Odropdown("Organic", o => o.ClearBtn()).DataFunc("filterData('Organic')")
</label>
<label style="margin-right: 1em;">
<span> </span><br />
<button type="button" id="btnClearFilter" class="awe-btn">Clear filters</button>
</label>
}
<label class="nonflt">
<span>Show items as:</span> <br />
@Html.Awe().CheckBox("itemsType").Otoggl(o => o.No("Rows").Yes("Custom").Width("7em"))
</label>
</div>
<div class="frowpnl awe-il">

</div>
@(Html.Awe().Grid("GridFrowOfr")
.Url(Url.Action("DinnersFilterGrid", "GridDemo"))
.Height(350)
.Reorderable()
.Mod(o => o.Main())
.Columns(
new Column{Bind = "Id", Width = 75, Groupable = false},
new Column{Bind = "Name"},
new Column{Bind = "Chef.FirstName,Chef.LastName", ClientFormat = ".(ChefName)", Header = "Chef"},
new Column{Bind = "Date"},
new Column { ClientFormat = ".(Meals)", Header = "Meals", Grow = 2},
new Column{ Bind = "Organic"}))
<script>
$(function () {
var g = $('#GridFrowOfr');
var fcont = $('#outfrow');
var opt = {};

// reload each filter control when grid loads
g.on('aweload', function () {
$('#outfrow .awe-val').each(function () {
var api = $(this).data('api');
api && api.load && api.load();
});
});

// apply filters on control change
fcont.on('change', function (e) {
opt.o = g.data('o');

opt.inp = fcont.find('input').not('.nonflt input');
// instead of opt.inp we could set opt.cont = fcont; but this will also include the itemsType input
// and the grid would reload when we change the items type also

awem.loadgflt(opt);
});

$('#btnClearFilter').on('click', function () {
fcont.find('.awe-val').not('.nonflt input').each(function () {
var it = $(this).val('');
var api = it.data('api');
api && api.render && api.render();
// call api.render instead of change to load the grid only once
});

opt.inp = fcont.find('input').not('.nonflt input');
awem.loadgflt(opt);
});

// rows/custom switch
$('#itemsType').on('change', function() {
changeItms(g.data('o'), $(this).val() === 'true');
});
});

// get data for filter control from grid model
function filterData(name) {
return function () {
var g = $('#GridFrowOfr');
var o = g.data('o');
return awem.frowData(o, name);
}
}
</script>
<script>
// switch between rows and custom items render
function changeItms(o, custom) {
var api = o.api;

if (!o.initRen) {

o.initRen = {
ncon: api.ncon,
ghead: api.ghead,
itmf: api.itmf
};
}

var custview = {
// node content add wrap for padding
ncon: function (p) {
if (!p.lvl) return p.ren();
return '<div style="padding-left:' + p.lvl + 'em;" >' + p.ren() + '</div>';
},

// group header content
ghead: function (g) {
return api.ceb() + g.c;
},

// render item
itmf: function (opt) {
var columns = o.columns;

var content = '';
if (opt.con) {
content = opt.con;
} else {
for (var i = 0; i < columns.length; i++) {
var col = columns[i];

// is column hidden
if (api.ich(col)) continue;
content += '<div class="elabel">' + (col.H ? col.H + ': ' : '') + '</div>' + utils.gcvw(api, col, opt) + '</br>';
}
}

if (opt.ceb) {
opt.clss += ' cardhead';
opt.style += 'margin-left:' + opt.ind + 'em;';
} else {
opt.clss += ' itcard';
}

var attr = opt.attr;
attr += ' class="' + opt.clss + '"';
opt.style && (attr += ' style="' + opt.style + '"');

return '<div ' + attr + '>' + content + '</div>';
}
};

var tableCon = '<table class="awe-table"><colgroup></colgroup><tbody class="awe-tbody awe-itc"></tbody></table>';
var itmCon = '<div class="awe-itc"></div>';

if (custom) {
o.v.find('.awe-tablw').html(itmCon);
api = $.extend(api, custview);
// ignore columns width for grid content
o.syncon = 0;

// no alt rows
o.alt = 0;

// no fixed height
o.h = 0;
} else {
o.v.find('.awe-tablw').html(tableCon);
api = $.extend(api, o.initRen);
o.syncon = 1;
o.alt = 1;
o.h = 350;
}

o.api.initLayout();
o.api.render();
}
</script>
<style>
.frowpnl {
padding: 1em 0;
}

.frowpnl label {
vertical-align: top;
max-width: 20em;
}
</style>
Demos/Grid/GridDemoController.cs

public IActionResult DinnersFilterGrid(GridParams g, string[] forder, int? date, string name, int? chef, int[] meal, bool? organic)
{
forder = forder ?? new string[] { };
var query = Db.Dinners.AsQueryable();
var filterRules = new Dictionary<string, Action>();
var frow = new DinnerFrow();

filterRules.Add("Name", () =>
{
if (name != null)
{
query = query.Where(o => o.Name.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0);
}
});

filterRules.Add("Date", () =>
{
var list = new List<KeyContent>
{
new KeyContent("", "all years")
};

list.AddRange(AweUtil.ToKeyContent(query.Select(o => o.Date.Year).Distinct().OrderBy(o => o)));

frow.Date = list.ToArray();

if (date.HasValue)
{
query = query.Where(o => o.Date.Year == date);
}
});

filterRules.Add("Chef", () =>
{
frow.Chef = query.Select(o => o.Chef).Distinct().Select(o => new KeyContent(o.Id, o.FirstName + " " + o.LastName)).ToArray();

if (chef.HasValue)
{
query = query.Where(o => o.Chef.Id == chef);
}
});

filterRules.Add("Meal", () =>
{
if (meal != null)
{
query = query.Where(o => meal.All(mid => o.Meals.Select(cm => cm.Id).Contains(mid)));
}

// get data after querying this time, to filter the meals dropmenu as well
frow.Meal = query.SelectMany(o => o.Meals).Distinct().OrderBy(o => o.Id).Select(o => new KeyContent(o.Id, o.Name)).ToArray();
});

filterRules.Add("Organic", () =>
{
var list = new List<KeyContent> { new KeyContent("", "all") };
list.AddRange(query.Select(o => o.Organic).Distinct().Select(o => new KeyContent(o, o ? "Yes" : "No")));

frow.Organic = list.ToArray();

if (organic != null)
{
query = query.Where(o => o.Organic == organic);
}
});

// apply rules present in forder (touched by the user)
foreach (var prop in forder)
{
if (filterRules.ContainsKey(prop))
{
filterRules[prop]();
}
}

// apply the rest
foreach (var pair in filterRules.Where(o => !forder.Contains(o.Key)))
{
pair.Value();
}

return Json(new GridModelBuilder<Dinner>(query, g)
{
KeyProp = o => o.Id,
Map = MapToGridModel,
Tag = new { frow = frow }
}.Build());
}



Comments