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

Remove component init() methods and initialise in constructor #4011

Merged
merged 5 commits into from
Jul 26, 2023
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ This change was made in [pull request #3819: Add linked image focus style](https

You must make the following changes when you migrate to this release, or your service might break.

#### Update component initialisation

Remove `.init()` from individually instantiated components as initialisation now happens automatically:

```mjs
new Radios($radio).init()
```

```mjs
new Radios($radio)
```

If you import the JavaScript using `window.GOVUKFrontend.initAll()`, you will not need to make any changes.

This change was introduced in [pull request #4011: Remove component init() methods and initialise in constructor](https://github.com/alphagov/govuk-frontend/pull/4011).

#### Check that details components work as expected

The Details component no longer uses JavaScript, and is no longer polyfilled in older browsers.
Expand Down
16 changes: 2 additions & 14 deletions docs/contributing/coding-standards/js.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,6 @@ export class Example {

this.$module = $module

// Code goes here
}

/**
* Initialise component
*/
init () {
// Check that required elements are present
if (!this.$module) {
return
}

// Code goes here
this.$module.addEventListener('click', () => {
// ...
Expand Down Expand Up @@ -104,14 +92,14 @@ Add methods to the class.
```mjs
// Good
class Example {
init () {
doSomething () {
// Code goes here
}
}

// Bad
Example.prototype = {
init: function () {
doSomething: function () {
// Code goes here
}
}
Expand Down
3 changes: 2 additions & 1 deletion docs/examples/webpack/src/javascripts/app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { Button } from 'govuk-frontend'
const $buttons = document.querySelectorAll('[data-module="govuk-button"]')

$buttons.forEach(($button) => {
new Button($button).init()
/* eslint-disable-next-line no-new */
new Button($button)
})
24 changes: 13 additions & 11 deletions packages/govuk-frontend/src/govuk/all.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

import { version } from './common/govuk-frontend-version.mjs'
import { Accordion } from './components/accordion/accordion.mjs'
import { Button } from './components/button/button.mjs'
Expand Down Expand Up @@ -33,60 +35,60 @@ function initAll (config) {

const $accordions = $scope.querySelectorAll('[data-module="govuk-accordion"]')
$accordions.forEach(($accordion) => {
new Accordion($accordion, config.accordion).init()
new Accordion($accordion, config.accordion)
})

const $buttons = $scope.querySelectorAll('[data-module="govuk-button"]')
$buttons.forEach(($button) => {
new Button($button, config.button).init()
new Button($button, config.button)
})

const $characterCounts = $scope.querySelectorAll('[data-module="govuk-character-count"]')
$characterCounts.forEach(($characterCount) => {
new CharacterCount($characterCount, config.characterCount).init()
new CharacterCount($characterCount, config.characterCount)
})

const $checkboxes = $scope.querySelectorAll('[data-module="govuk-checkboxes"]')
$checkboxes.forEach(($checkbox) => {
new Checkboxes($checkbox).init()
new Checkboxes($checkbox)
})

// Find first error summary module to enhance.
const $errorSummary = $scope.querySelector('[data-module="govuk-error-summary"]')
if ($errorSummary) {
new ErrorSummary($errorSummary, config.errorSummary).init()
new ErrorSummary($errorSummary, config.errorSummary)
}

const $exitThisPageButtons = $scope.querySelectorAll('[data-module="govuk-exit-this-page"]')
$exitThisPageButtons.forEach(($button) => {
new ExitThisPage($button, config.exitThisPage).init()
new ExitThisPage($button, config.exitThisPage)
})

// Find first header module to enhance.
const $header = $scope.querySelector('[data-module="govuk-header"]')
if ($header) {
new Header($header).init()
new Header($header)
}

const $notificationBanners = $scope.querySelectorAll('[data-module="govuk-notification-banner"]')
$notificationBanners.forEach(($notificationBanner) => {
new NotificationBanner($notificationBanner, config.notificationBanner).init()
new NotificationBanner($notificationBanner, config.notificationBanner)
})

const $radios = $scope.querySelectorAll('[data-module="govuk-radios"]')
$radios.forEach(($radio) => {
new Radios($radio).init()
new Radios($radio)
})

// Find first skip link module to enhance.
const $skipLink = $scope.querySelector('[data-module="govuk-skip-link"]')
if ($skipLink) {
new SkipLink($skipLink).init()
new SkipLink($skipLink)
}

const $tabs = $scope.querySelectorAll('[data-module="govuk-tabs"]')
$tabs.forEach(($tabs) => {
new Tabs($tabs).init()
new Tabs($tabs)
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,6 @@ export class Accordion {

this.$sections = $sections
this.browserSupportsSessionStorage = helper.checkForSessionStorage()
}

/**
* Initialise component
*/
init () {
// Check that required elements are present
if (!this.$module || !this.$sections) {
return
}

this.initControls()
this.initSectionHeaders()
Expand Down
10 changes: 0 additions & 10 deletions packages/govuk-frontend/src/govuk/components/button/button.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,6 @@ export class Button {
config || {},
normaliseDataset($module.dataset)
)
}

/**
* Initialise component
*/
init () {
// Check that required elements are present
if (!this.$module) {
return
}

this.$module.addEventListener('keydown', (event) => this.handleKeyDown(event))
this.$module.addEventListener('click', (event) => this.debounce(event))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ describe('/components/button', () => {

// `undefined` simulates the element being missing,
// from an unchecked `document.querySelector` for example
new namespace[exportName](undefined).init()
/* eslint-disable-next-line no-new */
new namespace[exportName](undefined)

// If our component initialisation breaks, this won't run
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,6 @@ export class CharacterCount {

this.$module = $module
this.$textarea = $textarea
}

/**
* Initialise component
*/
init () {
// Check that required elements are present
if (!this.$module || !this.$textarea) {
return
}

const $textareaDescription = document.getElementById(`${this.$textarea.id}-info`)
if (!$textareaDescription) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ export class Checkboxes {
$inputs

/**
* Checkboxes can be associated with a 'conditionally revealed' content block –
* for example, a checkbox for 'Phone' could reveal an additional form field for
* the user to enter their phone number.
*
* These associations are made using a `data-aria-controls` attribute, which is
* promoted to an aria-controls attribute during initialisation.
*
* We also need to restore the state of any conditional reveals on the page (for
* example if the user has navigated back), and set up event handlers to keep
* the reveal in sync with the checkbox state.
*
* @param {Element} $module - HTML element to use for checkboxes
*/
constructor ($module) {
Expand All @@ -24,27 +35,6 @@ export class Checkboxes {

this.$module = $module
this.$inputs = $inputs
}

/**
* Initialise component
*
* Checkboxes can be associated with a 'conditionally revealed' content block –
* for example, a checkbox for 'Phone' could reveal an additional form field for
* the user to enter their phone number.
*
* These associations are made using a `data-aria-controls` attribute, which is
* promoted to an aria-controls attribute during initialisation.
*
* We also need to restore the state of any conditional reveals on the page (for
* example if the user has navigated back), and set up event handlers to keep
* the reveal in sync with the checkbox state.
*/
init () {
// Check that required elements are present
if (!this.$module || !this.$inputs) {
return
}

this.$inputs.forEach(($input) => {
const targetId = $input.getAttribute('data-aria-controls')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,6 @@ export class ErrorSummary {
config || {},
normaliseDataset($module.dataset)
)
}

/**
* Initialise component
*/
init () {
// Check that required elements are present
if (!this.$module) {
return
}

this.setFocus()
this.$module.addEventListener('click', (event) => this.handleClick(event))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ describe('Error Summary', () => {

// `undefined` simulates the element being missing,
// from an unchecked `document.querySelector` for example
new namespace[exportName](undefined).init()
/* eslint-disable-next-line no-new */
new namespace[exportName](undefined)

// If our component initialisation breaks, this won't run
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,7 @@ export class ExitThisPage {
if ($skiplinkButton instanceof HTMLAnchorElement) {
this.$skiplinkButton = $skiplinkButton
}
}

/**
* Initialise component
*/
init () {
this.buildIndicator()
this.initUpdateSpan()
this.initButtonClickHandler()
Expand Down
16 changes: 0 additions & 16 deletions packages/govuk-frontend/src/govuk/components/globals.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,6 @@ describe('GOV.UK Frontend', () => {
])
})

it('exported Components have an init function', async () => {
domoscargin marked this conversation as resolved.
Show resolved Hide resolved
const components = exported
.filter(method => !['initAll', 'version'].includes(method))

const componentsWithoutInitFunctions = await page.evaluate(async (components) => {
const namespace = await import('govuk-frontend')

return components.filter(component => {
const prototype = namespace[component].prototype
return typeof prototype.init !== 'function'
})
}, components)

expect(componentsWithoutInitFunctions).toEqual([])
})

it('can be initialised scoped to certain sections of the page', async () => {
await goToExample(page, 'scoped-initialisation')

Expand Down
28 changes: 12 additions & 16 deletions packages/govuk-frontend/src/govuk/components/header/header.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ export class Header {

/**
* A global const for storing a matchMedia instance which we'll use to
* detect when a screen size change happens. We set this later during the
* init function and rely on it being null if the feature isn't available
* to initially apply hidden attributes
* detect when a screen size change happens. We rely on it being null if the
* feature isn't available to initially apply hidden attributes
*
* @private
* @type {MediaQueryList | null}
*/
mql = null

/**
* Apply a matchMedia for desktop which will trigger a state sync if the browser
* viewport moves between states.
*
* @param {Element} $module - HTML element to use for header
*/
constructor ($module) {
Expand All @@ -44,20 +46,14 @@ export class Header {
this.$menu = this.$menuButton && $module.querySelector(
`#${this.$menuButton.getAttribute('aria-controls')}`
)
}

/**
* Initialise component
*
* Check for the presence of the header, menu and menu button – if any are
* missing then there's nothing to do so return early.
* Apply a matchMedia for desktop which will trigger a state sync if the browser
* viewport moves between states.
*/
init () {
// Check that required elements are present
if (!this.$module || !this.$menuButton || !this.$menu) {
return
if (
!(
this.$menuButton instanceof HTMLElement ||
this.$menu instanceof HTMLElement
)
) {
return this
}

// Set the matchMedia to the govuk-frontend desktop breakpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,6 @@ export class NotificationBanner {
config || {},
normaliseDataset($module.dataset)
)
}

/**
* Initialise component
*/
init () {
// Check that required elements are present
if (!this.$module) {
return
}

this.setFocus()
}
Expand Down
Loading