diff --git a/.babelrc b/.babelrc index 3cdb872137..079641a4e4 100755 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,4 @@ { "presets": ["stage-2", "babel-preset-react", "babel-preset-es2015"], - "plugins": ["transform-class-properties"] + "plugins": ["transform-class-properties", "plugin-transform-runtime"] } diff --git a/_prototypes/individual-response--your-household-v15/assets/modules/abortable-fetch.js b/_prototypes/individual-response--your-household-v15/assets/modules/abortable-fetch.js index a0ced36c67..934cc50209 100644 --- a/_prototypes/individual-response--your-household-v15/assets/modules/abortable-fetch.js +++ b/_prototypes/individual-response--your-household-v15/assets/modules/abortable-fetch.js @@ -1,30 +1,26 @@ -export default class AbortableFetch { +class AboratableFetch { constructor(url, options) { this.url = url; - this.options = options; this.controller = new window.AbortController(); - this.status = 'UNSENT'; + this.options = { ...options, signal: this.controller.signal }; + + fetch(url, options).then(response => { + if (response.ok) { + this.thenCallback(response); + } else { + this.catchCallback(response); + } + }); } - send() { - this.status = 'LOADING'; + then(callback) { + this.thenCallback = callback; + return this; + } - return new Promise((resolve, reject) => { - abortableFetch(this.url, { signal: this.controller.signal, ...this.options }) - .then(response => { - if (response.status >= 200 && response.status < 300) { - this.status = 'DONE'; - resolve(response); - } else { - this.status = 'DONE'; - reject(response); - } - }) - .catch(error => { - this.status = 'DONE'; - reject(error); - }); - }); + catch(callback) { + this.catchCallback = callback; + return this; } abort() { @@ -32,18 +28,4 @@ export default class AbortableFetch { } } -function abortableFetch(url, options) { - return window.fetch(url, options) - .then(response => { - if (response.ok) { - return response; - } else { - const error = new Error(response.statusText); - error.response = response; - throw error; - } - }) - .catch(error => { - throw error; - }); -} +export default (url, options) => new AboratableFetch(url, options); diff --git a/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/abortable-fetch.js b/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/abortable-fetch.js deleted file mode 100644 index a0ced36c67..0000000000 --- a/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/abortable-fetch.js +++ /dev/null @@ -1,49 +0,0 @@ -export default class AbortableFetch { - constructor(url, options) { - this.url = url; - this.options = options; - this.controller = new window.AbortController(); - this.status = 'UNSENT'; - } - - send() { - this.status = 'LOADING'; - - return new Promise((resolve, reject) => { - abortableFetch(this.url, { signal: this.controller.signal, ...this.options }) - .then(response => { - if (response.status >= 200 && response.status < 300) { - this.status = 'DONE'; - resolve(response); - } else { - this.status = 'DONE'; - reject(response); - } - }) - .catch(error => { - this.status = 'DONE'; - reject(error); - }); - }); - } - - abort() { - this.controller.abort(); - } -} - -function abortableFetch(url, options) { - return window.fetch(url, options) - .then(response => { - if (response.ok) { - return response; - } else { - const error = new Error(response.statusText); - error.response = response; - throw error; - } - }) - .catch(error => { - throw error; - }); -} diff --git a/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/form-body-from-object.js b/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/form-body-from-object.js deleted file mode 100644 index ac4b357b7d..0000000000 --- a/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/form-body-from-object.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function formBodyFromObject(object) { - return Object.keys(object).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(object[key])}`).join('&'); -} diff --git a/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/typeahead-helpers.js b/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/typeahead-helpers.js deleted file mode 100644 index 800c4e5155..0000000000 --- a/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/typeahead-helpers.js +++ /dev/null @@ -1,11 +0,0 @@ -export function sanitiseTypeaheadText(string, sanitisedQueryReplaceChars = [], trimEnd = true) { - let sanitisedString = string.toLowerCase().replace(/\s\s+/g, ' '); - - sanitisedString = trimEnd ? sanitisedString.trim() : sanitisedString.trimStart(); - - sanitisedQueryReplaceChars.forEach(char => { - sanitisedString = sanitisedString.replace(new RegExp(char, 'g'), ''); - }); - - return sanitisedString; -} diff --git a/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/typeahead.component.js b/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/typeahead.component.js deleted file mode 100644 index 571f762326..0000000000 --- a/_prototypes/individual-response--your-household-v15/assets/modules/typeahead-refactored/typeahead.component.js +++ /dev/null @@ -1,406 +0,0 @@ -import EventEmitter from 'events'; -import {sanitiseTypeaheadText} from './typeahead-helpers'; - -const classTypeaheadCombobox = 'js-typeahead-combobox'; -const classTypeaheadLabel = 'js-typeahead-label'; -const classTypeaheadInput = 'js-typeahead-input'; -const classTypeaheadInstructions = 'js-typeahead-instructions'; -const classTypeaheadListbox = 'js-typeahead-listbox'; -const classTypeaheadAriaStatus = 'js-typeahead-aria-status'; - -const classTypeaheadOption = 'typeahead__option'; -const classTypeaheadOptionFocused = `${classTypeaheadOption}--focused`; -const classTypeaheadOptionNoResults = `${classTypeaheadOption}--no-results`; -const classTypeaheadOptionMoreResults = `${classTypeaheadOption}--more-results`; -const classTypeaheadComboboxFocused = 'typeahead__combobox--focused'; -const classTypeaheadHasResults = 'typeahead--has-results'; - -const KEYCODE = { - BACK_SPACE: 8, - RETURN: 13, - ENTER: 14, - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - DELETE: 46, - V: 86 -}; - -export const NEW_FIELD_VALUE_EVENT = 'NEW_FIELD_VALUE'; -export const NEW_ITEM_SELECTED_EVENT = 'NEW_ITEM_SELECTED'; -export const UNSET_FIELD_VALUE_EVENT = 'UNSET_FIELD_VALUE'; - -export default class TypeaheadComponent { - emitter = new EventEmitter(); - - constructor({ - context, - apiUrl, - minChars, - resultLimit, - sanitisedQueryReplaceChars = [], - lang = null}) { - - // DOM Elements - this.context = context; - this.combobox = context.querySelector(`.${classTypeaheadCombobox}`); - this.label = context.querySelector(`.${classTypeaheadLabel}`); - this.input = context.querySelector(`.${classTypeaheadInput}`); - this.listbox = context.querySelector(`.${classTypeaheadListbox}`); - this.instructions = context.querySelector(`.${classTypeaheadInstructions}`); - this.ariaStatus = context.querySelector(`.${classTypeaheadAriaStatus}`); - - // Suggestion URL - this.apiUrl = apiUrl || context.getAttribute('data-api-url'); - - // Settings - this.content = JSON.parse(context.getAttribute('data-content')); - this.listboxId = this.listbox.getAttribute('id'); - this.minChars = minChars || 2; - this.resultLimit = resultLimit || null; - - // State - this.ctrlKey = false; - this.deleting = false; - this.query = ''; - this.sanitisedQuery = ''; - this.previousQuery = ''; - this.results = []; - this.resultOptions = []; - this.foundResults = 0; - this.numberOfResults = 0; - this.highlightedResultIndex = 0; - this.settingResult = false; - this.resultSelected = false; - this.blurring = false; - this.blurTimeout = null; - this.sanitisedQueryReplaceChars = sanitisedQueryReplaceChars; - this.lang = lang || document.documentElement.getAttribute('lang').toLowerCase(); - - // Modify DOM - this.label.setAttribute('for', this.input.getAttribute('id')); - this.input.setAttribute('aria-autocomplete', 'list'); - this.input.setAttribute('aria-controls', this.listbox.getAttribute('id')); - this.input.setAttribute('aria-describedby', this.instructions.getAttribute('id')); - this.input.setAttribute('autocomplete', this.input.getAttribute('data-autocomplete')); - this.context.classList.add('typeahead--initialised'); - - // Bind event listeners - this.bindEventListeners(); - } - - bindEventListeners() { - this.input.addEventListener('keydown', this.handleKeydown.bind(this)); - this.input.addEventListener('keyup', this.handleKeyup.bind(this)); - this.input.addEventListener('input', this.handleChange.bind(this)); - this.input.addEventListener('focus', this.handleFocus.bind(this)); - this.input.addEventListener('blur', this.handleBlur.bind(this)); - - this.listbox.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.listbox.addEventListener('mouseout', this.handleMouseout.bind(this)); - } - - handleKeydown(event) { - this.ctrlKey = ((event.ctrlKey || event.metaKey) && event.keyCode !== KEYCODE.V); - - switch (event.keyCode) { - case KEYCODE.UP: { - event.preventDefault(); - this.navigateResults(-1); - break; - } - case KEYCODE.DOWN: { - event.preventDefault(); - this.navigateResults(1); - break; - } - case KEYCODE.ENTER: - case KEYCODE.RETURN: { - event.preventDefault(); - break; - } - } - } - - handleKeyup(event) { - switch (event.keyCode) { - case KEYCODE.UP: - case KEYCODE.DOWN: { - event.preventDefault(); - break; - } - case KEYCODE.ENTER: - case KEYCODE.RETURN: { - this.selectResult(); - break; - } - case KEYCODE.LEFT: - case KEYCODE.RIGHT: { - break; - } - } - - this.ctrlKey = false; - } - - handleChange() { - if (!this.blurring) { - this.valueChanged(); - } - } - - handleFocus() { - clearTimeout(this.blurTimeout); - this.combobox.classList.add(classTypeaheadComboboxFocused); - } - - handleBlur() { - clearTimeout(this.blurTimeout); - this.blurring = true; - - this.blurTimeout = setTimeout(() => { - this.combobox.classList.remove(classTypeaheadComboboxFocused); - this.blurring = false; - }, 0); - } - - handleMouseover() { - const focusedItem = this.resultOptions[this.highlightedResultIndex]; - - if (focusedItem) { - focusedItem.classList.remove(classTypeaheadOptionFocused); - } - } - - handleMouseout() { - const focusedItem = this.resultOptions[this.highlightedResultIndex]; - - if (focusedItem) { - focusedItem.classList.add(classTypeaheadOptionFocused); - } - } - - navigateResults(direction) { - let index = 0; - - if (this.highlightedResultIndex !== null) { - index = this.highlightedResultIndex + direction; - } - - if (index < this.numberOfResults) { - if (index < 0) { - index = null; - } - - this.setHighlightedResult(index); - } - } - - valueChanged(force) { - if (!this.settingResult) { - const query = this.input.value; - const sanitisedQuery = sanitiseTypeaheadText(query, this.sanitisedQueryReplaceChars); - - if (sanitisedQuery !== this.sanitisedQuery || (force && !this.resultSelected)) { - this.unsetResults(); - this.setAriaStatus(); - - this.query = query; - this.sanitisedQuery = sanitisedQuery; - - if (this.sanitisedQuery.length >= this.minChars) { - this.emitter.emit(NEW_FIELD_VALUE_EVENT, sanitisedQuery); - } else { - this.clearListbox(); - } - } - } - } - - unsetResults() { - this.results = []; - this.resultOptions = []; - this.resultSelected = false; - - this.emitter.emit(UNSET_FIELD_VALUE_EVENT); - } - - clearListbox(preventAriaStatusUpdate) { - this.listbox.innerHTML = ''; - this.context.classList.remove(classTypeaheadHasResults); - this.input.removeAttribute('aria-activedescendant'); - this.combobox.removeAttribute('aria-expanded'); - - if (!preventAriaStatusUpdate) { - this.setAriaStatus(); - } - } - - updateData(dataMap) { - this.results = dataMap.results; - this.foundResults = dataMap.totalResults; - this.numberOfResults = Math.max(this.results.length, 0); - - this.render(); - } - - render() { - if (!this.deleting || (this.numberOfResults && this.deleting)) { - if (this.numberOfResults.length === 1 && this.results[0].sanitisedText === this.sanitisedQuery) { - this.clearListbox(true); - this.selectResult(0); - } else { - this.listbox.innerHTML = ''; - this.resultOptions = this.results.map((result, index) => { - let ariaLabel = result[this.lang]; - let innerHTML = this.emboldenMatch(ariaLabel, this.query); - - if (Array.isArray(result.sanitisedAlternatives)) { - const alternativeMatch = result.sanitisedAlternatives.find(alternative => alternative !== result.sanitisedText && alternative.includes(this.sanitisedQuery)); - - if (alternativeMatch) { - const alternativeText = result.alternatives[result.sanitisedAlternatives.indexOf(alternativeMatch)]; - innerHTML += ` (${this.emboldenMatch(alternativeText, this.query)})`; - ariaLabel += `, (${alternativeText})`; - } - } - - const listElement = document.createElement('li'); - listElement.className = classTypeaheadOption; - listElement.setAttribute('id', `${this.listboxId}__option--${index}`); - listElement.setAttribute('role', 'option'); - listElement.setAttribute('tabindex', '-1'); - listElement.setAttribute('aria-label', ariaLabel); - listElement.innerHTML = innerHTML; - - listElement.addEventListener('click', () => { - this.selectResult(index); - }); - - return listElement; - }); - - this.resultOptions.forEach(listElement => this.listbox.appendChild(listElement)); - - if (this.numberOfResults < this.foundResults) { - const listElement = document.createElement('li'); - listElement.className = `${classTypeaheadOption} ${classTypeaheadOptionMoreResults} u-fs-b`; - listElement.setAttribute('tabindex', '-1'); - listElement.setAttribute('aria-hidden', 'true'); - listElement.innerHTML = this.content.more_results; - this.listbox.appendChild(listElement); - } - - this.setHighlightedResult(null); - this.combobox.setAttribute('aria-expanded', true); - this.context.classList.add(classTypeaheadHasResults); - } - } - - if (this.numberOfResults === 0 && this.content.no_results) { - this.listbox.innerHTML = `
+ + +
Can't complete this question?
Choose another section and
return to this later
+ + +
Can't complete this question?
Choose another section and
return to this later
- Include British, + Includes British, Northern Irish, Irish, Gypsy, Irish Traveller, Roma or diff --git a/_prototypes/individual-response--your-household-v15/individual-details-job-title.html b/_prototypes/individual-response--your-household-v15/individual-details-job-title.html index 20238f6bfc..f4507a4354 100644 --- a/_prototypes/individual-response--your-household-v15/individual-details-job-title.html +++ b/_prototypes/individual-response--your-household-v15/individual-details-job-title.html @@ -47,7 +47,7 @@
+ + +
Can't complete this question?
Choose another section and
return to this later
+ + +