Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added: refresh() function now accepts optional 'data' parameter which ca... #32

Merged
merged 4 commits into from
Mar 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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