Skip to content

Instantly share code, notes, and snippets.

@nh2
Last active December 23, 2015 10:39
Show Gist options
  • Save nh2/6622759 to your computer and use it in GitHub Desktop.
Save nh2/6622759 to your computer and use it in GitHub Desktop.
Javascript serializable object filters. Filters a list of objects, with easy selection of the field and value to filter on using regexes.
<!-- An examples of how filter tags can be visualized, here in Angular.js -->
<div class="filters">
<li class="filterTag " ng-repeat="filter in filters">
<!-- Negation button and field -->
<span class="filterLeft ">
<a href="" class="invert" ng-class="{ inverted: filter.inverted }" title="invert" ng-click="filter.inverted = !filter.inverted">¬</a>
<input type="text" class="filterField" ng-model="filter.field" placeholder="field" title="which column to filter (regex)" autosize />
</span>
<!-- Rexex to apply to that field -->
<input type="text" class="filterRegex" ng-model="filter.regex" placeholder="regex" title="regex to filter on that column" autosize />
<!-- Delete filter -->
<a href="" class="delete" ng-click="removeFilter(filter)">&times;</a>
</li>
</div>
# Stylus style sheet for filter tags
.filterTag
background-color: #ccc
border-radius: 5px
display: inline-block
height: 24px
line-height: 24px
font-weight: bold
.delete
padding-right: 3px
input
padding: 0 0 0 5px
margin: 0
border: none
height: 24px
background-color: transparent
box-shadow: none
font-weight: bold
vertical-align: top
// Invert button + field regex
.filterLeft
display: inline-block
height: 24px
background-color: #aaa
border-radius: 5px
input
color: white
font-weight: bold
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25)
// Invert button
a.invert
padding-left: 3px
font-size: 18px
&.inverted
color: #EB000A
# A filter that can match or not match an object.
# It contains:
# - field: the field (property) of the object to match on (as a string, will be used as a regex, case-insensitive)
# - regex: the regex that this field's value must match (as a string)
# - inverted: if true, the result of the filter is inverted (flips the value of `match()`)
#
# You can use `flattenObject()` to flatten objects before you pass them into `match()`,
# which allows you to match `field` anywhere in a nested object.
class ObjectFilter
constructor: (@field='', @regex='', @inverted=false) ->
# Formats the filter to a human-writable query string.
# field and regex separated by ':', negation with prefixed '!'.
# Example: !tag:mytag
toQueryString: -> "#{if @inverted then '!' else ''}#{@field}:#{@regex}"
# Parses a filter out of a query string as formatted by `toQueryString`.
@fromQueryString: (qs) ->
if (qs[0] == '!')
invertedSign = true
input = qs.substr(1)
else
invertedSign = false
input = qs
fs = input.split(':')
if (fs[1]?)
field = fs[0]
regex = fs.splice(1).join(':')
else
field = ''
regex = fs[0]
new ObjectFilter field, regex, if invertedSign then true else false # make the filter
# Returns whether the given object matches the filter.
#
# The filter matches is hidden if field matches some field, but `regex` rejects its `value`.
# `inverted` flips the result.
match: (obj) ->
fieldRegex = RegExp(@field, "i")
valueRegex = RegExp(@regex)
for own k, v of obj
if k.match fieldRegex
no_field_matched = false
# v might be nasty null or undefined
if (v or '').toString().match valueRegex
return true ^ @inverted
return no_field_matched ^ @inverted
# Flattens a nested object into a single one.
# Example: { a: 1, b: {x: 2, y: 3} } -> { a: 1, b.x: 2, b.y: 3 }
#
# From http://stackoverflow.com/a/965315/263061, using '.' instead of '_'.
@flattenObject = (obj, includePrototype, into, prefix) ->
into = into or {}
prefix = prefix or ""
for k of obj
if includePrototype or obj.hasOwnProperty(k)
prop = obj[k]
if prop and typeof prop is "object" and not (prop instanceof Date or prop instanceof RegExp)
@flattenObject prop, includePrototype, into, prefix + k + "."
else
into[prefix + k] = prop
into
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment