- Make sure to specify the data type in kendoGrid's schema model, especially for numbers, leave it empty may result value will be treated as a string.
- There is no "in" operator in kendo, instead array of filters with "eq" operator will be implemented.
- If filter has "filters" field (nested filter) that is not empty, the value inside "filters" will be used instead.
- Working with date may need additional effort to manipulate the data, if we just pass "2019-01-01 00:00:00.000Z", we will only get exactly the date with that specific time.
- Build for MongoDB, other DB may need some adjustments
- Filter:
- ToDBOXFilter
- ToAggregateFilter
- Sort:
- ToDBOXSort
- ToAggregateSort
payload := struct{
...
Filter kendohelper.Filter
Sort kendohelper.Sort
...
}{}
if err := k.GetPayload(&payload); err != nil {
return err
}
...
query := tk.M{
"where": payload.Filter.ToDBOXFilter(), // return *dbox.Filter
"order": payload.Sort.ToDBOXSort(), // return []string
}
...
pipe := []tk.M{
tk.M{
"$match": payload.Filter.ToAggregateFilter(), // return toolkit.M
},
tk.M{
"$sort": payload.Sort.ToAggregateSort(), // return bson.D
}
}
- Filter:
- HandleField
- Handle
- Sort:
- HandleField
- Handle
Before calling The Basic Func, we might want to refactor the struct using these functions first.
Note: There are always two ways to reconstruct the struct, one is on JS (frontend, before the data is being sent to the server) and the second one is on Go (backend). Choose which one is making more sense to you, based on the given context.
Examples:
payload.Filter.HandleField(strings.ToLower)
payload.Sort.HandleField(strings.ToLower)
handler := func(field string) string {
if field == "name" {
return "fullname"
}
return field
}
payload.Filter.HandleField(handler)
payload.Sort.HandleField(handler)
Sometimes, we may have very dynamic columns from aggregate's result that only show when value > 0 for example.
So instead of
fieldName: {$eq: 0} // will not retrieve any data cz the field itself is not exist
We might want to change it to
fieldName: {$exists: false}
To do that we might want to use Handle Func
payload.Filter.Handle(func(filter kendohelper.Filter) kendohelper.Filter {
if len(filter.Filters) == 0 {
value, ok := filter.Value.(float64)
if ok && value == 0 && filter.Operator == "eq" {
filter.Value = tk.M{"$exists": false}
}
}
return filter
})
Handling this scenario on backend is making more sense because the frontend shouldn't know how the filter is actually working on backend.
Those are just sample scenarios. The Handle Func also be used as field validation such as which fields are allowed to be filtered which fields are prohibited and so on and so on.
To prevent some restricted fields from being filtered, we can just make the operator empty, unrecognized operator will make that filter unprocessed.
payload.Filter.Handle(func(filter kendohelper.Filter) kendohelper.Filter {
if filter.Field == "commission_fee" {
filter.Operator = ""
}
return filter
})
To prevent some restricted fields from being sorted, we can make the Field and Dir to be an empty string.
payload.Sort.Handle(func(sortElem kendohelper.SortElem) kendohelper.SortElem {
if sortElem.Field == "commission_fee" {
sortElem.Field = ""
sortElem.Dir = ""
}
return sortElem
})
For example, we have field named created_at that has type of timestamp on mongo collection. To query the date that equals to 2019-01-01 between 00:00:00 to 23:59.59. We could reconstruct it like this:
On parameterMap, change the data from this:
data.filter: {
field: "created_at",
operator: "eq",
value: "2019-01-01 00:00:00.000Z"
}
Into this:
data.filter: {
field: "created_at", // will be ignored
operator: "eq", // will be ignored
value: "2019-01-01 00:00:00.000Z", // will be ignored
filters: [{
field: "created_at",
operator: "gte",
value: "2019-01-01 00:00:00.000Z"
},
{
field: "created_at",
operator: "lt",
value: "2019-01-02 00:00:00.000Z"
}],
logic: "and"
}
Filter{
Filters: []Filter{
...
Filter{
Field: "created_at",
Operator: "gte",
Value: "2019-01-01 00:00:00.000Z",
},
Filter{
Field: "created_at",
Operator: "lt",
Value: "2019-01-02 00:00:00.000Z",
},
},
Logic: "and",
}
How to do it on Go? We can use Handle Func:
payload.Filter.Handle(func(filter kendohelper.Filter) kendohelper.Filter {
if len(filter.Filters) == 0 {
if filter.Field == "created_at" {
valueStr, ok := filter.Value.(string)
if !ok {
return filter
}
t, err := time.Parse(time.RFC3339, valueStr)
if err != nil {
return filter
}
filter.Filters = []kendohelper.Filter{
kendohelper.Filter{
Field: filter.Field,
Operator: "gte",
Value: filter.Value,
},
kendohelper.Filter{
Field: filter.Field,
Operator: "lt",
Value: t.AddDate(0, 0, 1).Format(time.RFC3339),
},
}
filter.Logic = "and"
}
}
return filter
})
- Filter
- DeepClone
- HasField
- Sort
- DeepClone
- HasField
Filter has field "Filters" which is slice of Filter. In go, values of slice are passed by reference, to avoid making changes to the original Filter, use DeepClone instead
Examples:
// Let's say we we want to rename field in new filter (cloned filter) with this handler without affecting the original
handler := func(field string) string {
if field == "name" {
return "clientdoc.name"
}
return field
}
// DON'T DO:
newFilter := payload.Filter
newSort := payload.Sort
newFilter.HandleField(handler) // payload.Filter will also be affected
newSort.HandleField(handler) // payload.Sort will also be affected
// INSTEAD DO:
newFilter := payload.Filter.DeepClone()
newSort := payload.Sort.DeepClone()
newFilter.HandleField(handler) // completely isolated, won't affect payload.Filter
newSort.HandlerField(handler) // // completely isolated, won't affect payload.Sort
Note: These functions might be useful for example if we have Filter from kendo grid which its data is generated from an aggregate data (link to multiple collections/tables)
Examples:
isFilterHasField := payload.Filter.HasField("Name", "Nationality") // return bool
// isFilterHasField will be true if any Field in Filter equals to any of the input fields (including nested Filter). Otherwise it's false
isSortHasField := payload.Filter.HasField("Name", "Nationality") // return bool
// isFilterHasField will be true if any Field in Sort equals to any of the input fields. Otherwise it's false
since 2.0.2
- Filter:
- DeepCopyTo
// DON'T DO:
newFilter := payload.Filter
newFilter.HandleField(strings.ToLower) // payload.Filter will also be affected
// INSTEAD DO:
newFilter := kendofilter.Filter{}
payload.Filter.DeepCopyTo(&newFilter)
newFilter.HandleField(strings.ToLower) // completely isolated, won't affect payload.Filter