-
-
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'); | |
} | |
}); | |
} | |
}; | |
}); |
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!
Does not work:
Error: [$compile:ctreq] Controller 'scrollSpy', required by directive 'spy', can't be found!