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

Highlight anchors in the page contents navigation, on scroll #61

Merged
merged 10 commits into from
Oct 27, 2016

Conversation

gemmaleigh
Copy link
Contributor

@gemmaleigh gemmaleigh commented Sep 30, 2016

This PR adds a HighlightActiveSectionHeading module.

This will highlight an anchor link in the "Page contents" left hand navigation, when a user has scrolled past the corresponding heading in the main content area.

This PR also uses the GOVUK.StickAtTopWhenScrolling JS, to "stick" the navigation, once the user has scrolled past the left hand page contents and to "unstick" for smaller viewports.

Steps for this PR:

  • Merge the changes to GOVUK.StickAtTopWhenScrolling in the govuk_frontend_toolkit
  • Bump the version of the govuk_frontend_toolkit
  • Update the version of the govuk_frontend_toolkit that service-manual-frontend is using
  • Remove commits:
    45ebd8b - duplicating duplicate the GOVUK.StickAtTopWhenScrolling() JS
    and
    a5f8f41 - revert to use govuk/stick-at-top rather than temp/stick-at-top

@gemmaleigh gemmaleigh changed the title Add nav highlight [Do not merge, WIP] Highlight anchors in the page contents navigation, on scroll. [Also GOVUK.StickAtTopWhenScrolling demo]. Sep 30, 2016
Copy link
Contributor

@thehenster thehenster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just really looked at the test btw. Sorry.

expect($anchor.hasClass('active')).toBe(true)
})

it('highlights a nav item, when the setActiveItem function is passed a href', function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this test covered by the test above this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's a duplicate - I have removed it.

}
}
module.start($element)
var $anchor = $element.find('.js-page-contents a')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good if we were more explicit about which link has the 'active' class, rather than assert that one of the links is active.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a check here for the third item, as this has the active class.

</div>\
</div>\
</div>\
</div>')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering how much of this fixture is useful. We're faking the positions of the window scroll and elements.

Maybe a more minimal fixture would:

a) make it easier to read what markup is required
b) would be just as valid a test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've simplified the fixture to make it easier to read.

'use strict'

var GOVUK = window.GOVUK
var $ = window.$
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do? Was $ out of scope without it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed this, it was copied from another example.

}
var testHref = '#section-3'
module.start($element)
isLinkHighlighted(testHref)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably pass the string literal directly rather than assign it to a variable here.

</div>\
</div>\
</div>\
</div>')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a style thing for fun.. It's quite popular to break tests into paragraphs - setup, exercise and verification paragraphs. It just means putting a line break in between the paragraphs.

eg.

it('has no highlighted nav items, when the page loads', function () {
  setWindowScroll(0)

  module.start($element)

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

I also think it'd be cool to hide the complexity of stubbing the window and element position things in a helper function. I had a go in the above example.

None of this essential at all.. Just a suggestion for how I would find it more readable.

@gemmaleigh gemmaleigh force-pushed the add-nav-highlight branch 2 times, most recently from 37007e5 to 981905a Compare October 3, 2016 13:03
Copy link
Contributor

@36degrees 36degrees left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor things that jumped out at me. I must admit, I find the updateActiveNavItem function a tad hard to grok. But don't have any particular suggestions on how to fix that…

$window.scroll(self.hasScrolled)

setInterval(self.checkResize, _interval)
setInterval(self.checkScroll, _interval)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we're polling at intervals rather than binding directly to the scroll events? If it's to throttle/debounce the scroll event, have we ruled out using e.g. https://github.com/cowboy/jquery-throttle-debounce?

Either way, it'd be good to document why we're doing this as it seems a bit odd to me and difficult to follow…

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the example over here sticky-element-container.js, as it works in a similar way. If there's a better way to do this I am happy to amend.


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is totally personal preference, but I've moved to using e.g. .on('scroll', …) as I think it makes it a million times clearer that you're setting up a new binding, vs telling the window to resize or scroll itself.


self.$anchors = $el.find('.js-page-contents a')
self.totalAnchors = self.$anchors.length
self.getAnchors(self.totalAnchors)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAnchors is returning anchorIDs, but the result just gets thrown away? I'd suggest either removing the return from getAnchors to make it clear it's manipulating a variable outside of its own scope, or assigning the result to anchorIDs here.

self.getAnchors(self.totalAnchors)

self.checkResize()
self.checkScroll(self.totalAnchors)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkScroll isn't expecting any arguments?

self.getAnchors = function (totalAnchors) {
for (var i = 0; i < self.totalAnchors; i++) {
var anchor = self.$anchors[i]
var anchorID = $(anchor).attr('href')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If $anchors is an array of jQuery objects, do you need to call jQuery again? anchor.attrshould work? If so, worth naming anchor $anchor instead?

self.updateActiveNavItem = function () {
var windowVerticalPosition = self.getWindowPositions().scrollTop

for (var i = 0; i < self.totalAnchors; i++) {
Copy link
Contributor

@36degrees 36degrees Oct 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we're opting for this vs jQuery's .each, as we have jQuery loaded anyway?

@gemmaleigh gemmaleigh force-pushed the add-nav-highlight branch 2 times, most recently from 4d20135 to 54b73b8 Compare October 3, 2016 13:27
@36degrees
Copy link
Contributor

The commits since my last review all look good. I think my first two points are still outstanding though?

@36degrees
Copy link
Contributor

36degrees commented Oct 7, 2016

There are a few really picky edge cases:

  • If you scroll really quickly to e.g. the top of the document it doesn't quite keep up and you can end up with the wrong thing highlighted.
  • If you have a very short section at the end of your document it'll never become active as the previous heading doesn't leave the screen. This is especially odd if you click the heading to scroll down as the previous link becomes active instead.

However I think those really are edge cases and I'd be happy for this to go out as is, pending the two comments above.

@gemmaleigh gemmaleigh changed the title Highlight anchors in the page contents navigation, on scroll. [Also GOVUK.StickAtTopWhenScrolling demo]. Highlight anchors in the page contents navigation, on scroll. Oct 12, 2016
@gemmaleigh
Copy link
Contributor Author

gemmaleigh commented Oct 12, 2016

This can't be merged just yet, the govuk_frontend_toolkit's stick-at-top-when-scrolling.js needs updating to resize the shim it inserts when the .content-fixed class is added.

The bug spotted by @36degrees, means that when the screen is resized, the shim isn't, so the content in the page contents list is wider than it should be (as the shim prevents the grid-column from resizing the content), leading to the main content overlapping the page contents, at widths wider than 768px.

@gemmaleigh gemmaleigh changed the title Highlight anchors in the page contents navigation, on scroll. Highlight anchors in the page contents navigation, on scroll [Do not merge] Oct 12, 2016
@gemmaleigh
Copy link
Contributor Author

I'm now checking for the width of the parent element (in this case .column-third) and using this to set the width of the element (.page-contents) and the shim .shim.

This can't be added to the front end toolkit as-is (as there's no guarantee a sticky element will exist within a grid column).

Will have to have a think about how best to add this to our app. It may well be better to use our own version of stick-at-top-when-scrolling in this case.

@36degrees
Copy link
Contributor

The only thing I've thought of is to modify the Javsacript in front end toolkit to look for a data attribute (or extra class, or similar) on the 'stuck thing' and change its behaviour based on the presence of that attribute.

But I don't object to just adding a modified version to our app if that makes more sense for now.

Either way, it'd be good to see this merged!

This already exists within the frontend toolkit, use both stick at top
when scrolling (to set the page contents to position:fixed) and stop
scrolling at footer, to return the page contents to position: static
when the top of the footer reaches the bottom of the page contents.
The navigation highlight module uses this hook for the
in-page navigation (js-page-contents).

Also add a js hook to the page contents area
This is required to get the stick-at-top-when-scrolling js to work.

Stick at top when scrolling also has a futher optional hook to set the
sticky element and the shim to resize to fit the parent (the hook for this is js-sticky-resize).
@gemmaleigh gemmaleigh changed the title Highlight anchors in the page contents navigation, on scroll [Do not merge] Highlight anchors in the page contents navigation, on scroll Oct 25, 2016
gemmaleigh and others added 6 commits October 26, 2016 12:17
These are required by GOVUK.stickAtTopWhenScrolling.

Add extra padding on to the bottom of the page contents list, so that
it doesn’t allow the Is there anything wrong with this page? link to
overlap the bottom of the page contents list.
When the page is scrolled, this will highlight an item in the left hand page contents.
- it has no highlighted nav items, when the page loads
- it highlights a nav item, when the page is scrolled

Mock the values for scroll position and heading position, and also
footer position to ensure the last item is highlighted.
The body of a guide is rendered using a shared ‘GovSpeak’ component.

In test mode shared components are not fetched from Static. Instead they are rendered as a dummy tag which contains a JSON dump of the locals - the arguments passed to the component.

This means that the headings we expect to be present on the page (within the body of the guide) are not present. This which was causing JavaScript errors on the page, which were appearing in unrelated tests that used the Javascript driver to execute.

By returning early if we cannot get the offset of a heading we guard against this.
This is so that when a page contents anchor is clicked on, the title
“Page contents” and the title of the section will line up - rather than
the title of the section sitting at the very top of the page.
@gemmaleigh
Copy link
Contributor Author

I've also adjusted the margins for the govspeak content, so when an item in the left hand navigation is selected, the "Page contents" title and the section heading line up.

Before screenshot:
before

After screenshot:
who-the-community-after

@36degrees
Copy link
Contributor

Looks good to me!

@gemmaleigh
Copy link
Contributor Author

It would be great to get this merged! @36degrees could you please deploy it to integration?

@36degrees 36degrees merged commit 51c41a5 into master Oct 27, 2016
@36degrees 36degrees deleted the add-nav-highlight branch October 27, 2016 14:07
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants