Skip to content

Commit 78b0795

Browse files
committed
Update doc to account for more recent JS changes
1 parent ce2b03d commit 78b0795

File tree

1 file changed

+64
-9
lines changed

1 file changed

+64
-9
lines changed

docs/contributing/coding-standards/component-options.md

+64-9
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ Next, use the `mergeConfigs` helper to combine the default config and the config
3030

3131
The order in which variables are written defines their priority, with objects passed sooner being overwritten by those passed later. As we want the user's configuration to take precedence over our defaults, we list our default configuration object first.
3232

33-
There is no guarantee `config` will have any value at all, so it'll be `undefined`. We use an OR operator (`||`) as a safety check. If the value is `undefined`, we use an empty object instead.
33+
There is no guarantee `config` will have any value at all, so we set the default to an empty object (`{}`) in the constructor parameters.
3434

3535
```mjs
36+
import { mergeConfigs } from '../../common/index.mjs'
37+
3638
export class Accordion {
3739
constructor($module, config = {}) {
3840
this.config = mergeConfigs(
@@ -66,7 +68,7 @@ It's now possible to individually initialise the component with configuration op
6668

6769
## Allowing options to be passed through the `initAll` function
6870

69-
Usually, teams will not be individually initialising components. Instead, GOV.UK Frontend ships with an `initAll` function, which searches the page for instances of components and automatically initialises them.
71+
Often, teams will not be individually initialising components. Instead, GOV.UK Frontend ships with an `initAll` function, which searches the page for instances of components and automatically initialises them.
7072

7173
In `src/govuk/all.mjs`, update the component's `new` class to pass through a nested configuration object. The nested object should use the component's name converted to camelCase (for example, the 'Character Count' component becomes `characterCount`).
7274

@@ -95,25 +97,30 @@ It's now possible to pass configuration options for your component, as well as m
9597

9698
For convenience, we also allow for configuration options to be passed through HTML `data-*` attributes.
9799

98-
You can find `data-*` attributes in JavaScript by looking at an element's `dataset` property. Browsers will convert the attribute names from HTML's kebab-case to more JavaScript-friendly camelCase when working with `dataset`. See ['Naming configuration options'](#naming-configuration-options) for exceptions.
100+
You can find `data-*` attributes in JavaScript by looking at an element's `dataset` property. Browsers will convert the attribute names from HTML's kebab-case to more JavaScript-friendly camelCase when working with `dataset`.
101+
102+
See ['Naming configuration options'](#naming-configuration-options) for exceptions to how names are transformed.
99103

100104
As we expect configuration-related `data-*` attributes to always be on the component's root element (the same element with the `data-module` attribute), we can access them all using `$module.dataset`.
101105

102106
Using the `mergeConfigs` call discussed earlier in this document, update it to include `$module.dataset` as the highest priority.
103107

104108
```mjs
109+
import { mergeConfigs } from '../../common/index.mjs'
110+
import { normaliseDataset } from '../../common/normalise-dataset.mjs'
111+
105112
export class Accordion {
106113
constructor($module, config = {}) {
107114
this.config = mergeConfigs(
108115
Accordion.defaults,
109116
config,
110-
normaliseDataset($module.dataset)
117+
normaliseDataset(Accordion, $module.dataset)
111118
)
112119
}
113120
}
114121
```
115122

116-
Here, we pass the value of `$module.dataset` through our `normaliseDataset` function. This is because attribute values in dataset are always interpreted as strings. `normaliseDataset` runs a few simple checks to convert values back to numbers or booleans where appropriate.
123+
Here, we pass the value of `$module.dataset` through our `normaliseDataset` function. This is because attribute values in dataset are always interpreted as strings. `normaliseDataset` looks at the component's configuration schema and converts values into numbers or booleans where needed.
117124

118125
Now, in our HTML, we could pass configuration options by using the kebab-case version of the option's name.
119126

@@ -128,6 +135,51 @@ Now, in our HTML, we could pass configuration options by using the kebab-case ve
128135

129136
However, this only works for developers who are writing raw HTML. We include Nunjucks macros for each component with GOV.UK Frontend to make development easier and faster, but this also makes it harder for developers to manually alter the raw HTML. We'll add a new parameter to Nunjucks to help them out.
130137

138+
### Adding a configuration schema
139+
140+
Components that accept configuration using `data-*` attributes also require a schema. This schema documents what parameters a configuration object may contain and what types of value they're expected to be.
141+
142+
Having a schema is required for the `normaliseDataset` function to work. A schema is also needed to use the `validateConfig` and `extractConfigByNamespace` functions we'll cover later on.
143+
144+
```mjs
145+
export class Accordion {
146+
static schema = Object.freeze({
147+
properties: {
148+
i18n: { type: 'object' },
149+
rememberExpanded: { type: 'boolean' }
150+
}
151+
})
152+
}
153+
```
154+
155+
### Validating a provided configuration against the schema
156+
157+
You can use the `validateConfig` function to ensure that a configuration object matches the schema format.
158+
159+
If it doesn't, you can return a `ConfigError`.
160+
161+
```mjs
162+
import { mergeConfigs, validateConfig } from '../../common/index.mjs'
163+
import { normaliseDataset } from '../../common/normalise-dataset.mjs'
164+
import { ConfigError } from '../../errors/index.mjs'
165+
166+
export class Accordion {
167+
constructor($module, config = {}) {
168+
this.config = mergeConfigs(
169+
Accordion.defaults,
170+
config,
171+
normaliseDataset(Accordion, $module.dataset)
172+
)
173+
174+
// Check that the configuration provided is valid
175+
const errors = validateConfig(Accordion.schema, this.config)
176+
if (errors[0]) {
177+
throw new ConfigError(`Accordion: ${errors[0]}`)
178+
}
179+
}
180+
}
181+
```
182+
131183
## Adding a Nunjucks parameter
132184

133185
Most, but not all, components support adding arbitrary attributes and values through the `attributes` parameter. This method is also more verbose compared to having a dedicated Nunjucks parameter.
@@ -177,30 +229,33 @@ In our example, `rememberExpanded` becomes `data-remember-expanded`.
177229

178230
Unlike the `data-*` attribute in HTML and our use of `dataset` in JavaScript, there is no intrinsic link between the Nunjucks parameter name and the names used elsewhere. The Nunjucks parameter can therefore be named differently, if convenient.
179231

180-
A common case is specifying whether a parameter accepts HTML or only plain text. For example, if a configuration option's value is inserted into the page using `innerText`, you might want to name the Nunjucks parameter something like `sectionLabelText` to indicate that HTML will not be rendered.
232+
A common case is specifying whether a parameter accepts HTML or only plain text. For example, if a configuration option's value is inserted into the page using `innerText`, you might want to name the Nunjucks parameter something like `sectionLabelText` to indicate that HTML will not be parsed if provided.
181233

182234
There is more guidance on naming Nunjucks parameters in [the Nunjucks API documentation](https://github.com/alphagov/govuk-frontend/blob/main/docs/contributing/coding-standards/nunjucks-api.md#naming-options).
183235

184236
## Namespacing configuration options
185237

186238
You can group configuration options in JavaScript and HTML together by using namespaces; period-separated strings that prefix the configuration option name. Namespaces follow the same formats as other option names, being camelCase in JavaScript and kebab-case in HTML.
187239

188-
These are most commonly used for translation strings, which are usually namespaced under `i18n` (for 'internationalisation').
240+
These are most commonly used for translation strings, which are usually namespaced under `i18n` (short for 'internationalisation').
189241

190242
For example, we could namespace our `rememberExpanded` option under the `stateInfo` namespace. Our `data-*` attribute would now be named `data-state-info.remember-expanded` and accessed in the component's JavaScript using `this.config.stateInfo.rememberExpanded`.
191243

192244
The `extractConfigByNamespace` JavaScript helper can be used to create an object containing _only_ the configuration options that belong to a certain namespace.
193245

194246
```mjs
247+
import { mergeConfigs, extractConfigByNamespace } from '../../common/index.mjs'
248+
import { normaliseDataset } from '../../common/normalise-dataset.mjs'
249+
195250
export class Accordion {
196251
constructor($module, config = {}) {
197252
this.config = mergeConfigs(
198253
Accordion.defaults,
199254
config,
200-
normaliseDataset($module.dataset)
255+
normaliseDataset(Accordion, $module.dataset)
201256
)
202257

203-
this.stateInfo = extractConfigByNamespace(this.config, 'stateInfo');
258+
this.stateInfo = extractConfigByNamespace(Accordion, this.config, 'stateInfo');
204259
}
205260
}
206261
```

0 commit comments

Comments
 (0)