Skip to content

Commit

Permalink
Merge pull request #1937 from alphagov/accordion-heading-id-functiona…
Browse files Browse the repository at this point in the history
…lity

Add anchor navigation feature to accordions
  • Loading branch information
owenatgov authored Feb 18, 2021
2 parents b227195 + 368f4a0 commit 3bd03cc
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 110 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

* Fix cookie banner preview in the component guide ([PR #1935](https://github.com/alphagov/govuk_publishing_components/pull/1935))
* Implements scroll tracking for the covid pages ([PR #1942](https://github.com/alphagov/govuk_publishing_components/pull/1942))
* Add anchor navigation feature to accordions ([PR #1937](https://github.com/alphagov/govuk_publishing_components/pull/1937))

## 24.2.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};

GemAccordion.prototype.start = function ($module) {
this.$module = $module[0]
this.sectionClass = 'gem-c-accordion__section'
this.moduleId = this.$module.getAttribute('id')
this.sections = this.$module.querySelectorAll('.gem-c-accordion__section')
this.sections = this.$module.querySelectorAll('.' + this.sectionClass)
this.openAllButton = ''
this.browserSupportsSessionStorage = helper.checkForSessionStorage()
this.controlsClass = 'gem-c-accordion__controls'
Expand All @@ -36,6 +37,12 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
this.initControls()
this.initSectionHeaders()

// Feature flag for anchor tag navigation used on manuals
if (this.$module.getAttribute('data-anchor-navigation')) {
this.openByAnchorOnLoad()
this.addEventListenersForAnchors()
}

// See if "Show all sections" button text should be updated
var areAllSectionsOpen = this.checkIfAllSectionsOpen()
this.updateOpenAllButton(areAllSectionsOpen)
Expand All @@ -59,7 +66,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
accordionControls.appendChild(this.openAllButton)
this.$module.insertBefore(accordionControls, this.$module.firstChild)

// Build addtional wrapper for open all toggle text, place icon after wrapped text.
// Build additional wrapper for open all toggle text, place icon after wrapped text.
var wrapperOpenAllText = document.createElement('span')
wrapperOpenAllText.classList.add(this.openAllTextClass)
this.openAllButton.insertBefore(wrapperOpenAllText, this.openAllButton.childNodes[0] || null)
Expand All @@ -74,6 +81,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
nodeListForEach(this.sections, function (section, i) {
// Set header attributes
var header = section.querySelector('.' + this.sectionHeaderClass)

this.initHeaderAttributes(header, i)
this.setExpanded(this.isExpanded(section), section)

Expand Down Expand Up @@ -106,12 +114,12 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
srPause.classList.add('govuk-visually-hidden')
srPause.innerHTML = ', '

// Build addtional copy for assistive technology
var srAddtionalCopy = document.createElement('span')
srAddtionalCopy.classList.add('govuk-visually-hidden')
srAddtionalCopy.innerHTML = ' this section'
// Build additional copy for assistive technology
var srAdditionalCopy = document.createElement('span')
srAdditionalCopy.classList.add('govuk-visually-hidden')
srAdditionalCopy.innerHTML = ' this section'

// Build addtional wrapper for toggle text, place icon after wrapped text.
// Build additional wrapper for toggle text, place icon after wrapped text.
var wrapperShowHideIcon = document.createElement('span')
var icon = document.createElement('span')
icon.classList.add(this.upChevonIconClass)
Expand All @@ -138,7 +146,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
}

button.appendChild(showIcons)
button.appendChild(srAddtionalCopy)
button.appendChild(srAdditionalCopy)
}

// When section toggled, set and store state
Expand Down Expand Up @@ -283,5 +291,41 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
}
}

// Navigate to and open accordions with anchored content on page load if a hash is present
GemAccordion.prototype.openByAnchorOnLoad = function () {
if (window.location.hash && this.$module.querySelector(window.location.hash)) {
this.openForAnchor(window.location.hash)
}
}

// Add event listeners for links to open accordion sections when navigated to using said anchor links on the page
// Adding an event listener to all anchor link a tags in an accordion is risky but we circumvent this risk partially by only being a layer of accordion behaviour instead of any sort of change to link behaviour
GemAccordion.prototype.addEventListenersForAnchors = function () {
var links = this.$module.querySelectorAll('.' + this.sectionInnerContent + ' a[href*="#"]')

links.forEach(function (link) {
if (link.pathname === window.location.pathname) {
link.addEventListener('click', this.openForAnchor.bind(this, link.hash))
}
}.bind(this))
}

// Find the parent accordion section for the given id and open it
GemAccordion.prototype.openForAnchor = function (hash) {
var target = document.querySelector(hash)
var section = this.getContainingSection(target)

this.setExpanded(true, section)
}

// Loop through the given ids ancestors until the parent section class is found
GemAccordion.prototype.getContainingSection = function (target) {
while (!target.classList.contains(this.sectionClass)) {
target = target.parentElement
}

return target
}

Modules.GemAccordion = GemAccordion
})(window.GOVUK.Modules)
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
id ||= "default-id-#{SecureRandom.hex(4)}"
items ||= []
condensed ||= false
anchor_navigation ||= false

accordion_classes = %w(gem-c-accordion)
accordion_classes << 'gem-c-accordion--condensed' if condensed
accordion_classes << (shared_helper.get_margin_bottom)

data_attributes ||= {}
data_attributes[:module] = 'gem-accordion'
data_attributes[:anchor_navigation] = anchor_navigation
%>
<% if items.any? %>
<%= tag.div(class: accordion_classes, id: id, data: data_attributes) do %>
<% items.each_with_index do |item, i| %>
<%
index = i + 1

item[:data_attributes] ||= nil

section_classes = %w(gem-c-accordion__section)
section_classes << 'gem-c-accordion__section--expanded' if item[:expanded]

Expand All @@ -29,7 +29,7 @@

<%= tag.section(class: section_classes) do %>
<div class="gem-c-accordion__section-header">
<%= content_tag(shared_helper.get_heading_level, class: 'gem-c-accordion__section-heading') do %>
<%= content_tag(shared_helper.get_heading_level, class: 'gem-c-accordion__section-heading', id: item[:heading][:id]) do %>
<%= tag.span(item[:heading][:text], id: "#{id}-heading-#{index}", data: item[:data_attributes], class: 'gem-c-accordion__section-button') %>
<% end %>
<%= tag.div(item[:summary][:text], id: "#{id}-summary-#{index}", class: summary_classes) if item[:summary].present? %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,35 @@ examples:
text: "How people read"
content:
html: "<p class='govuk-body'>This is the content for How people read.</p>"
with_the_anchor_link_navigation:
description: |
Some apps require custom ids per accordion section heading for linking between those specific sections, sometimes across multiple pages. An example of this is on manuals pages where multiple manuals will each include large sets of accordions and will reference between specific sections for ease of access to that content. [Live example](https://www.gov.uk/guidance/how-to-publish-on-gov-uk/creating-and-updating-pages#associations).
This feature automatically opens accordions when an anchor link is clicked within another accordion that links to either the id of an accordion section heading or an id within the content of an accordion. This will also automatically navigate to and open accordions on page load using the same rules.
This feature won't be used if the `anchor_navigation` flag isn't passed as true to mitigate performance risk from event listeners on a large number of links.
Unlike with the accordion-wide custom id attribute, any ids passed to accordion headings as part of this feature aren't stored in `localStorage` so don't need to be unique across your domain, but **should still be unique in the context of the page**.
data:
anchor_navigation: true
items:
- heading:
text: "Writing well for the web"
id: "writing-well-for-the-web"
content:
html: "<p class='govuk-body'>This is the content for Writing well for the web.</p>"
- heading:
text: "Writing well for specialists"
content:
html: "<p class='govuk-body'>This is the content for Writing well for specialists.</p>"
- heading:
text: "Know your audience"
content:
html: "<p class='govuk-body'>This is the content for Know your audience.</p>"
- heading:
text: "How people read"
content:
html: "<p class='govuk-body'>This is the content for How people read.</p>"
condensed_layout:
description: |
This is for when a smaller accordion is required. Since smaller screens trigger a single column layout, this modifier only makes the accordion smaller when viewed on large screens.
Expand Down
Loading

0 comments on commit 3bd03cc

Please sign in to comment.