Skip to content

Instantly share code, notes, and snippets.

@Poordeveloper
Last active August 18, 2016 15:14
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Poordeveloper/e6a1714ea399f95c779f to your computer and use it in GitHub Desktop.
Save Poordeveloper/e6a1714ea399f95c779f to your computer and use it in GitHub Desktop.
m.directive('ionSticky', ['$ionicPosition', '$compile', '$timeout', function ($ionicPosition, $compile, $timeout) {
return {
restrict: 'A',
require: '^$ionicScroll',
link: function ($scope, $element, $attr, $ionicScroll) {
var scroll = angular.element($ionicScroll.element);
var clone;
// creates the sticky clone and adds it to DOM
var createStickyClone = function ($element) {
clone = $element.clone().css({
position: 'absolute',
top: $ionicPosition.position(scroll).top + "px",
left: 0,
right: 0
});
clone[0].className += " assertive";
clone.removeAttr('ng-repeat-start');
clone.removeAttr('ng-if');
scroll.parent().append(clone);
// compile the clone so that anything in it is in Angular lifecycle.
$compile(clone)($scope);
};
var removeStickyClone = function () {
if (clone)
clone.remove();
clone = null;
};
$scope.$on("$destroy", function () {
// remove the clone, unbind the scroll listener
removeStickyClone();
angular.element($ionicScroll.element).off('scroll');
});
var lastActive;
var updateSticky = ionic.throttle(function() {
var active = null;
var dividers = [];
var tmp = $element[0].getElementsByClassName("item-divider");
for (var i = 0; i < tmp.length; ++i) dividers.push(angular.element(tmp[i]));
if (dividers.length == 0) return;
if (!clone) createStickyClone(angular.element(dividers[0][0]))
dividers.sort(function(a, b) { return $ionicPosition.offset(a).top - $ionicPosition.offset(b).top; });
var sctop = $ionicPosition.offset(scroll).top;
if ($ionicPosition.offset(dividers[0]).top - sctop - dividers[0].prop('offsetHeight') > 0) {
var letter = dividers[0][0].innerHTML.trim();
var i = $scope.letters.indexOf(letter);
if (i == 0) return;
active = $scope.letters[i-1];
} else for (var i = 0; i < dividers.length; ++i) { // can be changed to binary search
if ($ionicPosition.offset(dividers[i]).top - sctop - dividers[i].prop('offsetHeight') < 0) { // this equals to jquery outerHeight
if (i === dividers.length-1 || $ionicPosition.offset(dividers[i+1]).top - sctop -
(dividers[i].prop('offsetHeight') + dividers[i+1].prop('offsetHeight')) > 0) {
active = dividers[i][0].innerHTML.trim();
break;
}
}
}
if (lastActive != active) {
if (active) clone[0].innerHTML = active;
if (lastActive) {
var e = scroll.parent()[0].getElementsByClassName(lastActive);
if (e && e[0]) e[0].className = e[0].className.replace(/assertive/g,'');
}
if (active) {
var e = scroll.parent()[0].getElementsByClassName(active);
if (e && e[0]) e[0].className += " assertive";
}
lastActive = active;
}
}, 200);
scroll.on('scroll', function (event) {
updateSticky();
});
}
}
}]);
@Poordeveloper
Copy link
Author

Please note that, it depends on $scope.letters which lists out all available dividers' innerHTML because colletion-repeat only renders visible list items. It only support simple dividers with the same style.

@boostup
Copy link

boostup commented Oct 17, 2015

Hello and thanks for this great directive !

Could you take a look at this ionic playground (similar to a jsfiddle or codepen, with HTML/JS/CSS tabs):
http://play.ionic.io/app/85ddaf5f1b64

I found your gist for the "collection-repeat" at https://gist.github.com/Poordeveloper/e6a1714ea399f95c779f
and used this code to define the "ion-sticky" directive (look at the JS tab, you'll find it under the 'MyCtrl' controller definition)

So here go the 2 problems I'm encountering:

  1. The directive throws an error when using "collection-repeat" to generate the list, which crashes the app
  2. The directive does NOT affect the list dividers, nor throws any errors when using "ng-repeat" to generate the list

Could you please check and let me know if this is a known issue to you (I see there are no issue on the github repo) ?

Thanx !

@degamer106
Copy link

I tried this gist with collection-repeat but it has a bug :(.

For example, while scrolling through a Contacts list with last name's beginning with 'C', sometimes the sticky divider shows P, O, or R (Btw, this happens when I'm in the middle of the list of C's and the 'D' item-divider isn't anywhere near the top). I've used the original implementation with ng-repeat with the same data + HTML markup and it works perfectly fine so I'm fairly certain its not my code.

I did a do a bit of debugging with print statements. For the scenario I mentioned above, I noticed that the item-dividers that are supposed to contain 'C' are not in the list of dividers returned from $element[0].getElementsByClassName("item-divider"). It seems like the recycling nature of collection-repeat causes some rows to be deleted so there's no way for it to find the right item-divider.

http://play.ionic.io/app/e073e099014e

@Poordeveloper
Copy link
Author

@egamer106, I did get this bug too.

@archy-bold
Copy link

Is there a solution to this bug or is it near impossible to sort for collection-repeat?

@archy-bold
Copy link

Think I have a fix for this. Divider elements can sometimes be in the DOM but not actually be positioned in the list. You can see this because they have a transform: translate3d(-9999px, -9999px, 0px) applied to the parent. If you exclude these elements from the dividers list, you'll only consider dividers that are actually visible.

Replace line 44:

for (var i = 0; i < tmp.length; ++i) dividers.push(angular.element(tmp[i]));

With:

for (var i = 0; i < tmp.length; ++i){
    // Get the parent's transform value.
    var elt = angular.element(tmp[i]);
    var parentTransform = elt.parent().css('transform');
    // Exclude dividers positioned out of the DOM
    if (!parentTransform || parentTransform.indexOf("-9999") == -1){
        dividers.push(elt);
    }
}

@RameshkrishnanV
Copy link

i used element with ng-repeat its working, but it loops ng-repeat 8 times

@bdirito
Copy link

bdirito commented Jun 8, 2016

@archy-bold

That doesnt always work. collection-repeat has the capacity to add or remove elements to the dom as it scrolls up and gets 'too old'

My version: https://gist.github.com/bdirito/e25b8fafe7363a0662e17d3b96a61ffa

It assumes that if something got removed from the dom thats simply an artifact of collection-repeat and not a removal from the underlying repeat object (valid in my case). It has several other modifications for things that I was personally interested in. Its also in coffeescript with ngInject, requirejs(amd) and jquery dependencies. However it should get people an idea.

@noveltys
Copy link

hi, the plugin works just great out of the box with collection-repeat with cache disabled, but when the view is cached it doesn't do the job anymore, i.e when i select an item in the list and then press the back button to return to the list, it is not effective.

Any suggestion about this ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment