|
64 | 64 | this._promise = readyPromise; |
65 | 65 | this._destroyFn = destroyFn; |
66 | 66 |
|
| 67 | + // indexCache is a weak hashmap (a lazy list) of keys to array indices, |
| 68 | + // items are not guaranteed to stay up to date in this list (since the list |
| 69 | + // can be manually edited without calling the $ methods) and it should |
| 70 | + // always be used with skepticism regarding whether it is accurate |
| 71 | + // (see $indexFor() below for proper usage) |
| 72 | + this._indexCache = {}; |
| 73 | + |
67 | 74 | // Array.isArray will not work on objects which extend the Array class. |
68 | 75 | // So instead of extending the Array class, we just return an actual array. |
69 | 76 | // However, it's still possible to extend FirebaseArray and have the public methods |
|
176 | 183 | */ |
177 | 184 | $indexFor: function(key) { |
178 | 185 | var self = this; |
179 | | - // todo optimize and/or cache these? they wouldn't need to be perfect |
180 | | - return this.$list.findIndex(function(rec) { return self.$$getKey(rec) === key; }); |
| 186 | + var cache = self._indexCache; |
| 187 | + // evaluate whether our key is cached and, if so, whether it is up to date |
| 188 | + if( !cache.hasOwnProperty(key) || self.$keyAt(cache[key]) !== key ) { |
| 189 | + // update the hashmap |
| 190 | + var pos = self.$list.findIndex(function(rec) { return self.$$getKey(rec) === key; }); |
| 191 | + if( pos !== -1 ) { |
| 192 | + cache[key] = pos; |
| 193 | + } |
| 194 | + } |
| 195 | + return cache.hasOwnProperty(key)? cache[key] : -1; |
181 | 196 | }, |
182 | 197 |
|
183 | 198 | /** |
|
367 | 382 | $$process: function(event, rec, prevChild) { |
368 | 383 | var key = this.$$getKey(rec); |
369 | 384 | var changed = false; |
370 | | - var pos; |
| 385 | + var curPos; |
371 | 386 | switch(event) { |
372 | 387 | case 'child_added': |
373 | | - pos = this.$indexFor(key); |
| 388 | + curPos = this.$indexFor(key); |
374 | 389 | break; |
375 | 390 | case 'child_moved': |
376 | | - pos = this.$indexFor(key); |
| 391 | + curPos = this.$indexFor(key); |
377 | 392 | this._spliceOut(key); |
378 | 393 | break; |
379 | 394 | case 'child_removed': |
|
386 | 401 | default: |
387 | 402 | throw new Error('Invalid event type: ' + event); |
388 | 403 | } |
389 | | - if( angular.isDefined(pos) ) { |
| 404 | + if( angular.isDefined(curPos) ) { |
390 | 405 | // add it to the array |
391 | | - changed = this._addAfter(rec, prevChild) !== pos; |
| 406 | + changed = this._addAfter(rec, prevChild) !== curPos; |
392 | 407 | } |
393 | 408 | if( changed ) { |
394 | 409 | // send notifications to anybody monitoring $watch |
|
434 | 449 | if( i === 0 ) { i = this.$list.length; } |
435 | 450 | } |
436 | 451 | this.$list.splice(i, 0, rec); |
| 452 | + this._indexCache[this.$$getKey(rec)] = i; |
437 | 453 | return i; |
438 | 454 | }, |
439 | 455 |
|
|
448 | 464 | _spliceOut: function(key) { |
449 | 465 | var i = this.$indexFor(key); |
450 | 466 | if( i > -1 ) { |
| 467 | + delete this._indexCache[key]; |
451 | 468 | return this.$list.splice(i, 1)[0]; |
452 | 469 | } |
453 | 470 | return null; |
|
467 | 484 | return list[indexOrItem]; |
468 | 485 | } |
469 | 486 | else if( angular.isObject(indexOrItem) ) { |
470 | | - var i = list.length; |
471 | | - while(i--) { |
472 | | - if( list[i] === indexOrItem ) { |
473 | | - return indexOrItem; |
474 | | - } |
475 | | - } |
| 487 | + // it must be an item in this array; it's not sufficient for it just to have |
| 488 | + // a $id or even a $id that is in the array, it must be an actual record |
| 489 | + // the fastest way to determine this is to use $getRecord (to avoid iterating all recs) |
| 490 | + // and compare the two |
| 491 | + var key = this.$$getKey(indexOrItem); |
| 492 | + var rec = this.$getRecord(key); |
| 493 | + return rec === indexOrItem? rec : null; |
476 | 494 | } |
477 | 495 | return null; |
478 | 496 | }, |
|
0 commit comments