Skip to content

Commit

Permalink
Merge pull request #32 from BlackMan82/master
Browse files Browse the repository at this point in the history
Added: refresh() function now accepts optional 'data' parameter which ca...
  • Loading branch information
kamilkp committed Mar 25, 2015
2 parents 3f026df + d1cc180 commit 21138a9
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 35 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ example:

- the vsRepeat directive must be applied to a direct parent of an element with `ngRepeat`
- the value of vsRepeat attribute is the single element's height/width measured in pixels. If none provided, the directive will compute it automatically


###OPTIONAL PARAMETERS (attributes):
- `vs-scroll-parent="selector"` - selector to the scrollable container. The directive will look for a closest parent matching the given selector (defaults to the current element)
Expand All @@ -57,7 +57,8 @@ example:
- `vs-excess="value"` - an integer number representing the number of elements to be rendered outside of the current container's viewport (defaults to 2)
- `vs-size-property="propertyName"` - a property name of the items in collection that is a number denoting the element size (in pixels)
- `vs-autoresize` - use this attribute without `vs-size-property` and without specifying element's size. The automatically computed element style will readjust upon window resize if the size is dependable on the viewport size
- `vs-scroll-settings` - an object with 2 possible properties: `scrollIndex: "value"` - index of the item that should be scrolled to; the exact position of this item in the viewport may be further defined by `scrollIndexPosition: "value"` - a position where the element at `scrollIndex` index will be scrolled to; either a number of pixels or one of the following strings: 'top', 'middle', 'bottom', 'inview' is the same as 'inview#top', 'inview#middle', 'inview#bottom', 'inview#auto'; the 'inview#\<position\>' settings means that if the item is already in the view, nothing is scrolled, but if it is not, then the item will be scrolled accordingly (to be in the \<position\>); position 'auto' means that it will be either 'top' or 'bottom' depending on what is closer to the current item position

###EVENTS:
- `vsRepeatTrigger` - an event the directive listens for to manually trigger reinitialization
- `vsRepeatReinitialized` - an event the directive emits upon reinitialization done
- `vsRepeatTrigger` - an event the directive listens for to manually trigger reinitialization; it may receive additional argument - an object with two properties: `scrollIndex` and `scrollIndexPosition` - their meaning is the same as in the optional attribute `vs-scroll-settings`
- `vsRepeatReinitialized` - an event the directive emits upon reinitialization done; the listener may accepts three arguments: `event`, `startIndex` and `endIndex`
266 changes: 235 additions & 31 deletions src/angular-vs-repeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@
positioningPropertyTransform = $$horizontal ? 'translateX' : 'translateY',
positioningProperty = $$horizontal ? 'left' : 'top',

localScrollTrigger = false,

clientSize = $$horizontal ? 'clientWidth' : 'clientHeight',
offsetSize = $$horizontal ? 'offsetWidth' : 'offsetHeight',
scrollPos = $$horizontal ? 'scrollLeft' : 'scrollTop';
Expand All @@ -138,10 +140,23 @@
$scope.offsetBefore = 0;
$scope.offsetAfter = 0;
$scope.excess = 2;
$scope.scrollSettings = {
scrollIndex: 0,
scrollIndexPosition: 'top',
};

$scope.$watch($attrs.vsScrollSettings, function(newValue, oldValue) {
if (typeof newValue === 'undefined') {
return;
}
$scope.scrollSettings = newValue;
reinitialize($scope.scrollSettings);
}, true);

