Skip to content

Commit

Permalink
feat(collectionRepeat): huge optimization upgrades
Browse files Browse the repository at this point in the history
Closes #1597
  • Loading branch information
ajoslin committed Jun 10, 2014
1 parent 8c36a66 commit 6af5d68
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 295 deletions.
10 changes: 2 additions & 8 deletions js/angular/directive/collectionRepeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,9 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
} else if (!isVertical && !$attr.collectionItemWidth) {
throw new Error(COLLECTION_REPEAT_ATTR_WIDTH_ERROR);
}
$attr.collectionItemHeight = $attr.collectionItemHeight || '"100%"';
$attr.collectionItemWidth = $attr.collectionItemWidth || '"100%"';

var heightParsed = $attr.collectionItemHeight ?
$parse($attr.collectionItemHeight) :
function() { return scrollView.__clientHeight; };
var widthParsed = $attr.collectionItemWidth ?
$parse($attr.collectionItemWidth) :
function() { return scrollView.__clientWidth; };
var heightParsed = $parse($attr.collectionItemHeight || '"100%"');
var widthParsed = $parse($attr.collectionItemWidth || '"100%"');

var heightGetter = function(scope, locals) {
var result = heightParsed(scope, locals);
Expand Down
112 changes: 64 additions & 48 deletions js/angular/service/collectionRepeatDataSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ IonicModule
'$parse',
'$rootScope',
function($cacheFactory, $parse, $rootScope) {
var nextCacheId = 0;

function CollectionRepeatDataSource(options) {
var self = this;
this.scope = options.scope;
Expand Down Expand Up @@ -35,33 +35,28 @@ function($cacheFactory, $parse, $rootScope) {
};
}

var cacheKeys = {};
this.itemCache = $cacheFactory(nextCacheId++, {size: 500});

var _put = this.itemCache.put;
this.itemCache.put = function(key, value) {
cacheKeys[key] = true;
return _put(key, value);
};

var _remove = this.itemCache.remove;
this.itemCache.remove = function(key) {
delete cacheKeys[key];
return _remove(key);
};
this.itemCache.keys = function() {
return Object.keys(cacheKeys);
};
this.attachedItems = {};
this.BACKUP_ITEMS_LENGTH = 10;
this.backupItemsArray = [];
}
CollectionRepeatDataSource.prototype = {
setup: function() {
for (var i = 0; i < this.BACKUP_ITEMS_LENGTH; i++) {
this.detachItem(this.createItem());
}
},
destroy: function() {
this.dimensions.length = 0;
this.itemCache.keys().forEach(function(key) {
var item = this.itemCache.get(key);
item.element.remove();
item.scope.$destroy();
this.data = null;
forEach(this.backupItemsArray, function(item) {
this.destroyItem(item);
}, this);
this.backupItemsArray.length = 0;

forEach(this.attachedItems, function(item, key) {
this.destroyItem(item);
}, this);
this.itemCache.removeAll();
this.attachedItems = {};
},
calculateDataDimensions: function() {
var locals = {};
Expand All @@ -74,53 +69,74 @@ function($cacheFactory, $parse, $rootScope) {
};
}, this);
},
compileItem: function(index, value) {
var key = this.itemHashGetter(index, value);
var cachedItem = this.itemCache.get(key);
if (cachedItem) return cachedItem;

createItem: function() {
var item = {};
item.scope = this.scope.$new();
item.scope[this.keyExpr] = value;

this.transcludeFn(item.scope, function(clone) {
clone.css('position', 'absolute');
item.element = clone;
});

return this.itemCache.put(key, item);
this.transcludeParent.append(item.element);

return item;
},
getItem: function(index) {
getItem: function(hash) {
window.AMOUNT = window.AMOUNT || 0;
if ( (item = this.attachedItems[hash]) ) {
//do nothing, the item is good
} else if ( (item = this.backupItemsArray.pop()) ) {
reconnectScope(item.scope);
} else {
AMOUNT++;
item = this.createItem();
}
return item;
},
attachItemAtIndex: function(index) {
var value = this.data[index];
var item = this.compileItem(index, value);
var hash = this.itemHashGetter(index, value);
var item = this.getItem(hash);

if (item.scope.$index !== index) {
if (item.scope.$index !== index || item.scope[this.keyExpr] !== value) {
item.scope[this.keyExpr] = value;
item.scope.$index = index;
item.scope.$first = (index === 0);
item.scope.$last = (index === (this.getLength() - 1));
item.scope.$middle = !(item.scope.$first || item.scope.$last);
item.scope.$odd = !(item.scope.$even = (index&1) === 0);

//We changed the scope, so digest if needed
if (!$rootScope.$$phase) {
item.scope.$digest();
}
}

item.hash = hash;
this.attachedItems[hash] = item;

return item;
},
detachItem: function(item) {
var i, node, parent;
//Don't .remove(), that will destroy element data
for (i = 0; i < item.element.length; i++) {
node = item.element[i];
parent = node.parentNode;
parent && parent.removeChild(node);
}
//Don't .$destroy(), just stop watchers and events firing
disconnectScope(item.scope);
destroyItem: function(item) {
item.element.remove();
item.scope.$destroy();
item.scope = null;
item.element = null;
},
attachItem: function(item) {
if (!item.element[0].parentNode) {
this.transcludeParent[0].appendChild(item.element[0]);
detachItem: function(item) {
delete this.attachedItems[item.hash];

// If we are at the limit of backup items, just get rid of the this element
if (this.backupItemsArray.length >= this.BACKUP_ITEMS_LENGTH) {
this.destroyItem(item);
// Otherwise, add it to our backup items
} else {
this.backupItemsArray.push(item);
item.element.css(ionic.CSS.TRANSFORM, 'translate3d(-2000px,-2000px,0)');
//Don't .$destroy(), just stop watchers and events firing
disconnectScope(item.scope);
}
reconnectScope(item.scope);
!$rootScope.$$phase && item.scope.$digest();
},
getLength: function() {
return this.data && this.data.length || 0;
Expand Down
Loading

0 comments on commit 6af5d68

Please sign in to comment.