Skip to content
This repository has been archived by the owner on Mar 6, 2023. It is now read-only.

Commit

Permalink
Merge pull request #61 from alphagov/add-nav-highlight
Browse files Browse the repository at this point in the history
Highlight anchors in the page contents navigation, on scroll
  • Loading branch information
36degrees committed Oct 27, 2016
2 parents 67fa99b + df4b873 commit 51c41a5
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 2 deletions.
6 changes: 6 additions & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
// from govuk_frontend_toolkit
//= require govuk/stick-at-top-when-scrolling
//= require govuk/stop-scrolling-at-footer
//= require_tree .

window.GOVUK.stickAtTopWhenScrolling.init();
window.GOVUK.stopScrollingAtFooter.addEl($('.js-stick-at-top-when-scrolling'));
150 changes: 150 additions & 0 deletions app/assets/javascripts/modules/highlight-active-section-heading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
;(function (Modules, root) {
'use strict'

var $ = root.$
var $window = $(root)

Modules.HighlightActiveSectionHeading = function () {
var self = this
var _hasResized = true
var _hasScrolled = true
var _interval = 50
var anchorIDs = []

self.getWindowDimensions = function () {
return {
height: $window.height(),
width: $window.width()
}
}

self.getWindowPositions = function () {
return {
scrollTop: $window.scrollTop()
}
}

self.getElementOffset = function ($el) {
return $el.offset()
}

self.start = function ($el) {
$window.resize(self.hasResized)
$window.scroll(self.hasScrolled)

setInterval(self.checkResize, _interval)
setInterval(self.checkScroll, _interval)

self.$anchors = $el.find('.js-page-contents a')
self.getAnchors()

self.checkResize()
self.checkScroll()
}

self.hasResized = function () {
_hasResized = true
return _hasResized
}

self.hasScrolled = function () {
_hasScrolled = true
return _hasScrolled
}

self.checkResize = function () {
if (_hasResized) {
_hasResized = false
_hasScrolled = true
}
}

self.checkScroll = function () {
if (_hasScrolled) {
_hasScrolled = false
var windowDimensions = self.getWindowDimensions()
if ( windowDimensions.width <= 768) {
self.removeActiveItem()
} else {
self.updateActiveNavItem()
}
}
}

self.getAnchors = function () {
$.each(self.$anchors, function(i) {
var anchorID = $(this).attr('href')
// e.g. anchorIDs['#meeting-the-digital-service-standard', '#understand-your-users', '#research-continually']
anchorIDs.push(anchorID)
})
}

self.getHeadingPosition = function ($theID) {
return $theID.offset()
}

self.getNextHeadingPosition = function ($theNextID) {
return $theNextID.offset()
}

self.getFooterPosition = function ($theID) {
return $theID.offset()
}

self.getDistanceBetweenHeadings = function (headingPosition, nextHeadingPosition) {
var distanceBetweenHeadings = (nextHeadingPosition - headingPosition)
return distanceBetweenHeadings
}

self.updateActiveNavItem = function () {
var windowVerticalPosition = self.getWindowPositions().scrollTop
var footerPosition = self.getFooterPosition($('#footer'))

$.each(self.$anchors, function(i) {

var theID = anchorIDs[i]
var theNextID = anchorIDs[i + 1]

var $theID = $(theID)
var $theNextID = $(theNextID)

var headingPosition = self.getHeadingPosition($theID)

if (!headingPosition) {
return
}

headingPosition = headingPosition.top
headingPosition = headingPosition - 53 // fix the offset from top of page

if (theNextID) {
var nextHeadingPosition = self.getNextHeadingPosition($theNextID).top
}

var distanceBetweenHeadings = self.getDistanceBetweenHeadings(headingPosition, nextHeadingPosition)
if (distanceBetweenHeadings) {
var isPastHeading = (windowVerticalPosition >= headingPosition && windowVerticalPosition < (headingPosition + distanceBetweenHeadings))
}
// when distanceBetweenHeadings is false (as there isn't a next heading)
else {
var isPastHeading = (windowVerticalPosition >= headingPosition && windowVerticalPosition < footerPosition.top)
}

if (isPastHeading) {
self.setActiveItem(theID)
}

})

}

self.setActiveItem = function (theID) {
self.$anchors.removeClass('active')
self.$anchors.filter("[href='" + theID + "']").addClass('active')
}

self.removeActiveItem = function () {
self.$anchors.removeClass('active')
}
}
})(window.GOVUK.Modules, window)
17 changes: 17 additions & 0 deletions app/assets/stylesheets/modules/_govspeak-wrapper.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,21 @@
@include media(tablet) {
padding-top: 1.875em;
}

