feat(filterFilter): allow overwriting the special $ property name
Previously, the special property name that would match against any
property was hard-coded to `$`.
With this commit, the user can specify an arbitrary property name,
by passing a 4th argument to `filterFilter()`. E.g.:
```js
var items = [{foo: 'bar'}, {baz: 'qux'}];
var expr = {'%': 'bar'};
console.log(filterFilter(items, expr, null, '%')); // [{foo: 'bar'}]
```
Fixes #13313
PR (#13356)
This commit is contained in:
committed by
Martin Staffa
parent
ff5f645b00
commit
2b182eb020
+23
-17
@@ -22,10 +22,11 @@
|
||||
* - `Object`: A pattern object can be used to filter specific properties on objects contained
|
||||
* by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
|
||||
* which have property `name` containing "M" and property `phone` containing "1". A special
|
||||
* property name `$` can be used (as in `{$:"text"}`) to accept a match against any
|
||||
* property of the object or its nested object properties. That's equivalent to the simple
|
||||
* substring match with a `string` as described above. The predicate can be negated by prefixing
|
||||
* the string with `!`.
|
||||
* property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match
|
||||
* against any property of the object or its nested object properties. That's equivalent to the
|
||||
* simple substring match with a `string` as described above. The special property name can be
|
||||
* overwritten, using the `anyPropertyKey` parameter.
|
||||
* The predicate can be negated by prefixing the string with `!`.
|
||||
* For example `{name: "!M"}` predicate will return an array of items which have property `name`
|
||||
* not containing "M".
|
||||
*
|
||||
@@ -59,6 +60,9 @@
|
||||
* Primitive values are converted to strings. Objects are not compared against primitives,
|
||||
* unless they have a custom `toString` method (e.g. `Date` objects).
|
||||
*
|
||||
* @param {string=} anyPropertyKey The special property name that matches against any property.
|
||||
* By default `$`.
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
@@ -127,8 +131,9 @@
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
function filterFilter() {
|
||||
return function(array, expression, comparator) {
|
||||
return function(array, expression, comparator, anyPropertyKey) {
|
||||
if (!isArrayLike(array)) {
|
||||
if (array == null) {
|
||||
return array;
|
||||
@@ -137,6 +142,7 @@ function filterFilter() {
|
||||
}
|
||||
}
|
||||
|
||||
anyPropertyKey = anyPropertyKey || '$';
|
||||
var expressionType = getTypeForFilter(expression);
|
||||
var predicateFn;
|
||||
var matchAgainstAnyProp;
|
||||
@@ -153,7 +159,7 @@ function filterFilter() {
|
||||
//jshint -W086
|
||||
case 'object':
|
||||
//jshint +W086
|
||||
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
|
||||
predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp);
|
||||
break;
|
||||
default:
|
||||
return array;
|
||||
@@ -164,8 +170,8 @@ function filterFilter() {
|
||||
}
|
||||
|
||||
// Helper functions for `filterFilter`
|
||||
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
|
||||
function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) {
|
||||
var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression);
|
||||
var predicateFn;
|
||||
|
||||
if (comparator === true) {
|
||||
@@ -193,25 +199,25 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
|
||||
predicateFn = function(item) {
|
||||
if (shouldMatchPrimitives && !isObject(item)) {
|
||||
return deepCompare(item, expression.$, comparator, false);
|
||||
return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false);
|
||||
}
|
||||
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
|
||||
return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp);
|
||||
};
|
||||
|
||||
return predicateFn;
|
||||
}
|
||||
|
||||
function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
|
||||
function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) {
|
||||
var actualType = getTypeForFilter(actual);
|
||||
var expectedType = getTypeForFilter(expected);
|
||||
|
||||
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
|
||||
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
|
||||
return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp);
|
||||
} else if (isArray(actual)) {
|
||||
// In case `actual` is an array, consider it a match
|
||||
// if ANY of it's items matches `expected`
|
||||
return actual.some(function(item) {
|
||||
return deepCompare(item, expected, comparator, matchAgainstAnyProp);
|
||||
return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,11 +226,11 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
|
||||
var key;
|
||||
if (matchAgainstAnyProp) {
|
||||
for (key in actual) {
|
||||
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
|
||||
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
|
||||
return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false);
|
||||
} else if (expectedType === 'object') {
|
||||
for (key in expected) {
|
||||
var expectedVal = expected[key];
|
||||
@@ -232,9 +238,9 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchAnyProperty = key === '$';
|
||||
var matchAnyProperty = key === anyPropertyKey;
|
||||
var actualVal = matchAnyProperty ? actual : actual[key];
|
||||
if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
|
||||
if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +192,25 @@ describe('Filter: filter', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should allow specifying the special "match-all" property', function() {
|
||||
var items = [
|
||||
{foo: 'baz'},
|
||||
{bar: 'baz'},
|
||||
{'%': 'no dollar'}
|
||||
];
|
||||
|
||||
expect(filter(items, {$: 'baz'}).length).toBe(2);
|
||||
expect(filter(items, {$: 'baz'}, null, '%').length).toBe(0);
|
||||
|
||||
expect(filter(items, {'%': 'dollar'}).length).toBe(1);
|
||||
expect(filter(items, {$: 'dollar'}).length).toBe(1);
|
||||
expect(filter(items, {$: 'dollar'}, null, '%').length).toBe(0);
|
||||
|
||||
expect(filter(items, {'%': 'baz'}).length).toBe(0);
|
||||
expect(filter(items, {'%': 'baz'}, null, '%').length).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
it('should match any properties in the nested object for given deep "$" property', function() {
|
||||
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
|
||||
{person: {name: 'Billy', email: 'me@billy.com'}},
|
||||
@@ -425,6 +444,7 @@ describe('Filter: filter', function() {
|
||||
toThrowMinErr('filter', 'notarray', 'Expected array but received: {"toString":null,"valueOf":null}');
|
||||
});
|
||||
|
||||
|
||||
it('should not throw an error if used with an array like object', function() {
|
||||
function getArguments() {
|
||||
return arguments;
|
||||
@@ -439,7 +459,6 @@ describe('Filter: filter', function() {
|
||||
expect(filter(argsObj, 'i').length).toBe(2);
|
||||
expect(filter('abc','b').length).toBe(1);
|
||||
expect(filter(nodeList, nodeFilterPredicate).length).toBe(1);
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user