Introduction
This article will explain a simple client-side filtering framework that can be implemented in any page to obtain easy-to-use filters.
Background
I was looking for some controls that could help me create my application. The control which suited me the most was jqGrid - a jQuery plugin. jqGrid displays a JSON data source in tabular format, along with filtering and sorting. For this, we must define a columns model (see example) - in JSON format of course:
colModel:[
{name:'id',index:'id', width:60, sorttype:"int"},
{name:'name',index:'name', width:100, editable:true},
{name:'note',index:'note', width:150, sortable:false}
]
Using the Code
Unfortunately, the filtering capability of the jqGrid is not as complex as I need. A filter is a JSON object with the following format type:
{ "groupOp": "and",
"rules": [
{ "field": "name", "op": "eq", "data": "Romania" },
{ "field": "id", "op": "le", "data": "1"}
]
}
It's basically a set of rules grouped together using a logical operator: "OR
" or "AND
". For a simple search, this filter can be enough, but anything more complex requires a tree of such groups. So let's add an array of groups to this structure. Now we have the tree structure that we need and still maintain the support for normal jqGrid filters. Suppose we would need a filter like this:
(name == "John" AND age > 30) OR (name == "Doe" AND age < 30) OR name == "Joe"
Note first that between groups and also between groups and rules, we must have the same logical operator, otherwise we will have to isolate them in a subgroup. The above formula will map into:
{ "groupOp": "or",
"groups": [
{ "groupOp": "AND",
"rules": [
{ "field": "name", "op": "eq", "data": "John" },
{ "field": "age", "op": "gt", "data": "30" }
]
},
{ "groupOp": "AND",
"rules": [
{ "field": "name", "op": "eq", "data": "Doe" },
{ "field": "age", "op": "lt", "data": "30" }
]
}
]
"rules": [{ "field": "name", "op": "eq", "data": "Joe" }]
}
As I said, we have two types of operators: group operators and rule operators. The only group operators are "OR
" and "AND
". As rule operators, we have: eq (equal to), us (different to), lt (lower than), etc. as well as string operators: cn (contains), sw (starts with), etc. The above can be easily converted into a syntax closer to the JavaScript, by using functions:
(eq(item.name, "John") && gt(item.age, "30")) ||
(eq(item.name, "Doe") && lt(item.age, "30")) || eq(item.name, "Joe")
Note that all entity fields have are prefixed with "item.
". Let's define the necessary JavaScript functions:
function eq(d1, d2) { return d1 == d2; }
function lt(d1, d2) { return d1 < d2; }
function gt(d1, d2) { return d1 > d2; }
Now we can loop through a JSON object and evaluate the above formula.
var jsonItems = [
{"name": "John", "age": "18"},
{"name": "Amanda", "age": "21"},
{"name": "Dave", "age": "31"}
];
var expression = '(eq(item.name, "John") && gt(item.age, "30")) ||
(eq(item.name, "Doe") && lt(item.age, "30")) || eq(item.name, "Joe")';
var newJsonItems = [];
for(var index = 0; index < jsonItems.length; index++){
var item = jsonItems[index];
if(eval(expression) == true){
newJsonItems.push(item);
}
}
So far we have logic, but we need a web user interface to cofigure the filter. Luckily I wrote a JS class that does this: xFilter
(see attachment). To use it, you only need to provide the DOM parent element and columns model.
var colModel = [
{ "name": "id"},
{ "name": "name"},
{ "name": "note"}
];
var items = [
{ "name": "Romania", "id": "1", "note": "ro" },
{ "name": "Franta", "id": "2", "note": "fr"},
{ "name": "Anglia", "id": "3", "note": "uk"},
{ "name": "Italia", "id": "4", "note": "it" },
{ "name": "Germania", "id": "5", "note": "ge" }
];
var f = new xFilter(document.getElementById("fil"),
{
columns: colModel,
onchange: function() {
document.getElementById("message").innerHTML =
this.toUserFriendlyString();
var resItems = f.Apply(items);
var s = "";
for (var i = 0; i < resItems.length; i++) {
s += "," + resItems[i].name;
}
document.getElementById("message").innerHTML = s;
}
});
As you can see, the class has the "onchange
" event that is triggered whenever a change to the filter occurs. Of course, one can use the method "Apply
" - whose logic is presented above - to filter a JSON object.
Conclusion
Hope you enjoyed this. The next article will take this model on the server side (C#), where the filters we've created will become expression trees that can be used with LINQ implementations.
Link
Part 2 of creating a dynamical filtering mechanism in C#