diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 0c336d808..85aba52b9 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,7 +12,16 @@ jobs: - name: Install dependencies run: npm install - + + - name: Sonar analysis + uses: sonarsource/sonarcloud-github-action@master + with: + projectBaseDir: . + env: + GITHUB_TOKEN: ${{ secrets.TOKEN_GITHUB }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + - name: Run linting run: npm run lint diff --git a/CHANGELOG.md b/CHANGELOG.md index 62c590f9e..537869795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ - Updated header component unit tests ([PR 900](https://github.com/nhsuk/nhsuk-frontend/pull/900)). - Fixed bug where the header didn't align with the main width container ([PR 902](https://github.com/nhsuk/nhsuk-frontend/pull/902)). This fixes [Issue 901](https://github.com/nhsuk/nhsuk-frontend/issues/901) +:new: **New features** + +- Add and export new `initAll` method in `nhsuk.js`, and pass document by default, but allowing smaller DOM scopes to be passed. This allows new nhsuk-frontend JS components to be initialised after page load, such as in new pieces of DOM added by JavaScript. + - This fixes [issue 906](https://github.com/nhsuk/nhsuk-frontend/issues/906) where button elements added _after_ the page has loaded would not benefit from the button component's JS behaviours (double click prevention and space bar activation for links). ([PR 907](https://github.com/nhsuk/nhsuk-frontend/pull/907)). + ## 8.0.2 - 19 October 2023 :wrench: **Fixes** diff --git a/packages/components/button/button.js b/packages/components/button/button.js index 0653d27c0..3b2a87755 100644 --- a/packages/components/button/button.js +++ b/packages/components/button/button.js @@ -62,8 +62,8 @@ class Button { } } -export default () => { - const buttons = document.querySelectorAll('[data-module="nhsuk-button"]') +export default ({ scope = document } = {}) => { + const buttons = scope.querySelectorAll('[data-module="nhsuk-button"]') buttons.forEach((el) => { new Button(el).init() }) diff --git a/packages/components/character-count/character-count.js b/packages/components/character-count/character-count.js index 6a4f19552..8a96b2344 100644 --- a/packages/components/character-count/character-count.js +++ b/packages/components/character-count/character-count.js @@ -248,8 +248,8 @@ CharacterCount.prototype.defaults = { wordCountAttribute: 'data-maxwords' } -export default () => { - const characterCounts = document.querySelectorAll('[data-module="nhsuk-character-count"]') +export default ({ scope = document } = {}) => { + const characterCounts = scope.querySelectorAll('[data-module="nhsuk-character-count"]') characterCounts.forEach((el) => { new CharacterCount(el).init() }) diff --git a/packages/components/checkboxes/checkboxes.js b/packages/components/checkboxes/checkboxes.js index 39650acf5..6b3ecc197 100644 --- a/packages/components/checkboxes/checkboxes.js +++ b/packages/components/checkboxes/checkboxes.js @@ -54,9 +54,9 @@ const unCheckExclusiveInputs = function unCheckExclusiveInputs(input) { syncAllConditionalReveals(input) } -export default () => { +export default ({ scope = document } = {}) => { // Checkbox input DOMElements inside a conditional form group - const checkboxInputs = document.querySelectorAll('.nhsuk-checkboxes .nhsuk-checkboxes__input') + const checkboxInputs = scope.querySelectorAll('.nhsuk-checkboxes .nhsuk-checkboxes__input') /** * Toggle classes and attributes diff --git a/packages/components/details/details.js b/packages/components/details/details.js index 09c5fe196..70cd993e5 100644 --- a/packages/components/details/details.js +++ b/packages/components/details/details.js @@ -5,7 +5,7 @@ import { toggleAttribute } from '../../common' * Test at http://0.0.0.0:3000/components/details/index.html */ -export default () => { +export default ({ scope = document } = {}) => { // Does the browser support details component const nativeSupport = typeof document.createElement('details').open === 'boolean' if (nativeSupport) { @@ -13,7 +13,7 @@ export default () => { } // Nodelist of all details elements - const allDetails = document.querySelectorAll('details') + const allDetails = scope.querySelectorAll('details') /** * Adds all necessary functionality to a details element @@ -28,11 +28,11 @@ export default () => { if (!element.id) element.setAttribute('id', `nhsuk-details${index}`) // Set content element and give it an ID if it doesn't already have one - const content = document.querySelector(`#${element.id} .nhsuk-details__text`) + const content = scope.querySelector(`#${element.id} .nhsuk-details__text`) if (!content.id) content.setAttribute('id', `nhsuk-details__text${index}`) // Set summary element - const summary = document.querySelector(`#${element.id} .nhsuk-details__summary`) + const summary = scope.querySelector(`#${element.id} .nhsuk-details__summary`) // Set initial summary aria attributes summary.setAttribute('role', 'button') diff --git a/packages/components/error-summary/error-summary.js b/packages/components/error-summary/error-summary.js index 35a584152..2c797c26a 100644 --- a/packages/components/error-summary/error-summary.js +++ b/packages/components/error-summary/error-summary.js @@ -101,9 +101,9 @@ function handleClick(event) { } } -export default ({ focusOnPageLoad = true } = {}) => { +export default ({ focusOnPageLoad = true, scope = document } = {}) => { // Error summary component - const errorSummary = document.querySelector('.nhsuk-error-summary') + const errorSummary = scope.querySelector('.nhsuk-error-summary') if (errorSummary) { // Focus error summary component if it exists diff --git a/packages/components/radios/radios.js b/packages/components/radios/radios.js index 48dba75f5..aaa6a5c63 100644 --- a/packages/components/radios/radios.js +++ b/packages/components/radios/radios.js @@ -5,9 +5,9 @@ import { toggleConditionalInput } from '../../common' * Test at http://0.0.0.0:3000/components/radios/conditional.html */ -export default () => { +export default ({ scope = document } = {}) => { // Radio input HTMLElements inside a conditional form group - const radioInputs = document.querySelectorAll('.nhsuk-radios--conditional .nhsuk-radios__input') + const radioInputs = scope.querySelectorAll('.nhsuk-radios--conditional .nhsuk-radios__input') /** * Update all conditional reveals to match checked state diff --git a/packages/components/tabs/tabs.js b/packages/components/tabs/tabs.js index 75f5bbf9e..6c87bbc16 100644 --- a/packages/components/tabs/tabs.js +++ b/packages/components/tabs/tabs.js @@ -315,8 +315,8 @@ class Tabs { * Tabs({responsive: false}); * Tabs({namespace: 'my-custom-namespace'}); // Alters classes allowing alternative css */ -export default ({ namespace = 'nhsuk-tabs', responsive = true, historyEnabled = true } = {}) => { - const tabs = document.querySelectorAll(`[data-module="${namespace}"]`) +export default ({ namespace = 'nhsuk-tabs', responsive = true, historyEnabled = true, scope = document } = {}) => { + const tabs = scope.querySelectorAll(`[data-module="${namespace}"]`) tabs.forEach((el) => { new Tabs(el, namespace, responsive, historyEnabled).init() }) diff --git a/packages/nhsuk.js b/packages/nhsuk.js index 6334b30a3..f3638a347 100644 --- a/packages/nhsuk.js +++ b/packages/nhsuk.js @@ -1,25 +1,38 @@ // Components -import Button from './components/button/button' -import CharacterCount from './components/character-count/character-count' -import Checkboxes from './components/checkboxes/checkboxes' -import Details from './components/details/details' -import ErrorSummary from './components/error-summary/error-summary' -import Header from './components/header/header' -import Radios from './components/radios/radios' -import SkipLink from './components/skip-link/skip-link' -import Tabs from './components/tabs/tabs' +import initButton from './components/button/button' +import initCharacterCount from './components/character-count/character-count' +import initCheckboxes from './components/checkboxes/checkboxes' +import initDetails from './components/details/details' +import initErrorSummary from './components/error-summary/error-summary' +import initHeader from './components/header/header' +import initRadios from './components/radios/radios' +import initSkipLink from './components/skip-link/skip-link' +import initTabs from './components/tabs/tabs' import './polyfills' +/** + * Use this function to initialise nhsuk-frontend components within a + * given scope. This function is called by default with the document + * element, but you can call it again later with a new DOM element + * containing nhsuk-frontend components which you wish to initialise. + * + * @param {HTMLElement} scope + */ +export function initAll(scope) { + initButton({ scope }) + initCharacterCount({ scope }) + initCheckboxes({ scope }) + initDetails({ scope }) + initErrorSummary({ scope }) + initRadios({ scope }) + initTabs({ scope }) +} + // Initialize components document.addEventListener('DOMContentLoaded', () => { - CharacterCount() - Button() - Checkboxes() - Details() - ErrorSummary() - Header() - Radios() - SkipLink() - Tabs() + initHeader() + initSkipLink() + + initAll(document) }) diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..43466a098 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,24 @@ +# SonarQube properties +sonar.projectKey=nhsuk-frontend +sonar.organization=nhsdigital + +# Project language +sonar.language=js + +# Encoding of the source code. Default is default system encoding +sonar.sourceEncoding=UTF-8 + +# Add file exclusions +sonar.exclusions=reports/** + +# Add coverage exclusions +sonar.coverage.exclusions=**/*.config.js,**/coverage/**,**/*check-report.html + +# Adding the dependency-check parameter +sonar.dependencyCheck.htmlReportPath=reports/dependency-check/dependency-check-report.html + +# Encoding of the source code. Default is default system encoding +sonar.sourceEncoding=UTF-8 + +# Adding the coverage analysis parameter +sonar.javascript.lcov.reportPaths=./coverage/lcov.info \ No newline at end of file