Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow initAll to be scoped to a specific part of a page #1216

Merged
merged 4 commits into from
Mar 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@

([PR #1233](https://github.com/alphagov/govuk-frontend/pull/1233))

- Allow initAll to be scoped to a specific part of a page

See ["Initialise GOV.UK Frontend in only certain sections of a page"](docs/installation/installing-with-npm.md#initialise-govuk-frontend-in-only-certain-sections-of-a-page) for more information.

([PR #1216](https://github.com/alphagov/govuk-frontend/pull/1216))

🔧 Fixes:

- Pull Request Title goes here
Expand Down Expand Up @@ -157,7 +163,7 @@
Also thanks to [Malcolm Butler](https://github.com/MoJ-Longbeard) for exploring a [previous version of this fix](https://github.com/alphagov/govuk-frontend/pull/1185).

([PR #1220](https://github.com/alphagov/govuk-frontend/pull/1220))


## 2.7.0 (Feature release)

Expand Down
63 changes: 63 additions & 0 deletions app/views/examples/scoped-initialisation/index.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{% extends "template.njk" %}

{% from "checkboxes/macro.njk" import govukCheckboxes %}

{% block head %}
<!--[if !IE 8]><!-->
<link rel="stylesheet" href="/public/app.css">
<!--<![endif]-->
<!--[if IE 8]>
<link rel="stylesheet" href="/public/app-ie8.css">
<![endif]-->
<!--[if lt IE 9]>
<script src="/vendor/html5-shiv/html5shiv.js"></script>
<![endif]-->
{% endblock %}

{% block content %}
<h1 class="govuk-heading-xl">Scoped initialisation</h1>
<h2 class="govuk-heading-m">Unscoped section</h2>
<p class="govuk-body">This example should intentionally not have JavaScript functionality</p>

{{ govukCheckboxes({
idPrefix: "not-scoped",
name: "not-scoped",
items: [
{
value: "not-scoped",
text: "Not scoped",
conditional: {
html: '<p class="govuk-body">Revealed not scoped text</p>'
}
}
]
}) }}

<h2 class="govuk-heading-m">Scoped section</h2>
<p class="govuk-body">Only this example should have JavaScript functionality</p>
<div id="scoped">
{{ govukCheckboxes({
idPrefix: "scoped",
name: "scoped",
items: [
{
value: "scoped",
text: "Scoped",
conditional: {
html: '<p class="govuk-body">Revealed scoped text</p>'
}
}
]
}) }}
</div>
{% endblock %}

{% block bodyEnd %}
<script src="/public/all.js"></script>
<script>
var $scope = document.getElementById('scoped')
window.GOVUKFrontend.initAll({
scope: $scope
})
</script>
{% endblock %}
15 changes: 15 additions & 0 deletions docs/installation/installing-with-npm.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ Including the script elsewhere will stop components from functioning or displayi
</html>
```

#### Initialise GOV.UK Frontend in only certain sections of a page
Copy link
Contributor

Choose a reason for hiding this comment

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

❤️ 📖

Copy link
Contributor

Choose a reason for hiding this comment

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

@soupdragon99 has helped on this one


By default, the `initAll` function from GOV.UK Frontend initialises all components scoped to an entire page with the `document` object.

You can change this by passing the `scope` parameter to the `initAll` function.

For example, if you have a modal dialog box that opens with new markup you could do the following:

```js
var $modal = document.querySelector('.modal')
window.GOVUKFrontend.initAll({
scope: $modal
})
```

#### Initialise individual included components

GOV.UK Frontend components with JavaScript behaviour have the `data-module` attribute set in their markup.
Expand Down
29 changes: 18 additions & 11 deletions src/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,53 @@ import Header from './components/header/header'
import Radios from './components/radios/radios'
import Tabs from './components/tabs/tabs'

function initAll () {
// Find all buttons with [role=button] on the document to enhance.
new Button(document).init()
function initAll (options) {
// Set the options to an empty object by default if no options are passed.
options = typeof options !== 'undefined' ? options : {}

// Allow the user to initialise GOV.UK Frontend in only certain sections of the page
// Defaults to the entire document if nothing is set.
var scope = typeof options.scope !== 'undefined' ? options.scope : document

// Find all buttons with [role=button] on the scope to enhance.
new Button(scope).init()

// Find all global accordion components to enhance.
var $accordions = document.querySelectorAll('[data-module="accordion"]')
var $accordions = scope.querySelectorAll('[data-module="accordion"]')
nodeListForEach($accordions, function ($accordion) {
new Accordion($accordion).init()
})

// Find all global details elements to enhance.
var $details = document.querySelectorAll('details')
var $details = scope.querySelectorAll('details')
nodeListForEach($details, function ($detail) {
new Details($detail).init()
})

var $characterCount = document.querySelectorAll('[data-module="character-count"]')
var $characterCount = scope.querySelectorAll('[data-module="character-count"]')
nodeListForEach($characterCount, function ($characterCount) {
new CharacterCount($characterCount).init()
})

var $checkboxes = document.querySelectorAll('[data-module="checkboxes"]')
var $checkboxes = scope.querySelectorAll('[data-module="checkboxes"]')
nodeListForEach($checkboxes, function ($checkbox) {
new Checkboxes($checkbox).init()
})

// Find first error summary module to enhance.
var $errorSummary = document.querySelector('[data-module="error-summary"]')
var $errorSummary = scope.querySelector('[data-module="error-summary"]')
new ErrorSummary($errorSummary).init()

// Find first header module to enhance.
var $toggleButton = document.querySelector('[data-module="header"]')
var $toggleButton = scope.querySelector('[data-module="header"]')
new Header($toggleButton).init()

var $radios = document.querySelectorAll('[data-module="radios"]')
var $radios = scope.querySelectorAll('[data-module="radios"]')
nodeListForEach($radios, function ($radio) {
new Radios($radio).init()
})

var $tabs = document.querySelectorAll('[data-module="tabs"]')
var $tabs = scope.querySelectorAll('[data-module="tabs"]')
nodeListForEach($tabs, function ($tabs) {
new Tabs($tabs).init()
})
Expand Down
22 changes: 22 additions & 0 deletions src/all.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,28 @@ describe('GOV.UK Frontend', () => {
}, component)
})
})
it('can be initialised scoped to certain sections of the page', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This makes sense, but only tests the instantiation of a single component and feels like it could be relatively fragile if we e.g. changed the way that conditional reveals work.

Wondering if there's a better way to test this, maybe as a unit test rather than as an integration test, e.g. passing a stubbed DomElement as the scope and asserting that querySelectorAll is called, and/or stubbing document and asserting that it doesn't get called?

(Just worth thinking about, this isn't a blocking comment 👍)

Copy link
Contributor

Choose a reason for hiding this comment

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

We are somewhat bound by the limitations of our testing setup.

I'll see if I can stub an element like you've suggested...

Copy link
Contributor

Choose a reason for hiding this comment

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

Totally fair, don't spend too much time on it 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I think we'd have to run the test using JSDOM rather than in a browser, and it's not clear that you can spy on an element so I'll try to make the current setup clearer but not sure it's worth the time spent...

Copy link
Contributor

Choose a reason for hiding this comment

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

Let me know if you think the current approach is too risky

await page.goto(baseUrl + '/examples/scoped-initialisation', { waitUntil: 'load' })

// To test that certain parts of the page are scoped we have two similar components
// that we can interact with to check if they're interactive.

// Check that the conditional reveal component has a conditional section that would open if enhanced.
await page.waitForSelector('#conditional-not-scoped-1', { hidden: true })

await page.click('[for="not-scoped-1"]')

// Check that when it is clicked that nothing opens, which shows that it has not been enhanced.
await page.waitForSelector('#conditional-not-scoped-1', { hidden: true })
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this definitely test what we want? It's 'waiting' for something was hidden to still be hidden (which it already was…), rather that asserting that it never becomes visible.

Copy link
Contributor

Choose a reason for hiding this comment

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

It was indeed testing it when I remove the scope functionality and ran these tests.


// Check the other conditional reveal which has been enhanced based on it's scope.
await page.waitForSelector('#conditional-scoped-1', { hidden: true })

await page.click('[for="scoped-1"]')

// Check that it has opened as expected.
await page.waitForSelector('#conditional-scoped-1', { hidden: false })
})
})
describe('global styles', async () => {
it('are disabled by default', async () => {
Expand Down