|
22 | 22 | * - `Object`: A pattern object can be used to filter specific properties on objects contained
|
23 | 23 | * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
|
24 | 24 | * which have property `name` containing "M" and property `phone` containing "1". A special
|
25 |
| - * property name `$` can be used (as in `{$:"text"}`) to accept a match against any |
26 |
| - * property of the object or its nested object properties. That's equivalent to the simple |
27 |
| - * substring match with a `string` as described above. The predicate can be negated by prefixing |
28 |
| - * the string with `!`. |
| 25 | + * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match |
| 26 | + * against any property of the object or its nested object properties. That's equivalent to the |
| 27 | + * simple substring match with a `string` as described above. The special property name can be |
| 28 | + * overwritten, using the `specialProp` parameter. |
| 29 | + * The predicate can be negated by prefixing the string with `!`. |
29 | 30 | * For example `{name: "!M"}` predicate will return an array of items which have property `name`
|
30 | 31 | * not containing "M".
|
31 | 32 | *
|
|
59 | 60 | * Primitive values are converted to strings. Objects are not compared against primitives,
|
60 | 61 | * unless they have a custom `toString` method (e.g. `Date` objects).
|
61 | 62 | *
|
| 63 | + * @param {string=} specialKey The special property name that matches against any property. |
| 64 | + * By default `$`. |
| 65 | + * |
62 | 66 | * @example
|
63 | 67 | <example>
|
64 | 68 | <file name="index.html">
|
|
127 | 131 | </file>
|
128 | 132 | </example>
|
129 | 133 | */
|
| 134 | + |
| 135 | +// ---------------------------------------------------------------------------------------------- // |
| 136 | +// NEW IMPLEMENTATION |
| 137 | +// ---------------------------------------------------------------------------------------------- // |
| 138 | +function filterFilter() { |
| 139 | + // return function(array, expression, comparator) { |
| 140 | + return function(array, expression, comparator, specialKey) { |
| 141 | + if (!isArrayLike(array)) { |
| 142 | + if (array == null) { |
| 143 | + return array; |
| 144 | + } else { |
| 145 | + throw minErr('filter')('notarray', 'Expected array but received: {0}', array); |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + // |
| 150 | + specialKey = specialKey || '$'; |
| 151 | + var expressionType = getTypeForFilter(expression); |
| 152 | + var predicateFn; |
| 153 | + var matchAgainstAnyProp; |
| 154 | + |
| 155 | + switch (expressionType) { |
| 156 | + case 'function': |
| 157 | + predicateFn = expression; |
| 158 | + break; |
| 159 | + case 'boolean': |
| 160 | + case 'null': |
| 161 | + case 'number': |
| 162 | + case 'string': |
| 163 | + matchAgainstAnyProp = true; |
| 164 | + //jshint -W086 |
| 165 | + case 'object': |
| 166 | + //jshint +W086 |
| 167 | + // predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); |
| 168 | + predicateFn = createPredicateFn(expression, comparator, specialKey, matchAgainstAnyProp); |
| 169 | + break; |
| 170 | + default: |
| 171 | + return array; |
| 172 | + } |
| 173 | + |
| 174 | + return Array.prototype.filter.call(array, predicateFn); |
| 175 | + }; |
| 176 | +} |
| 177 | + |
| 178 | +// Helper functions for `filterFilter` |
| 179 | +// function createPredicateFn(expression, comparator, matchAgainstAnyProp) { |
| 180 | +function createPredicateFn(expression, comparator, specialKey, matchAgainstAnyProp) { |
| 181 | + // var shouldMatchPrimitives = isObject(expression) && ('$' in expression); |
| 182 | + var shouldMatchPrimitives = isObject(expression) && (specialKey in expression); |
| 183 | + var predicateFn; |
| 184 | + |
| 185 | + if (comparator === true) { |
| 186 | + comparator = equals; |
| 187 | + } else if (!isFunction(comparator)) { |
| 188 | + comparator = function(actual, expected) { |
| 189 | + if (isUndefined(actual)) { |
| 190 | + // No substring matching against `undefined` |
| 191 | + return false; |
| 192 | + } |
| 193 | + if ((actual === null) || (expected === null)) { |
| 194 | + // No substring matching against `null`; only match against `null` |
| 195 | + return actual === expected; |
| 196 | + } |
| 197 | + if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { |
| 198 | + // Should not compare primitives against objects, unless they have custom `toString` method |
| 199 | + return false; |
| 200 | + } |
| 201 | + |
| 202 | + actual = lowercase('' + actual); |
| 203 | + expected = lowercase('' + expected); |
| 204 | + return actual.indexOf(expected) !== -1; |
| 205 | + }; |
| 206 | + } |
| 207 | + |
| 208 | + predicateFn = function(item) { |
| 209 | + if (shouldMatchPrimitives && !isObject(item)) { |
| 210 | + // return deepCompare(item, expression.$, comparator, false); |
| 211 | + return deepCompare(item, expression[specialKey], comparator, specialKey, false); |
| 212 | + } |
| 213 | + // return deepCompare(item, expression, comparator, matchAgainstAnyProp); |
| 214 | + return deepCompare(item, expression, comparator, specialKey, matchAgainstAnyProp); |
| 215 | + }; |
| 216 | + |
| 217 | + return predicateFn; |
| 218 | +} |
| 219 | + |
| 220 | +// function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { |
| 221 | +function deepCompare(actual, expected, comparator, specialKey, matchAgainstAnyProp, dontMatchWholeObject) { |
| 222 | + var actualType = getTypeForFilter(actual); |
| 223 | + var expectedType = getTypeForFilter(expected); |
| 224 | + |
| 225 | + if ((expectedType === 'string') && (expected.charAt(0) === '!')) { |
| 226 | + // return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); |
| 227 | + return !deepCompare(actual, expected.substring(1), comparator, specialKey, matchAgainstAnyProp); |
| 228 | + } else if (isArray(actual)) { |
| 229 | + // In case `actual` is an array, consider it a match |
| 230 | + // if ANY of it's items matches `expected` |
| 231 | + return actual.some(function(item) { |
| 232 | + // return deepCompare(item, expected, comparator, matchAgainstAnyProp); |
| 233 | + return deepCompare(item, expected, comparator, specialKey, matchAgainstAnyProp); |
| 234 | + }); |
| 235 | + } |
| 236 | + |
| 237 | + switch (actualType) { |
| 238 | + case 'object': |
| 239 | + var key; |
| 240 | + if (matchAgainstAnyProp) { |
| 241 | + for (key in actual) { |
| 242 | + // if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { |
| 243 | + if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, specialKey, true)) { |
| 244 | + return true; |
| 245 | + } |
| 246 | + } |
| 247 | + // return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); |
| 248 | + return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, specialKey, false); |
| 249 | + } else if (expectedType === 'object') { |
| 250 | + for (key in expected) { |
| 251 | + var expectedVal = expected[key]; |
| 252 | + if (isFunction(expectedVal) || isUndefined(expectedVal)) { |
| 253 | + continue; |
| 254 | + } |
| 255 | + |
| 256 | + // var matchAnyProperty = key === '$'; |
| 257 | + var matchAnyProperty = key === specialKey; |
| 258 | + var actualVal = matchAnyProperty ? actual : actual[key]; |
| 259 | + // if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { |
| 260 | + if (!deepCompare(actualVal, expectedVal, comparator, specialKey, matchAnyProperty, matchAnyProperty)) { |
| 261 | + return false; |
| 262 | + } |
| 263 | + } |
| 264 | + return true; |
| 265 | + } else { |
| 266 | + return comparator(actual, expected); |
| 267 | + } |
| 268 | + break; |
| 269 | + case 'function': |
| 270 | + return false; |
| 271 | + default: |
| 272 | + return comparator(actual, expected); |
| 273 | + } |
| 274 | +} |
| 275 | +// ---------------------------------------------------------------------------------------------- // |
| 276 | + |
| 277 | + |
| 278 | +// ---------------------------------------------------------------------------------------------- // |
| 279 | +// OLD IMPLEMENTATION |
| 280 | +// ---------------------------------------------------------------------------------------------- // |
130 | 281 | function filterFilter() {
|
131 | 282 | return function(array, expression, comparator) {
|
132 | 283 | if (!isArrayLike(array)) {
|
@@ -249,6 +400,8 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
|
249 | 400 | return comparator(actual, expected);
|
250 | 401 | }
|
251 | 402 | }
|
| 403 | +// ---------------------------------------------------------------------------------------------- // |
| 404 | + |
252 | 405 |
|
253 | 406 | // Used for easily differentiating between `null` and actual `object`
|
254 | 407 | function getTypeForFilter(val) {
|
|
0 commit comments