- Notifications
You must be signed in to change notification settings - Fork0
EasyFilter is a lightweight ☁️, just one dependency 🚢, minimal setup 😮, intuitive 😃 and powerful 💪 filter for all your filter needs.
License
Noriller/easy-filter
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
🎈 Welcome toEasyFilter! 👋
EasyFilter is a lightweight ☁️, just one dependency 🚢, minimal setup 😮, intuitive 😃 and powerful 💪 filter for all your filter needs.
It's as easy as this:
constfilter=EasyFilter(sourceArray)constfilteredResult=filter.search('your query')
The one dependency isEasyFilterParser that was part of this package before. 😉
npm install @noriller/easy-filter
yarn add @noriller/easy-filter
importEasyFilterfrom'@noriller/easy-filter'
constEasyFilter=require('@noriller/easy-filter')
constfilter=EasyFilter(sourceArray)constfilteredResult=filter.search('your query')
That's it! 🧙♂️
Check out the sectionEasyFilter Operators to see all that you can pass to the filter, the real ✨magic✨ is there!
✨Magicliketurningthis:`search for something "this between quotes" and then here:"you search for this"`✨Intosomethingthatworksforsinglevalues,quotedvaluesandevenvaluesnestedinsidekeys.ANDMORE!✨
Ok. If you need more options, here's the full setup you can do using all options available:
constfilter=EasyFilter(sourceArray,{filterOptions:{dateFormat:'DD-MM-YYYY',normalize:true,indexing:true,limit:10,},tagAliases:{tag:['tag1','tag2','tag3'],}})constfilteredResult=filter.search('your query')
It's still that simple. 👨💻
All the options will be explained inEasyFilter Options.And most of them you can pass in thesearch
🔎 string when you need.
In corporate scenarios, sometimes we have too much information 😵. We make pages with endless columns and if we need to filter that data, we either use something generic likeObject.keys(object).join(' ').includes('string')
or we have to make a custom search... for. each. table. 😫
Meanwhile I saw awesome (and probably custom solutions) in things we use everyday.
Check out the ones I was aiming for 🌟:
- Github
- Stackoverflow
- Gmail/Google Search
In the latter, users can use their UI to create their queries while powerusers can just type that and much more.
I too needed to provide a way to users to filter the data and ended up settling at a simpler version of this project. Mostly because of all the solutions I was able to find were neither user or developer friendly. 😢
(Also a little rant:search
,filter
,matcher
and the like are a nightmare to search for... too many hits and too little relevant results 😕)
This is what I'm trying to offer here: a powerful engine to make your queries. 😎👍
Then it's up to you to offer a UI for what makes sense for your data. And it's still intuitive for common users and powerful for powerusers.
Unless searching and filtering data IS your business... each table, each data source you need to filter based on any criteria could be another source of frustration. 👨🏭
While sometimes this can be easy asdataSource.filter(x => x.attribute === 'something')
, usually it's more complicated than that.
And when you have to factor what the users might want at any given time, it can become a nightmare. 😫
And if you're seeing yourself here... then you probably should use it.
If something like
Object.keys(object).join(' ').includes('string')
ordataSource.filter(x => x.attribute === 'something')
is enough for your need, you probably wouldn't want to bother using it.EasyFilter is not a fuzzy filter (at least not yet, who knows? 🤔), so if you're expecting clumsy typers searching your data... they might have a hard time.
EasyFilter also don't care about upper or lowercase and/or ordering of the words in your queries (again: at least not yet), so if that's important... I'm sorry? 🙇♂️
This one is a little relative: if you need maximum performance... well, test it out if it work for you! The more options you use and the more your data objects branches out on objects upon objects... the more time it needs to traverse everything. (And even then, I believe you can use it for prototyping and/or as a crutch while you make your own custom filter. Don't worry, I understand. 😉)
The trade off is clear: we give you a powerful engine that will return the data following what you're searched for, but it comes at a cost.
For your everyday use, you're probably fine and your users will love it. 😎
(As a side note, I would love to know how EasyFilter fare against any other solution you might try. ❤😍❤)
Most of this should be intuitive for most users... that's what I was aiming for after all. 🧐
Any word or operators are, primarily and lastly, treated asOR
queries.
To be a match, the data needs only to match any of them to be returned.
filter.search('word1 word2 tag:value "quoted value"')
word1
,word2
,tag:value
and"quoted value"
each become separated entities and a match of any one of those will return.
Anything inside quotes (either double"
or single'
) will be treated as one entity.
EasyFilter relies heavily on recursion and this one entity will be split into multiple entities, following those entities rules.
To be a match, the data must get a match from each subquery inside quotes.
AnAND
query can contain:OR
,TAG
and even nestedAND
queries.
In case of nestedTAG
andAND
queries, the nested quote must not match the parent quote.
filter.search('"quoted value tag:value"')
quoted
,value
andtag:value
first are anAND
query and will match only if all of the subqueries match.
In this case, bothquoted
andvalue
becomeOR
queries andtag:value
becomesTAG
query.
TAG here is equivalent to anykey
of a Javascript object.
TAG
, likeAND
queries, return subqueries where they target a slice of the object, namely thekey
.
To be a match, the data must get a match from the subquery after theTAG
.
ATAG
query can contain:OR
,AND
and thenNULL
andRANGE
/DATE_RANGE
queries.
TAG
doesn't support nestedTAG
queries. (As of now.)
filter.search('tag:value')
Just theTAG
followed by a colon and thevalue
.
value
in this example will become anOR
query.
filter.search('tag:(value1 value2 value3)')
By using brackets, you can have anOR
query with multiple values at once.
filter.search('tag:"value1 value2 value3"')
By using quotes (single/double), you can have anAND
query.
filter.search('tag:null tag:nil tag:none tag:nothing')
By passing, alone, any of the words:NULL
,NIL
,NONE
orNOTHING
as the value of theTAG
, it will match only if thekey
doesn't exist in the object (or if the key isnull
orundefined
).
tag:(nothing)
, in contrast, will match only if thekey
contains the string "nothing".
filter.search('tag.subTag.thirdTag:value')
You can chain tags together using a.
(full stop/period).
This would be equivalent to nestedTAGs
. (Nested tags aren't supported.)
filter.search('tag.0:value tag2.*.subTag:value')
While the most common use case for EasyFilter would be, indeed, common objects (think JSON Objects with key/value pairs), arrays are supported.
The main difference is that thekey
they use are numerical and ordered.
tag.0:value
will search inside the "tag" key, then in the element "0" for the "value".
tag2.*.subTag:value
will search inside the "tag2" key, then in ALL elements for the "subTag" and then for the "value".
So, if the position in the array matters, use the index. Otherwise, use*
(asterisk) to search all elements in the array.
filter.search('tag:range(0,5)')
By passing, alone, the operatorRANGE()
you can pass one or two arguments that will filter based on the numbers.
RANGE
can only be used insideTAG
and withnumber
values.
The first argument is the lower bound (-Infinity
) and the second argument is the upper bound (Infinity
).
Passing only one argument sets only the lower bound. To set only the upper bound, pass it empty:RANGE(,5)
.
filter.search('tag:dateRange(2020-05-01, 2021-09-05)')
By passing, alone, the operatorDATERANGE()
you can pass one or two arguments that will filter based on the dates.
DATERANGE
can only be used insideTAG
and withdate
values.
The first argument is the lower bound (0000-01-01
) and the second argument is the upper bound (9999-01-01
).
Passing only one argument sets only the lower bound. To set only the upper bound, pass it empty:DATERANGE(,2021-09-05)
.
More on acceptedDate Formats
inDate Format (Query), but you can use all the common formats likeDD/MM/YYYY
,MM/DD/YYYY
andYYYY/MM/DD
as long as you pass it as anOPTION
. If noDate Format
is provided, the Javascript default implementation ofnew Date('your date string')
will be used.
By nesting any and multiple queries inside the syntaxNOT()
you can invert those and it will NOT return anything that matches.
If there's a match in theNOT
query, it won't return even if there's a match in other queries.
If your query contains onlyNOT
queries, it will return everything that don't have a match.
When combining with other queries,NOT
queries will filter out matches from those.
ANOT
query can contain:OR
,AND
andTAG
queries.
AllNOT
are parsed at the same level, nesting it inside other queries will just remove them from the query.
filter.search('not("quoted value tag:value")')
Any element withquoted
,value
andtag:value
will not be returned.
There's three types of options:
- Those that can be passed any time:
- Those that can only be passed in the setup:
- Those that can only be passed with the query:
Using the syntaxOPTION()
orOPTIONS()
you can pass the following options inside your search string.
TheOPTION
keyword is parsed first, it will be just removed if nested in other queries and anything else inside will be either parsed as an option or ignored.
When passed as anOPTION
,DateFormat
will be used to parse the dates used inDATE_RANGE
.
This way your users can use their locale date format in their query.
When usingDATE_RANGE
, if noDateFormat
is passed as an option the Javascript default implementation ofnew Date('your date string')
will be used.
The formats can be:YYYY-MM-DD
,DD-MM-YYYY
andMM-DD-YYYY
while the separators can be:-
,.
,,
and/
.
filter.search('tag:dateRange(30-12-2020,30-12-2022) option(dateFormat:DD.MM.YYYY)')
When theNORMALIZE
option is used,EasyFilter
will discard/ignore every and all diacritics when making comparisons. It's FALSE by default.
This means that withNORMALIZE
:Crème brûlée
is equal toCreme brulee
.
EasyFilter
uses thestring.normalize('NFD')
javascript API to decompose the strings and then remove allCombining Diacritical Marks.
NORMALIZE
uses aboolean
flag, and when used inOPTIONS
alone likeoption(normalize)
it will assume the TRUE value, but you can explicitly use:normalize:true
.
You can also usenormalize:false
to disable asetup default normalization for a specific query.
When theINDEXING
option is used,EasyFilter
will use a numerical ranking system and if there's a match, it will return a copy of the element with an additional key_EasyFilterIndex
that will have a "relevance score" that you can use to sort the results. It's FALSE by default.
INDEXING
uses aboolean
flag, and when used inOPTIONS
alone likeoption(index)
oroption(indexing)
it will assume the TRUE value, but you can explicitly use:index:true
.
You can also usenormalize:false
to disable asetup default indexing for a specific query.
EasyFilter
gives a number based on the types of queries and multiplies the results returned by nested queries.
- OR = 1
- AND = 3
- TAG = 5
- RANGE/DATE_RANGE/NULL = 10
This way, using more generic queries, those with the more specific parameters will have a higher indexing value.
When theLIMIT
option is used,EasyFilter
will return only theLIMIT
number of results. It's Zero/FALSE by default.
LIMIT
needs anumber
value, when used inOPTIONS
you need to also pass anumber
value:option(limit:1)
.
You can also uselimit:0
to disable asetup default limit for a specific query.
In the setup you may pass:
The following options works the same way as if passing in the query:
By passing it in the setup, they will be used in everysearch
.
When passed in theSetup
,DateFormat
will be used to parse the dates in yourSource Array
.
If noDateFormat
is passed in thesetup
, the Javascript default implementation ofnew Date('your date string')
will be used.
If that default implementation wouldn't work with yoursource array
, then provide aDateFormat
.
The formats can be:YYYY-MM-DD
,DD-MM-YYYY
andMM-DD-YYYY
while the separators can be:-
,.
,,
and/
(you can use the provided typing).
PassTAG Aliases
in the setup to expose to users more friendly (or broader) terms that they can call your data usingTAG
.
Tag Aliases
should be a dictionary withkey
/value
pairs where thekey
is what your users can use and thevalue
is a array of strings that will refer to your actual data.
Ourdata sources
might not always be the most user friendly, or something important might be nested where usersTag Aliases
couldn't possibly know. This is where you useTag Aliases
.
constfilter=EasyFilter(sourceArray,{tagAliases:{// if you want more friendly aliasesdata:['DT_0001X420'],name:['nm_first','nm_last'],// if the important data is nestedage:['person.info.age'],// if your users expect to find everything related to a wordaddress:['address','city','country','province','zip_code'],// and you have no idea which words they will search for// just create multiple aliases with the same tagscity:['address','city','country','province','zip_code'],country:['address','city','country','province','zip_code'],province:['address','city','country','province','zip_code'],zip:['address','city','country','province','zip_code'],location:['address','city','country','province','zip_code'],where:['address','city','country','province','zip_code'],position:['address','city','country','province','zip_code'],}})
Here's something you can expect in the future:
- New objective:
EasyFilter
will now become a trilogy! They will all share the same parser, so you will be able to filter values already buffered and values in your databases all the same, simple way.- This, which filter values in objects.
EasyFilter-SQL
- That will create SQL queries.EasyFilter-Mongo
- That will create Mongo queries.
- (TBD) Streams support ⚡: be it from an API or from a data source, data usually comes as a stream. If the Javascript Engine can handle streaming fine, why wait for it to buffer everything?
Either if you're encountered a problem: 😢 or if you're have an idea to make it better: 🤩
Feel free to contribute, to open issues, bug reports or just to say hello! 🤜🤛
In case of bugs or errors, if possible, send an example of your data, of the query you're using and what you've expected.
Since it supports any kind of data or queries... who knows what can happen?
I do have one remark to say: It will probably err on the side of caution... maybe returning more than you might have expected and when using the negation query, filtering more than intended, so keep that in mind.
About
EasyFilter is a lightweight ☁️, just one dependency 🚢, minimal setup 😮, intuitive 😃 and powerful 💪 filter for all your filter needs.