Object.keys(attributesDictionary).forEach(function(key){
if($attrs[key]){
$attrs.$observe(key, function(value){
// '+' serves for getting a number from the string as the attributes are always strings
$scope[attributesDictionary[key]] = +value;
reinitialize();
});
Expand All @@ -154,7 +169,7 @@
refresh();
});

function refresh(){
function refresh(event, data){
if(!originalCollection || originalCollection.length < 1){
$scope[collectionName] = [];
originalLength = 0;
Expand All @@ -179,7 +194,7 @@
setAutoSize();
}

reinitialize();
reinitialize(data);
}

function setAutoSize(){
Expand Down Expand Up @@ -268,8 +283,16 @@
$scope.endIndex = 0;

$scrollParent.on('scroll', function scrollHandler(e){
if(updateInnerCollection())
$scope.$apply();
// Check if the scrolling was triggerred by a local action to avoid
// unnecessary inner collection updating
if (localScrollTrigger) {
localScrollTrigger = false;
}
else {
if(updateInnerCollection()) {
$scope.$apply();
}
}
});

if(isMacOS){
Expand Down Expand Up @@ -305,15 +328,15 @@

var _prevStartIndex,
_prevEndIndex;
function reinitialize(){
function reinitialize(data){
_prevStartIndex = void 0;
_prevEndIndex = void 0;
updateInnerCollection();
updateInnerCollection(data);
resizeFillElement(sizesPropertyExists ?
$scope.sizesCumulative[originalLength] :
$scope.elementSize*originalLength
);
$scope.$emit('vsRepeatReinitialized');
$scope.$emit('vsRepeatReinitialized', $scope.startIndex, $scope.endIndex);
}

function resizeFillElement(size){
Expand Down Expand Up @@ -363,39 +386,220 @@
reinitOnClientHeightChange();
});

function updateInnerCollection(){
if(sizesPropertyExists){
$scope.startIndex = 0;
while($scope.sizesCumulative[$scope.startIndex] < $scrollParent[0][scrollPos] - $scope.offsetBefore)
$scope.startIndex++;
if($scope.startIndex > 0) $scope.startIndex--;
// Scroll to required position
// scrollTo - number of pixels to be scrolled to
function scrollToPosition(scrollTo) {
var scrolled = false;
if (scrollTo !== undefined && (typeof scrollTo) === 'number') {
// Set the position to be scrolled to
scrolled = Math.max(scrollTo, 0);

// Is there a scroll change?
if ($scrollParent[0][scrollPos] !== scrolled) {
$scrollParent[0][scrollPos] = scrolled;
localScrollTrigger = true;
}
else {
scrolled = false;
}

$scope.endIndex = $scope.startIndex;
while($scope.sizesCumulative[$scope.endIndex] < $scrollParent[0][scrollPos] - $scope.offsetBefore + $scrollParent[0][clientSize])
$scope.endIndex++;
// Emit the event
$scope.$emit('vsRepeatScrolled', scrolled);
}
else{
$scope.startIndex = Math.max(
Math.floor(
($scrollParent[0][scrollPos] - $scope.offsetBefore) / $scope.elementSize + $scope.excess/2
) - $scope.excess,
0
);

$scope.endIndex = Math.min(
$scope.startIndex + Math.ceil(
$scrollParent[0][clientSize] / $scope.elementSize
) + $scope.excess,
originalLength
);
return scrolled;
}

function updateInnerCollection(data){

var scrollChange = true,
position,
visibleStartIndex;

if (data && data.scrollIndex !== undefined) {
if (typeof $scope.scrollSettings !== 'undefined') {
$scope.scrollSettings.scrollIndex = data.scrollIndex;
}

// Item scroll position relative to the view, i.e. position === 0 means the top of the view,
// position === $scrollParent[0][clientSize] means the bottom
if (data.scrollIndexPosition !== undefined) {
if (typeof $scope.scrollSettings !== 'undefined') {
$scope.scrollSettings.scrollIndexPosition = data.scrollIndexPosition;
}
position = 0;
switch (typeof data.scrollIndexPosition) {
case 'number':
position = data.scrollIndexPosition + $scope.offsetBefore;
break;
case 'string':
switch (data.scrollIndexPosition) {
case 'top':
position = $scope.offsetBefore;
break;
case 'middle':
position = ($scrollParent[0][clientSize] - $scope.sizes[data.scrollIndex]) / 2;
break;
case 'bottom':
position = $scrollParent[0][clientSize] - $scope.sizes[data.scrollIndex] - $scope.offsetAfter;
break;
case 'inview':
case 'inview#top':
case 'inview#middle':
case 'inview#bottom':
case 'inview#auto':
// The item is in the viewport, do nothing
if (
($scrollParent[0][scrollPos] <= ($scope.sizesCumulative[data.scrollIndex])) &&
($scrollParent[0][scrollPos] + $scrollParent[0][clientSize] - $scope.sizes[data.scrollIndex] >= $scope.sizesCumulative[data.scrollIndex])) {
scrollChange = false;
// The current item scroll position
position = $scope.sizesCumulative[data.scrollIndex] - $scrollParent[0][scrollPos];
}
// The item is out of the viewport
else {
if (data.scrollIndexPosition === 'inview#top' || data.scrollIndexPosition === 'inview') {
// Get it at the top
position = $scope.offsetBefore;
}
if (data.scrollIndexPosition === 'inview#bottom') {
// Get it at the bottom
position = $scrollParent[0][clientSize] - $scope.sizes[data.scrollIndex] + $scope.offsetAfter;
}
if (data.scrollIndexPosition === 'inview#middle') {
// Get it at the middle
position = ($scrollParent[0][clientSize] - $scope.sizes[data.scrollIndex]) / 2;
}
if (data.scrollIndexPosition === 'inview#auto') {
// Get it at the bottom or at the top, depending on what is closer
if ($scrollParent[0][scrollPos] <= $scope.sizesCumulative[data.scrollIndex]) {
position = $scrollParent[0][clientSize] - $scope.sizes[data.scrollIndex] + $scope.offsetAfter;
}
else {
position = $scope.offsetBefore;
}
}
}
break;
default:
console.warn('Incorrect scrollIndexPosition string value');
break;
}
break;
default:
console.warn('Incorrect scrollIndexPosition type');
break;
}
}
else {
// The item is not required to be in the viewport, do nothing
scrollChange = false;
// The current item scroll position
position = $scope.sizesCumulative[data.scrollIndex] - $scrollParent[0][scrollPos];
}

$scope.startIndex = data.scrollIndex;

if(sizesPropertyExists){

while($scope.sizesCumulative[$scope.startIndex] > $scope.sizesCumulative[data.scrollIndex] - position)
{
$scope.startIndex--;
}
// The real first item in the view
visibleStartIndex = $scope.startIndex;
// Adjust the start index according to the excess
$scope.startIndex = Math.max(
Math.floor($scope.startIndex - ($scope.excess / 2)),
0
);

$scope.endIndex = $scope.startIndex;
while($scope.sizesCumulative[$scope.endIndex] < $scope.sizesCumulative[visibleStartIndex] - $scope.offsetBefore + $scrollParent[0][clientSize]) {
$scope.endIndex++;
}
// Adjust the end index according to the excess
$scope.endIndex = Math.min(
Math.ceil($scope.endIndex + ($scope.excess / 2)),
originalLength
);

}
else {

while(($scope.startIndex * $scope.elementSize) > (data.scrollIndex * $scope.elementSize) - position)
{
$scope.startIndex--;
}
// The real first item in the view
visibleStartIndex = $scope.startIndex;
$scope.startIndex = Math.max(
Math.floor($scope.startIndex - ($scope.excess / 2)),
0
);

$scope.endIndex = Math.min(
$scope.startIndex + Math.ceil($scrollParent[0][clientSize] / $scope.elementSize) + $scope.excess / 2,
originalLength
);

}
}
else {

if(sizesPropertyExists){
$scope.startIndex = 0;
while($scope.sizesCumulative[$scope.startIndex] < $scrollParent[0][scrollPos] - $scope.offsetBefore) {
$scope.startIndex++;
}
if($scope.startIndex > 0) { $scope.startIndex--; }
// Adjust the start index according to the excess
$scope.startIndex = Math.max(
Math.floor($scope.startIndex - $scope.excess / 2),
0
);

$scope.endIndex = $scope.startIndex;
while($scope.sizesCumulative[$scope.endIndex] < $scrollParent[0][scrollPos] - $scope.offsetBefore + $scrollParent[0][clientSize]) {
$scope.endIndex++;
}
// Adjust the end index according to the excess
$scope.endIndex = Math.min(
Math.ceil($scope.endIndex + $scope.excess / 2),
originalLength
);
}
else{
$scope.startIndex = Math.max(
Math.floor(
($scrollParent[0][scrollPos] - $scope.offsetBefore) / $scope.elementSize + $scope.excess / 2
) - $scope.excess,
0
);

$scope.endIndex = Math.min(
$scope.startIndex + Math.ceil(
$scrollParent[0][clientSize] / $scope.elementSize
) + $scope.excess,
originalLength
);
}
}

var scrolled = false;
if (data !== undefined && data.scrollIndex !== undefined && position !== undefined && scrollChange) {
// Scroll to the requested position
scrolled = scrollToPosition($scope.sizesCumulative[data.scrollIndex] - position);
}

var digestRequired = $scope.startIndex !== _prevStartIndex || $scope.endIndex !== _prevEndIndex;

if(digestRequired)
if(digestRequired) {
$scope[collectionName] = originalCollection.slice($scope.startIndex, $scope.endIndex);

// Emit the event
$scope.$emit('vsRepeatInnerCollectionUpdated', $scope.startIndex, $scope.endIndex, _prevStartIndex, _prevEndIndex);
}

_prevStartIndex = $scope.startIndex;
_prevEndIndex = $scope.endIndex;

Expand Down
Loading

0 comments on commit 21138a9

Please sign in to comment.