|
176 | 176 | orderByFilter.$inject = ['$parse'];
|
177 | 177 | function orderByFilter($parse) {
|
178 | 178 | return function(array, sortPredicate, reverseOrder) {
|
| 179 | + |
179 | 180 | if (!(isArrayLike(array))) return array;
|
180 |
| - sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate]; |
| 181 | + |
| 182 | + if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } |
181 | 183 | if (sortPredicate.length === 0) { sortPredicate = ['+']; }
|
182 |
| - sortPredicate = sortPredicate.map(function(predicate) { |
183 |
| - var descending = false, get = predicate || identity; |
184 |
| - if (isString(predicate)) { |
| 184 | + |
| 185 | + var predicates = processPredicates(sortPredicate, reverseOrder); |
| 186 | + |
| 187 | + // The next three lines are a version of a Swartzian Transform idiom from Perl |
| 188 | + // (sometimes called the Decorate-Sort-Undecorate idiom) |
| 189 | + // See https://en.wikipedia.org/wiki/Schwartzian_transform |
| 190 | + var compareValues = Array.prototype.map.call(array, getComparisonObject); |
| 191 | + compareValues.sort(doComparison); |
| 192 | + array = compareValues.map(function(item) { return item.value; }); |
| 193 | + |
| 194 | + return array; |
| 195 | + |
| 196 | + function getComparisonObject(value, index) { |
| 197 | + return { |
| 198 | + value: value, |
| 199 | + predicateValues: predicates.map(function(predicate) { |
| 200 | + return getPredicateValue(predicate.get(value), index); |
| 201 | + }) |
| 202 | + }; |
| 203 | + } |
| 204 | + |
| 205 | + function doComparison(v1, v2) { |
| 206 | + var result = 0; |
| 207 | + for (var index=0, length = predicates.length; index < length; ++index) { |
| 208 | + result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; |
| 209 | + if (result) break; |
| 210 | + } |
| 211 | + return result; |
| 212 | + } |
| 213 | + }; |
| 214 | + |
| 215 | + function processPredicates(sortPredicate, reverseOrder) { |
| 216 | + reverseOrder = reverseOrder ? -1 : 1; |
| 217 | + return sortPredicate.map(function(predicate) { |
| 218 | + var descending = 1, get = identity; |
| 219 | + |
| 220 | + if (isFunction(predicate)) { |
| 221 | + get = predicate; |
| 222 | + } else if (isString(predicate)) { |
185 | 223 | if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
|
186 |
| - descending = predicate.charAt(0) == '-'; |
| 224 | + descending = predicate.charAt(0) == '-' ? -1 : 1; |
187 | 225 | predicate = predicate.substring(1);
|
188 | 226 | }
|
189 |
| - if (predicate === '') { |
190 |
| - // Effectively no predicate was passed so we compare identity |
191 |
| - return reverseComparator(compare, descending); |
192 |
| - } |
193 |
| - get = $parse(predicate); |
194 |
| - if (get.constant) { |
195 |
| - var key = get(); |
196 |
| - return reverseComparator(function(a, b) { |
197 |
| - return compare(a[key], b[key]); |
198 |
| - }, descending); |
| 227 | + if (predicate !== '') { |
| 228 | + get = $parse(predicate); |
| 229 | + if (get.constant) { |
| 230 | + var key = get(); |
| 231 | + get = function(value) { return value[key]; }; |
| 232 | + } |
199 | 233 | }
|
200 | 234 | }
|
201 |
| - return reverseComparator(function(a, b) { |
202 |
| - return compare(get(a),get(b)); |
203 |
| - }, descending); |
| 235 | + return { get: get, descending: descending * reverseOrder }; |
204 | 236 | });
|
205 |
| - return slice.call(array).sort(reverseComparator(comparator, reverseOrder)); |
| 237 | + } |
206 | 238 |
|
207 |
| - function comparator(o1, o2) { |
208 |
| - for (var i = 0; i < sortPredicate.length; i++) { |
209 |
| - var comp = sortPredicate[i](o1, o2); |
210 |
| - if (comp !== 0) return comp; |
211 |
| - } |
212 |
| - return 0; |
213 |
| - } |
214 |
| - function reverseComparator(comp, descending) { |
215 |
| - return descending |
216 |
| - ? function(a, b) {return comp(b,a);} |
217 |
| - : comp; |
| 239 | + function isPrimitive(value) { |
| 240 | + switch (typeof value) { |
| 241 | + case 'number': /* falls through */ |
| 242 | + case 'boolean': /* falls through */ |
| 243 | + case 'string': |
| 244 | + return true; |
| 245 | + default: |
| 246 | + return false; |
218 | 247 | }
|
| 248 | + } |
219 | 249 |
|
220 |
| - function isPrimitive(value) { |
221 |
| - switch (typeof value) { |
222 |
| - case 'number': /* falls through */ |
223 |
| - case 'boolean': /* falls through */ |
224 |
| - case 'string': |
225 |
| - return true; |
226 |
| - default: |
227 |
| - return false; |
228 |
| - } |
| 250 | + function objectValue(value, index) { |
| 251 | + // If `valueOf` is a valid function use that |
| 252 | + if (typeof value.valueOf === 'function') { |
| 253 | + value = value.valueOf(); |
| 254 | + if (isPrimitive(value)) return value; |
| 255 | + } |
| 256 | + // If `toString` is a valid function and not the one from `Object.prototype` use that |
| 257 | + if (hasCustomToString(value)) { |
| 258 | + value = value.toString(); |
| 259 | + if (isPrimitive(value)) return value; |
229 | 260 | }
|
| 261 | + // We have a basic object so we use the position of the object in the collection |
| 262 | + return index; |
| 263 | + } |
230 | 264 |
|
231 |
| - function objectToString(value) { |
232 |
| - if (value === null) return 'null'; |
233 |
| - if (typeof value.valueOf === 'function') { |
234 |
| - value = value.valueOf(); |
235 |
| - if (isPrimitive(value)) return value; |
236 |
| - } |
237 |
| - if (typeof value.toString === 'function') { |
238 |
| - value = value.toString(); |
239 |
| - if (isPrimitive(value)) return value; |
240 |
| - } |
241 |
| - return ''; |
| 265 | + function getPredicateValue(value, index) { |
| 266 | + var type = typeof value; |
| 267 | + if (value === null) { |
| 268 | + type = 'string'; |
| 269 | + value = 'null'; |
| 270 | + } else if (type === 'string') { |
| 271 | + value = value.toLowerCase(); |
| 272 | + } else if (type === 'object') { |
| 273 | + value = objectValue(value, index); |
242 | 274 | }
|
| 275 | + return { value: value, type: type }; |
| 276 | + } |
243 | 277 |
|
244 |
| - function compare(v1, v2) { |
245 |
| - var t1 = typeof v1; |
246 |
| - var t2 = typeof v2; |
247 |
| - if (t1 === t2 && t1 === "object") { |
248 |
| - v1 = objectToString(v1); |
249 |
| - v2 = objectToString(v2); |
250 |
| - } |
251 |
| - if (t1 === t2) { |
252 |
| - if (t1 === "string") { |
253 |
| - v1 = v1.toLowerCase(); |
254 |
| - v2 = v2.toLowerCase(); |
255 |
| - } |
256 |
| - if (v1 === v2) return 0; |
257 |
| - return v1 < v2 ? -1 : 1; |
258 |
| - } else { |
259 |
| - return t1 < t2 ? -1 : 1; |
| 278 | + function compare(v1, v2) { |
| 279 | + var result = 0; |
| 280 | + if (v1.type === v2.type) { |
| 281 | + if (v1.value !== v2.value) { |
| 282 | + result = v1.value < v2.value ? -1 : 1; |
260 | 283 | }
|
| 284 | + } else { |
| 285 | + result = v1.type < v2.type ? -1 : 1; |
261 | 286 | }
|
262 |
| - }; |
| 287 | + return result; |
| 288 | + } |
263 | 289 | }
|
0 commit comments