kendohelper

package module
v0.0.0-...-920ea58 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 14, 2020 License: MIT Imports: 4 Imported by: 0

README

kendohelper v2

kendohelper kendohelper kendohelper

IMPORTANT NOTES:
  1. 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.
  2. There is no "in" operator in kendo, instead array of filters with "eq" operator will be implemented.
  3. If filter has "filters" field (nested filter) that is not empty, the value inside "filters" will be used instead.
  4. 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.
  5. Build for MongoDB, other DB may need some adjustments

Getting Started

The Basic Func
  • Filter:
    • ToDBOXFilter
    • ToAggregateFilter
  • Sort:
    • ToDBOXSort
    • ToAggregateSort
Preparing payload:
payload := struct{
    ...
    Filter       kendohelper.Filter
    Sort         kendohelper.Sort
    ...
}{}

if err := k.GetPayload(&payload); err != nil {
    return err
}
Use in find query:
...
query := tk.M{
    "where": payload.Filter.ToDBOXFilter(), // return *dbox.Filter
    "order": payload.Sort.ToDBOXSort(),     // return []string
}
Use in pipe command:
...
pipe := []tk.M{
    tk.M{
            "$match": payload.Filter.ToAggregateFilter(), // return toolkit.M
    },
    tk.M{
            "$sort": payload.Sort.ToAggregateSort(),      // return bson.D
    }
}
The Handle Func
  • 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:

Treat the fields as lower case
payload.Filter.HandleField(strings.ToLower)
payload.Sort.HandleField(strings.ToLower)
Rename specific field to something else (field aliasing)
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.


More Examples

Filter Validation

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
})
Working with date

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:

JS side:

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"
}
On Go side would looks like this:
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
})
The Additional Func
  • Filter
    • DeepClone
    • HasField
  • Sort
    • DeepClone
    • HasField
Copy Filter or Sort deeply to a new variable

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)

Check deeply if Filter contains some fields

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
In Compatibility mode

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


Thanks to surya and radit for supporting this project, any (usually unecessary) talk means a lot. :D © 2019-2020

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultDBOXFilter

func DefaultDBOXFilter() *dbox.Filter

DefaultDBOXFilter is the default value of ToDBOXFilter() when no filter is generated. It avoids (panic) nil pointer dereference or empty dbox.Filter{} as result. The idea is, if filter is empty, the query will continue to show data. You could also use this variable to check, whether to use the output as filter or not.

Types

type Filter

type Filter struct {
	Field    string
	Operator string
	Value    interface{}
	Filters  []Filter
	Logic    string
}

Filter is Kendo filter's object structure.

func (*Filter) DeepClone

func (f *Filter) DeepClone() Filter

DeepClone will clone filter deeply as a branch new Filter

func (*Filter) DeepCopyTo

func (f *Filter) DeepCopyTo(dest *Filter)

DeepCopyTo will copy filter as a branch new Filter to dest Filter. [Compability mode with previous version, it's recommended to use DeepClone]

func (*Filter) Handle

func (f *Filter) Handle(handler FilterHandleFunc)

Handle handles refactoring Filter struct

func (*Filter) HandleField

func (f *Filter) HandleField(handler func(field string) string)

HandleField refactoring specifically on fields inside Filter struct

func (*Filter) HasField

func (f *Filter) HasField(fields ...string) bool

HasField checks whether filter contains specific field name

func (*Filter) ToAggregateFilter

func (f *Filter) ToAggregateFilter() toolkit.M

ToAggregateFilter converts Filter to Mongo Pipeline $match (aggregation). Querying a string, except for "eq" and "neq", is case-insensitive

func (*Filter) ToDBOXFilter

func (f *Filter) ToDBOXFilter() *dbox.Filter

ToDBOXFilter converts Filter to *dbox.Filter{}. Querying a string, except for "eq" and "neq", is case-insensitive

type FilterHandleFunc

type FilterHandleFunc func(Filter) Filter

The FilterHandleFunc type is an adapter to allow the use of the Filter's Handler

type Sort

type Sort []SortElem

Sort is Kendo sort's array structure.

func (*Sort) DeepCopy

func (s *Sort) DeepCopy() Sort

func (*Sort) Handle

func (s *Sort) Handle(handler SortHandlerFunc)

Handle handles refactoring the struct before execute the ToDBOXSort or ToAggregateSort func

func (*Sort) HandleField

func (s *Sort) HandleField(handler func(field string) string)

HandleField handles refactoring specifically on fields inside Sort struct

func (*Sort) HasField

func (s *Sort) HasField(fields ...string) bool

func (*Sort) ToAggregateSort

func (s *Sort) ToAggregateSort() bson.D

ToAggregateSort converts Sort to bson.D (Ordered Map) used in Mongo Pipeline $sort (aggregation)

func (*Sort) ToDBOXSort

func (s *Sort) ToDBOXSort() []string

ToDBOXSort converts Sort to []string of sort by ordered field

type SortElem

type SortElem struct {
	Field string
	Dir   string
}

SortElem is element of Kendo's sort array

type SortHandlerFunc

type SortHandlerFunc func(SortElem) SortElem

The SortHandlerFunc type is an adapter to allow the use of the Sort's Handler

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL