Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix(filterFilter): correctly handle deep expression objects #9757

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 85 additions & 88 deletions src/ng/filter/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,104 +119,101 @@ function filterFilter() {
return function(array, expression, comparator) {
if (!isArray(array)) return array;

var comparatorType = typeof(comparator),
predicates = [];
var predicateFn;
var matchAgainstAnyProp;

predicates.check = function(value, index) {
for (var j = 0; j < predicates.length; j++) {
if (!predicates[j](value, index)) {
return false;
}
}
return true;
};

if (comparatorType !== 'function') {
if (comparatorType === 'boolean' && comparator) {
comparator = function(obj, text) {
return angular.equals(obj, text);
};
} else {
comparator = function(obj, text) {
if (obj && text && typeof obj === 'object' && typeof text === 'object') {
for (var objKey in obj) {
if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
comparator(obj[objKey], text[objKey])) {
return true;
}
}
return false;
}
text = ('' + text).toLowerCase();
return ('' + obj).toLowerCase().indexOf(text) > -1;
};
}
}

var search = function(obj, text) {
if (typeof text === 'string' && text.charAt(0) === '!') {
return !search(obj, text.substr(1));
}
switch (typeof obj) {
case 'boolean':
case 'number':
case 'string':
return comparator(obj, text);
case 'object':
switch (typeof text) {
case 'object':
return comparator(obj, text);
default:
for (var objKey in obj) {
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
return true;
}
}
break;
}
return false;
case 'array':
for (var i = 0; i < obj.length; i++) {
if (search(obj[i], text)) {
return true;
}
}
return false;
default:
return false;
}
};
switch (typeof expression) {
case 'function':
predicateFn = expression;
break;
case 'boolean':
case 'number':
case 'string':
// Set up expression object and fall through
expression = {$:expression};
// jshint -W086
matchAgainstAnyProp = true;
//jshint -W086
case 'object':
// jshint +W086
for (var key in expression) {
(function(path) {
if (typeof expression[path] === 'undefined') return;
predicates.push(function(value) {
return search(path == '$' ? value : (value && value[path]), expression[path]);
});
})(key);
}
break;
case 'function':
predicates.push(expression);
//jshint +W086
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
break;
default:
return array;
}
var filtered = [];
for (var j = 0; j < array.length; j++) {
var value = array[j];
if (predicates.check(value, j)) {
filtered.push(value);

return array.filter(predicateFn);
};
}

// Helper functions for `filterFilter`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so I like the way this is re-organized, but I will have to work a bit to review this.

function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
var predicateFn;

if (comparator === true) {
comparator = equals;
} else if (!isFunction(comparator)) {
comparator = function(actual, expected) {
if (isObject(actual) || isObject(expected)) {
// Prevent an object to be considered equal to a string like `'[object'`
return false;
}
}
return filtered;

actual = lowercase('' + actual);
expected = lowercase('' + expected);
return actual.indexOf(expected) !== -1;
};
}

predicateFn = function(item) {
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
};

return predicateFn;
}

function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
var actualType = typeof actual;
var expectedType = typeof expected;

if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
} else if (actualType === 'array') {
// 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);
});
}

switch (actualType) {
case 'object':
var key;
if (matchAgainstAnyProp) {
for (key in actual) {
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
return true;
}
}
return false;
} else if (expectedType === 'object') {
for (key in expected) {
var expectedVal = expected[key];
if (isFunction(expectedVal)) {
continue;
}

var keyIsDollar = key === '$';
var actualVal = keyIsDollar ? actual : actual[key];
if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
return false;
}
}
return true;
} else {
return comparator(actual, expected);
}
break;
case 'function':
return false;
default:
return comparator(actual, expected);
}
}
Loading