.govuk-govspeak p,
.govuk-govspeak ol,
.govuk-govspeak ul {
// govspeak margin bottom is 20px
margin-bottom: $gutter-half;
}

.govuk-govspeak h2 {
margin-top: 15px;
padding-top: 15px;
// govspeak margin-top is 45px
@include media(tablet) {
margin-top: 15px;
padding-top: 30px;
}
}
}
5 changes: 5 additions & 0 deletions app/assets/stylesheets/modules/_page-contents.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

@include media(tablet) {
margin-top: 2em;
padding-bottom: 4em;
}

&__title {
Expand Down Expand Up @@ -35,4 +36,8 @@
text-decoration: underline;
}
}
// Styles required by GOVUK.HighlightActiveNavItem JS
&__list .active {
font-weight: bold;
}
}
4 changes: 2 additions & 2 deletions app/views/content_items/service_manual_guide.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@
</div>
</div>

<div class="grid-row">
<div class="grid-row" data-module="highlight-active-section-heading">
<div class="column-third">

<!-- Page contents -->
<div class="page-contents">
<div class="page-contents js-page-contents js-stick-at-top-when-scrolling js-sticky-resize">
<h2 class="page-contents__title">Page contents:</h2>
<ul class="page-contents__list">
<% @content_item.header_links.each do |header_link| %>
Expand Down
95 changes: 95 additions & 0 deletions spec/javascripts/highlight-active-section-heading-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/* eslint-env jasmine */
/* eslint-disable no-multi-str */

describe('A highlight active section heading module', function () {
'use strict'

var module
var $element

beforeEach(function () {
module = new GOVUK.Modules.HighlightActiveSectionHeading()

$element = $('<div class="grid-row" data-module="highlight-active-section-heading">\
<div class="column-third">\
<div class="page-contents js-page-contents js-stick-at-top-when-scrolling">\
<h2 class="page-contents__title">Page contents:</h2>\
<ul class="page-contents__list">\
<li><a href="#section-1">Section 1</a></li>\
<li><a href="#section-2">Section 2</a></li>\
<li><a href="#section-3">Section 3</a></li>\
</ul>\
</div>\
</div>\
<div class="column-two-thirds">\
<div class="govspeak-wrapper">\
<div class="govuk-govspeak">\
<h2 id="section-1">Section 1</h2>\
<p>Section 1 text</p>\
<h2 id="section-2">Section 2</h2>\
<p>Section 2 text</p>\
<h2 id="section-3">Section 3</h2>\
<p>Section 3 text</p>\
</div>\
</div>\
</div>\
</div>')

module.getWindowDimensions = function () {
return {
height: 768,
width: 1024
}
}
module.getFooterPosition = function () {
return {
top: 500
}
}
module.getHeadingPosition = function () {
return {
top: 100
}
}
module.getNextHeadingPosition = function () {
return {
top: 200
}
}
})

afterEach(function () {
$(document).off()
})

// The anchor link with the href matching testHref should be highlighted
function isLinkHighlighted (testHref) {
var $anchor = $element.find('.js-page-contents a[href="' + testHref + '"]')
expect($anchor.hasClass('active')).toBe(true)
}

it('When the page loads, it has no highlighted nav items', function () {
module.getWindowPositions = function () {
return {
scrollTop: 0
}
}
module.start($element)

var $anchors = $element.find('.js-page-contents a')
expect($anchors.hasClass('active')).toBe(false)
})

it('When the page is scrolled, it highlights a nav item', function () {
module.getWindowPositions = function () {
return {
scrollTop: 180
}
}
module.start($element)

var $anchors = $element.find('.js-page-contents a')

isLinkHighlighted('#section-3')
})
})

0 comments on commit 51c41a5

Please sign in to comment.