-
-
Save nwpappas/9194765 to your computer and use it in GitHub Desktop.
app.directive('scrollSpy', function ($window) { | |
return { | |
restrict: 'A', | |
controller: function ($scope) { | |
$scope.spies = []; | |
this.addSpy = function (spyObj) { | |
$scope.spies.push(spyObj); | |
}; | |
}, | |
link: function (scope, elem, attrs) { | |
var spyElems; | |
spyElems = []; | |
scope.$watch('spies', function (spies) { | |
var spy, _i, _len, _results; | |
_results = []; | |
for (_i = 0, _len = spies.length; _i < _len; _i++) { | |
spy = spies[_i]; | |
if (spyElems[spy.id] == null) { | |
_results.push(spyElems[spy.id] = elem.find('#' + spy.id)); | |
} | |
} | |
return _results; | |
}); | |
$($window).scroll(function () { | |
var highlightSpy, pos, spy, _i, _len, _ref; | |
highlightSpy = null; | |
_ref = scope.spies; | |
// cycle through `spy` elements to find which to highlight | |
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
spy = _ref[_i]; | |
spy.out(); | |
// catch case where a `spy` does not have an associated `id` anchor | |
if (spyElems[spy.id].offset() === undefined) { | |
continue; | |
} | |
if ((pos = spyElems[spy.id].offset().top) - $window.scrollY <= 0) { | |
// the window has been scrolled past the top of a spy element | |
spy.pos = pos; | |
if (highlightSpy == null) { | |
highlightSpy = spy; | |
} | |
if (highlightSpy.pos < spy.pos) { | |
highlightSpy = spy; | |
} | |
} | |
} | |
// select the last `spy` if the scrollbar is at the bottom of the page | |
if ($(window).scrollTop() + $(window).height() >= $(document).height()) { | |
spy.pos = pos; | |
highlightSpy = spy; | |
} | |
return highlightSpy != null ? highlightSpy["in"]() : void 0; | |
}); | |
} | |
}; | |
}); | |
app.directive('spy', function ($location, $anchorScroll) { | |
return { | |
restrict: "A", | |
require: "^scrollSpy", | |
link: function(scope, elem, attrs, affix) { | |
elem.click(function () { | |
$location.hash(attrs.spy); | |
$anchorScroll(); | |
}); | |
affix.addSpy({ | |
id: attrs.spy, | |
in: function() { | |
elem.addClass('active'); | |
}, | |
out: function() { | |
elem.removeClass('active'); | |
} | |
}); | |
} | |
}; | |
}); |
one reason it doesn't work in ie is $window.scrollY. Use pageYOffset instead.
Hi
I got this error trying to implement your code.
Error: [$compile:ctreq] Controller 'scrollSpy', required by directive 'spy', can't be found!
AngularJS docs says that The ^ prefix means that this directive searches for the controller on its parents (without the ^ prefix, the directive would look for the controller on just its own element).
In this case we have controller in scrollSpy directive, still it gives this error. Help please.
Does not work:
Error: [$compile:ctreq] Controller 'scrollSpy', required by directive 'spy', can't be found!
You are missing the scroll-spy
in your markup, it should look like (notice the first line):
<div class="row" scroll-spy>
<div class="col-md-3 sidebar">
<ul>
<li spy="overview">Overview</li>
<li spy="main">Main Content</li>
<li spy="summary">Summary</li>
<li spy="links">Other Links</li>
</ul>
</div>
<div class="col-md-9 content">
<h3 id="overview">Overview</h3>
<!-- overview goes here -->
<h3 id="main">Main Body</h3>
<!-- main content goes here -->
<h3 id="summary">Summary</h3>
<!-- summary goes here -->
<h3 id="links">Other Links</h3>
<!-- other links go here -->
</div>
</div>
I have found a couple of things that may help people trying to get this code to work for them.
- Modify the "click" event handler on the "spy" directive to call scope.$apply().
For example:
elem.click(function () {
scope.$apply(function () {
$location.hash(attrs.spy);
});
});
The call to $apply is in Alxhill's CoffeScript code, so must have accidentally gotten removed when converting to JavaScript.
- If you're using Angular routing (ngRoute / ngView), make sure to set "reloadOnSearch" to false so that the view doesn't reload every time $location.hash is called.
I have implemented this angular scrollspy in my single page application - which also uses ng-route
and dynamically populates the sidenav
based on the h1
-h6
elements within #main
.
See my code on Plnkr: http://plnkr.co/edit/OXyMEACVW5RDx09UG8LT?p=preview
Because my code creates some spy
s without id's I've included a condition in the spy directive:
if (attrs.spy != "") {
affix.addSpy({``
id: attrs.spy,
in: function() {
elem.addClass('active');
},
out: function() {
elem.removeClass('active');
}
});
}
Edit: Does anyone have a working version with nested sidenavs which keeps the .active
on parent elements with .active
children?
in mozilla if i m clicking on a specfic li it assign the active class in the upper li how to resolve this
Big thanks mate!
For this to work with dynamically generated content, i.e., ng-repeat creating the DOM for both the navigation links and the header tags with matching ids, you must add a $timeout or an $evalAsync around the code that actually calls elem.find('#' + spy.id) else those elements don't exist yet and will not work as expected.
@ikend do you fixe the problem? If yes, can you send the solution?
As far as I understand, the $scope.spies.push(spyObj) in the this.addSpy function doesn't triggers the $watch('spies'... (watcher that adds the jquery element on the spyElems list). I'm not a master on angularjs and I cannot guess why this watcher doesn't trigger on a watched variable changes...
Thanks!
I am using this directive, and it works great in Chrome. However, It is not working in IE 11. The active class is not correctly assigned, and I am not able to navigate to the first anchor. Debugged it quite a bit, but not seeing anything to indicate why it is failing.