Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
colinrotherham committed Feb 7, 2025
1 parent 2ea99e1 commit 1fbc7ff
Show file tree
Hide file tree
Showing 15 changed files with 492 additions and 171 deletions.
53 changes: 47 additions & 6 deletions src/moj/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ const {
const { nodeListForEach } = require('./helpers.js')
const { version } = require('./version.js')

function initAll(options) {
/**
* @param {Config} [config]
*/
function initAll(config) {
// Set the options to an empty object by default if no options are passed.
options = typeof options !== 'undefined' ? options : {}
config = typeof config !== 'undefined' ? config : {}

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

const $addAnothers = scope.querySelectorAll('[data-module="moj-add-another"]')

Expand All @@ -42,10 +45,16 @@ function initAll(options) {
)

nodeListForEach($multiSelects, function ($multiSelect) {
const containerSelector = $multiSelect.getAttribute(
'data-multi-select-checkbox'
)

if (!($multiSelect instanceof HTMLElement) || !containerSelector) {
return
}

new MultiSelect({
container: $multiSelect.querySelector(
$multiSelect.getAttribute('data-multi-select-checkbox')
),
container: $multiSelect.querySelector(containerSelector),
checkboxes: $multiSelect.querySelectorAll(
'tbody .govuk-checkboxes__input'
),
Expand All @@ -66,6 +75,7 @@ function initAll(options) {
)

nodeListForEach($richTextEditors, function ($richTextEditor) {
/** @type {RichTextEditorConfig} */
const options = {
textarea: $($richTextEditor)
}
Expand Down Expand Up @@ -116,12 +126,20 @@ function initAll(options) {
const $datepickers = scope.querySelectorAll('[data-module="moj-date-picker"]')

nodeListForEach($datepickers, function ($datepicker) {
if (!($datepicker instanceof HTMLElement)) {
return
}

new DatePicker($datepicker, {}).init()
})

const $buttonMenus = scope.querySelectorAll('[data-module="moj-button-menu"]')

nodeListForEach($buttonMenus, function ($buttonmenu) {
if (!($buttonmenu instanceof HTMLElement)) {
return
}

new ButtonMenu($buttonmenu, {}).init()
})
}
Expand All @@ -142,3 +160,26 @@ module.exports = {
SearchToggle,
SortableTable
}

/**
* @typedef {object} Config
* @property {Element} [scope=document] - Scope to query for components
*/

/**
* Schema for component config
*
* @typedef {object} Schema
* @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
*/

/**
* Schema property for component config
*
* @typedef {object} SchemaProperty
* @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
*/

/**
* @import { RichTextEditorConfig } from './components/rich-text-editor/rich-text-editor.js'
*/
58 changes: 50 additions & 8 deletions src/moj/components/add-another/add-another.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
/**
* @class
* @param {Element | null} container - HTML element container
*/
function AddAnother(container) {
if (!container || !(container instanceof HTMLElement)) {
return
}

this.container = $(container)

if (this.container.data('moj-add-another-initialised')) {
Expand All @@ -22,6 +30,9 @@ function AddAnother(container) {
.prop('type', 'button')
}

/**
* @param {JQuery.ClickEvent<HTMLElement>} e - Click event
*/
AddAnother.prototype.onAddButtonClick = function (e) {
const item = this.getNewItem()
this.updateAttributes(this.getItems().length, item)
Expand All @@ -34,11 +45,18 @@ AddAnother.prototype.onAddButtonClick = function (e) {
item.find('input, textarea, select').first().focus()
}

/**
* @param {JQuery<HTMLElement>} item - Add another item
*/
AddAnother.prototype.hasRemoveButton = function (item) {
return item.find('.moj-add-another__remove-button').length
}

AddAnother.prototype.getItems = function () {
if (!this.container) {
return $()
}

return this.container.find('.moj-add-another__item')
}

Expand All @@ -50,33 +68,50 @@ AddAnother.prototype.getNewItem = function () {
return item
}

/**
* @param {number} index - Add another item index
* @param {JQuery<HTMLElement>} item - Add another item
*/
AddAnother.prototype.updateAttributes = function (index, item) {
item.find('[data-name]').each(function (i, el) {
if (!(el instanceof HTMLInputElement)) {
return
}

const name = $(el).attr('data-name') || ''
const id = $(el).attr('data-id') || ''
const originalId = el.id

el.name = $(el)
.attr('data-name')
.replace(/%index%/, index)
el.id = $(el)
.attr('data-id')
.replace(/%index%/, index)
el.name = name.replace(/%index%/, `${index}`)
el.id = id.replace(/%index%/, `${index}`)

const label =
$(el).siblings('label')[0] ||
$(el).parents('label')[0] ||
item.find(`[for="${originalId}"]`)[0]

label.htmlFor = el.id
})
}

/**
* @param {JQuery<HTMLElement>} item - Add another item
*/
AddAnother.prototype.createRemoveButton = function (item) {
item.append(
'<button type="button" class="govuk-button govuk-button--secondary moj-add-another__remove-button">Remove</button>'
)
}

/**
* @param {JQuery<HTMLElement>} item - Add another item
*/
AddAnother.prototype.resetItem = function (item) {
item.find('[data-name], [data-id]').each(function (index, el) {
if (!(el instanceof HTMLInputElement)) {
return
}

if (el.type === 'checkbox' || el.type === 'radio') {
el.checked = false
} else {
Expand All @@ -85,20 +120,27 @@ AddAnother.prototype.resetItem = function (item) {
})
}

/**
* @param {JQuery.ClickEvent<HTMLElement, undefined, HTMLButtonElement>} e - Click event
*/
AddAnother.prototype.onRemoveButtonClick = function (e) {
$(e.currentTarget).parents('.moj-add-another__item').remove()
const items = this.getItems()
if (items.length === 1) {
items.find('.moj-add-another__remove-button').remove()
}
items.each((index, el) => {
this.updateAttributes(index, $(el))
this.updateAttributes(index, $(el))
})
this.focusHeading()
}

AddAnother.prototype.focusHeading = function () {
this.container.find('.moj-add-another__heading').get(0).focus()
const heading = this.container.find('.moj-add-another__heading').get(0)

if (heading) {
heading.focus()
}
}

module.exports = { AddAnother }
30 changes: 10 additions & 20 deletions src/moj/components/button-menu/button-menu.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
/**
* @typedef {object} ButtonMenuConfig
* @property {string} [buttonText=Actions] - Label for the toggle button
* @property {"left" | "right"} [alignMenu=left] - the alignment of the menu
* @property {string} [buttonClasses=govuk-button--secondary] - css classes applied to the toggle button
*/

/**
* @class
* @param {HTMLElement} $module
* @param {ButtonMenuConfig} config
* @class
*/
function ButtonMenu($module, config = {}) {
if (!$module) {
Expand Down Expand Up @@ -141,6 +134,9 @@ ButtonMenu.prototype.isOpen = function () {
return this.$menuToggle.getAttribute('aria-expanded') === 'true'
}

/**
* @param {MouseEvent} event - Click event
*/
ButtonMenu.prototype.toggleMenu = function (event) {
event.preventDefault()

Expand Down Expand Up @@ -258,9 +254,10 @@ ButtonMenu.prototype.handleKeyDown = function (event) {
*
* @param {Schema} schema - component schema
* @param {DOMStringMap} dataset - HTML element dataset
* @returns {object} Normalised dataset
* @returns {{ [key: string]: unknown }} Normalised dataset
*/
ButtonMenu.prototype.parseDataset = function (schema, dataset) {
/** @type {{ [key: string]: unknown }} */
const parsed = {}

for (const [field, ,] of Object.entries(schema.properties)) {
Expand Down Expand Up @@ -310,15 +307,8 @@ ButtonMenu.prototype.mergeConfigs = function (...configObjects) {
module.exports = { ButtonMenu }

/**
* Schema for component config
*
* @typedef {object} Schema
* @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
*/

/**
* Schema property for component config
*
* @typedef {object} SchemaProperty
* @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
* @typedef {object} ButtonMenuConfig
* @property {string} [buttonText='Actions'] - Label for the toggle button
* @property {"left" | "right"} [alignMenu='left'] - the alignment of the menu
* @property {string} [buttonClasses='govuk-button--secondary'] - css classes applied to the toggle button
*/
41 changes: 17 additions & 24 deletions src/moj/components/date-picker/date-picker.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
/**
* Date picker config
*
* @typedef {object} DatePickerConfig
* @property {string} [excludedDates] - Dates that cannot be selected
* @property {string} [excludedDays] - Days that cannot be selected
* @property {boolean} [leadingZeroes] - Whether to add leading zeroes when populating the field
* @property {string} [minDate] - The earliest available date
* @property {string} [maxDate] - The latest available date
* @property {string} [weekStartDay] - First day of the week in calendar view
*/

/**
* @class
* @param {HTMLElement} $module - HTML element
* @param {DatePickerConfig} config - config object
* @class
*/
function DatePicker($module, config = {}) {
if (!$module) {
Expand Down Expand Up @@ -790,9 +778,10 @@ DatePicker.prototype.focusPreviousYear = function (event, focus = true) {
*
* @param {Schema} schema - Component class
* @param {DOMStringMap} dataset - HTML element dataset
* @returns {object} Normalised dataset
* @returns {{ [key: string]: unknown }} Normalised dataset
*/
DatePicker.prototype.parseDataset = function (schema, dataset) {
/** @type {{ [key: string]: unknown }} */
const parsed = {}

for (const [field, ,] of Object.entries(schema.properties)) {
Expand Down Expand Up @@ -891,6 +880,9 @@ DSCalendarDay.prototype.update = function (day, hidden, disabled) {
this.date = new Date(day)
}

/**
* @param {MouseEvent} event - Click event
*/
DSCalendarDay.prototype.click = function (event) {
this.picker.goToDate(this.date)
this.picker.selectDate(this.date)
Expand All @@ -899,6 +891,9 @@ DSCalendarDay.prototype.click = function (event) {
event.preventDefault()
}

/**
* @param {KeyboardEvent} event - Key press event
*/
DSCalendarDay.prototype.keyPress = function (event) {
let calendarNavKey = true

Expand Down Expand Up @@ -947,15 +942,13 @@ DSCalendarDay.prototype.keyPress = function (event) {
module.exports = { DatePicker }

/**
* Schema for component config
*
* @typedef {object} Schema
* @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
*/

/**
* Schema property for component config
* Date picker config
*
* @typedef {object} SchemaProperty
* @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
* @typedef {object} DatePickerConfig
* @property {string} [excludedDates] - Dates that cannot be selected
* @property {string} [excludedDays] - Days that cannot be selected
* @property {boolean} [leadingZeroes] - Whether to add leading zeroes when populating the field
* @property {string} [minDate] - The earliest available date
* @property {string} [maxDate] - The latest available date
* @property {string} [weekStartDay] - First day of the week in calendar view
*/
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* @class
*/
function FilterToggleButton(options) {
this.options = options
this.container = $(this.options.toggleButton.container)
Expand Down
3 changes: 3 additions & 0 deletions src/moj/components/form-validator/form-validator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const { addAttributeValue, removeAttributeValue } = require('../../helpers.js')

/**
* @class
*/
function FormValidator(form, options) {
this.form = form
this.errors = []
Expand Down
Loading

0 comments on commit 1fbc7ff

Please sign in to comment.