forked from ionic-team/ionic-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
virtualScroll: non-full-featured version, to start testing
- Loading branch information
Showing
7 changed files
with
577 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
IonicModule | ||
.directive('collectionRepeat', [ | ||
'$collectionRepeatManager', | ||
'$collectionRepeatDataSource', | ||
'$parse', | ||
function($collectionRepeatManager, $collectionRepeatDataSource, $parse) { | ||
return { | ||
priority: 1000, | ||
transclude: 'element', | ||
terminal: true, | ||
$$tlb: true, | ||
require: '^$ionicScroll', | ||
link: function($scope, $element, $attr, scrollCtrl, $transclude) { | ||
var scrollView = scrollCtrl.scrollView; | ||
if (scrollView.options.scrollingX && scrollView.options.scrollingY) { | ||
throw new Error("Cannot create a collection-repeat within a scrollView that is scrollable on both x and y axis. Choose only one."); | ||
} | ||
|
||
var isVertical = !!scrollView.options.scrollingY; | ||
if (isVertical && !$attr.collectionItemHeight) { | ||
throw new Error("collection-repeat expected attribute collection-item-height to be a an expression that returns a number."); | ||
} else if (!isVertical && !$attr.collectionItemWidth) { | ||
throw new Error("collection-repeat expected attribute collection-item-width to be a an expression that returns a number."); | ||
} | ||
var heightGetter = $attr.collectionItemHeight ? | ||
$parse($attr.collectionItemHeight) : | ||
function() { return scrollView.__clientHeight; }; | ||
var widthGetter = $attr.collectionItemWidth ? | ||
$parse($attr.collectionItemWidth) : | ||
function() { return scrollView.__clientWidth; }; | ||
|
||
var match = $attr.collectionRepeat.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); | ||
if (!match) { | ||
throw new Error("collection-repeat expected expression in form of '_item_ in _collection_[ track by _id_]' but got '" + $attr.collectionRepeat + "'."); | ||
} | ||
|
||
var dataSource = new $collectionRepeatDataSource({ | ||
scope: $scope, | ||
transcludeFn: $transclude, | ||
transcludeParent: $element.parent(), | ||
keyExpr: match[1], | ||
listExpr: match[2], | ||
trackByExpr: match[3], | ||
heightGetter: heightGetter, | ||
widthGetter: widthGetter | ||
}); | ||
var collectionRepeatManager = new $collectionRepeatManager({ | ||
dataSource: dataSource, | ||
element: scrollCtrl.$element, | ||
scrollView: scrollCtrl.scrollView, | ||
}); | ||
|
||
$scope.$watchCollection(dataSource.listExpr, function(value) { | ||
if (value && !angular.isArray(value)) { | ||
throw new Error("collection-repeat expects an array to repeat over, but instead got '" + typeof value + "'."); | ||
} | ||
dataSource.setData(value); | ||
collectionRepeatManager.resize(); | ||
}); | ||
ionic.on('resize', function() { | ||
collectionRepeatManager.resize(); | ||
}, window); | ||
|
||
$scope.$on('$destroy', function() { | ||
collectionRepeatManager.destroy(); | ||
dataSource.destroy(); | ||
}); | ||
} | ||
}; | ||
}]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
IonicModule | ||
.factory('$collectionRepeatDataSource', [ | ||
'$cacheFactory', | ||
'$parse', | ||
function($cacheFactory, $parse) { | ||
var nextCacheId = 0; | ||
function CollectionRepeatDataSource(options) { | ||
var self = this; | ||
this.scope = options.scope; | ||
this.transcludeFn = options.transcludeFn; | ||
this.transcludeParent = options.transcludeParent; | ||
|
||
this.keyExpr = options.keyExpr; | ||
this.listExpr = options.listExpr; | ||
this.trackByExpr = options.trackByExpr; | ||
|
||
this.heightGetter = options.heightGetter; | ||
this.widthGetter = options.widthGetter; | ||
|
||
if (this.trackByExpr) { | ||
var trackByGetter = $parse(this.trackByExpr); | ||
var hashFnLocals = {$id: hashKey}; | ||
this.trackByIdGetter = function(index, value) { | ||
hashFnLocals[self.keyExpr] = value; | ||
hashFnLocals.$index = index; | ||
return trackByGetter(self.scope, hashFnLocals); | ||
}; | ||
} else { | ||
this.trackByIdGetter = function(index, value) { | ||
return hashKey(value); | ||
}; | ||
} | ||
|
||
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 cacheKeys; | ||
}; | ||
} | ||
CollectionRepeatDataSource.prototype = { | ||
destroy: function() { | ||
this.dimensions.length = 0; | ||
for (var key in this.itemCache.keys()) { | ||
var item = this.itemCache.get(key); | ||
item.element.remove(); | ||
item.scope.$destroy(); | ||
} | ||
this.itemCache.removeAll(); | ||
}, | ||
calculateDataDimensions: function() { | ||
var totalWidth = 0; | ||
var totalHeight = 0; | ||
var locals = {}; | ||
|
||
this.dimensions = this.data.map(function(value, index) { | ||
locals[this.keyExpr] = value; | ||
locals.$index = index; | ||
var ret = { | ||
width: this.widthGetter(this.scope, locals), | ||
height: this.heightGetter(this.scope, locals), | ||
totalWidth: totalWidth, | ||
totalHeight: totalHeight | ||
}; | ||
totalWidth += ret.width; | ||
totalHeight += ret.height; | ||
return ret; | ||
}, this); | ||
this.totalWidth = totalWidth; | ||
this.totalHeight = totalHeight; | ||
}, | ||
compileItem: function(index, value) { | ||
var key = this.trackByIdGetter(index, value); | ||
var cachedItem = this.itemCache.get(key); | ||
if (cachedItem) return cachedItem; | ||
|
||
var item = {}; | ||
item.scope = this.scope.$new(); | ||
item.scope[this.keyExpr] = value; | ||
|
||
this.transcludeFn(item.scope, function(clone) { | ||
item.element = clone; | ||
item.element[0].classList.add('scroll-collection-item'); | ||
}); | ||
|
||
return this.itemCache.put(key, item); | ||
}, | ||
getItem: function(index) { | ||
var value = this.data[index]; | ||
var item = this.compileItem(index, value); | ||
|
||
if (item.scope.$index !== index) { | ||
item.scope.$index = item.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); | ||
} | ||
|
||
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); | ||
}, | ||
attachItem: function(item) { | ||
if (!item.element[0].parentNode) { | ||
this.transcludeParent[0].appendChild(item.element[0]); | ||
} | ||
reconnectScope(item.scope); | ||
}, | ||
getLength: function() { | ||
return this.data && this.data.length || 0; | ||
}, | ||
setData: function(value) { | ||
this.data = value; | ||
this.calculateDataDimensions(); | ||
}, | ||
}; | ||
|
||
return CollectionRepeatDataSource; | ||
}]); | ||
|
||
/** | ||
* Computes a hash of an 'obj'. | ||
* Hash of a: | ||
* string is string | ||
* number is number as string | ||
* object is either result of calling $$hashKey function on the object or uniquely generated id, | ||
* that is also assigned to the $$hashKey property of the object. | ||
* | ||
* @param obj | ||
* @returns {string} hash string such that the same input will have the same hash string. | ||
* The resulting string key is in 'type:hashKey' format. | ||
*/ | ||
function hashKey(obj) { | ||
var objType = typeof obj, | ||
key; | ||
|
||
if (objType == 'object' && obj !== null) { | ||
if (typeof (key = obj.$$hashKey) == 'function') { | ||
// must invoke on object to keep the right this | ||
key = obj.$$hashKey(); | ||
} else if (key === undefined) { | ||
key = obj.$$hashKey = ionic.Utils.nextUid(); | ||
} | ||
} else { | ||
key = obj; | ||
} | ||
|
||
return objType + ':' + key; | ||
} | ||
|
||
function disconnectScope(scope) { | ||
if (scope.$root === scope) { | ||
return; // we can't disconnect the root node; | ||
} | ||
var parent = scope.$parent; | ||
scope.$$disconnected = true; | ||
// See Scope.$destroy | ||
if (parent.$$childHead === scope) { | ||
parent.$$childHead = scope.$$nextSibling; | ||
} | ||
if (parent.$$childTail === scope) { | ||
parent.$$childTail = scope.$$prevSibling; | ||
} | ||
if (scope.$$prevSibling) { | ||
scope.$$prevSibling.$$nextSibling = scope.$$nextSibling; | ||
} | ||
if (scope.$$nextSibling) { | ||
scope.$$nextSibling.$$prevSibling = scope.$$prevSibling; | ||
} | ||
scope.$$nextSibling = scope.$$prevSibling = null; | ||
} | ||
|
||
function reconnectScope(scope) { | ||
if (scope.$root === scope) { | ||
return; // we can't disconnect the root node; | ||
} | ||
if (!scope.$$disconnected) { | ||
return; | ||
} | ||
var parent = scope.$parent; | ||
scope.$$disconnected = false; | ||
// See Scope.$new for this logic... | ||
scope.$$prevSibling = parent.$$childTail; | ||
if (parent.$$childHead) { | ||
parent.$$childTail.$$nextSibling = scope; | ||
parent.$$childTail = scope; | ||
} else { | ||
parent.$$childHead = parent.$$childTail = scope; | ||
} | ||
} |
Oops, something went wrong.