Skip to content

Commit 3d8f7db

Browse files
Merge pull request #9 from practical-computer/thomas/pract-140-add-optional-html_content-property-to-error-mapping
Add optional `html_content` property to error mapping
2 parents 2a864ad + 16e40b9 commit 3d8f7db

File tree

6 files changed

+75
-3
lines changed

6 files changed

+75
-3
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ When returning error mappings, they need to have the following format:
261261
{
262262
"container_id": "dom-id-without-#", // the ID of the error container you want to append this error message to
263263
"element_to_invalidate_id": "dom-id-without-#", // the ID of the input that should be marked as invalid
264-
"message": "The error message to render",
264+
"message": "The error message to render", // The plaintext message used for the error
265+
"html_content": "<strong>Rich</strong> Markup", // an OPTIONAL HTML string that will be parsed using `DOMParser` and used as the message. If present, `message` is ignored
265266
"type": "error-type-key", //The identifier for what type of error this is
266267
},
267268
// ...

demo/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
message: "General errors from the server-side",
5555
type: "general"
5656
},
57+
{
58+
container_id: "test-form-error-container",
59+
element_to_invalidate_id: "test-form",
60+
message: "This message content will be ignored",
61+
html_content: `<strong>Richly <a href="https://example.com">rendered</a> errors!</strong>`,
62+
type: "rich_error"
63+
},
5764
{
5865
container_id: "random-missing-field-errors",
5966
element_to_invalidate_id: "random-missing-field",

src/error-mapping.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import {
1414
clearErrorListsInForm,
1515
errorMessageListItem,
16+
errorMessageListItemWithHTML,
1617
markErrorTypeAsVisible
1718
} from './rendering.js'
1819

@@ -30,6 +31,7 @@ import {
3031
* @param {string} errors[].container_id - The ID of the container this error message should be rendered in
3132
* @param {string} errors[].element_id - The ID of the element to call {@link setValidityStateAttributes} with `false` for
3233
* @param {string} errors[].message - the error message
34+
* @param {string} errors[].html_content - An optional HTML string that can be used to render rich markup in an error message. If present, `message` is ignored
3335
* @param {string} errors[].type - the value for `data-pf-error-type` that represents the type of error this is
3436
*
3537
* @example
@@ -74,7 +76,13 @@ export function applyErrorMappingToForm(form, errors) {
7476
const preservedError = getPreservedErrorForType(errorList, error.type)
7577
preservedError?.remove()
7678

77-
let listItem = errorMessageListItem(error.message, error.type)
79+
let listItem
80+
81+
if(error.html_content){
82+
listItem = errorMessageListItemWithHTML(error.html_content, error.type)
83+
} else {
84+
listItem = errorMessageListItem(error.message, error.type)
85+
}
7886

7987
if(shouldBePreserved) {
8088
markAsPreservedError(listItem)

src/rendering.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,24 @@ export function errorMessageListItem(message, type) {
7070
return clone
7171
}
7272

73+
/**
74+
* Clones the {@link errorListItemTemplate}, parsing the given HTML string and inserting it into the element with `[data-pf-error-message]` to the message,
75+
* and setting the `data-pf-error-type` to the given type.
76+
*
77+
* @params {string} htmlString the HTML content to render in the string
78+
* @params {string} type the value that will be used for `data-pf-error-type``
79+
* @returns {Element} the cloned element
80+
*/
81+
export function errorMessageListItemWithHTML(htmlString, type) {
82+
const clone = errorListItemTemplate().content.cloneNode(true).querySelector(`*:first-child`)
83+
84+
const parsedDocument = new DOMParser().parseFromString(htmlString, "text/html")
85+
86+
clone.setAttribute(`data-pf-error-type`, type)
87+
clone.querySelector(`[data-pf-error-message]`).replaceChildren(parsedDocument.body.firstChild)
88+
return clone
89+
}
90+
7391
/**
7492
* Clears the error messages from the list, while keeping preserved errors.
7593
* Removes the `data-pf-error-visible` attribute from any preserved attributes

test/error-mapping.test.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ suite('Error Mapping', async () => {
106106
type: `unconfirmed_error`,
107107
message: "Please confirm"
108108
},
109+
{
110+
container_id: `confirm-email-field-errors`,
111+
element_to_invalidate_id: `confirm-email-field`,
112+
type: `custom_error_with_html_content`,
113+
message: "This message body should be ignored",
114+
html_content: `<strong>Richly <a href="https://example.com">rendered</a> errors!</strong>`
115+
}
109116
]
110117

111118
const form = container.querySelector(`form`)
@@ -145,7 +152,7 @@ suite('Error Mapping', async () => {
145152
document.getElementById(`email-field-errors`).querySelector(`[data-pf-error-visible][data-pf-error-type="ad_hoc_server_error_5"]`).textContent
146153
)
147154

148-
assert.equal(2, document.getElementById(`confirm-email-field-errors`).querySelectorAll(`[data-pf-error-type][data-pf-error-visible]`).length)
155+
assert.equal(3, document.getElementById(`confirm-email-field-errors`).querySelectorAll(`[data-pf-error-type][data-pf-error-visible]`).length)
149156
assert.equal(
150157
"‼️ Ad-hoc server error 6",
151158
document.getElementById(`confirm-email-field-errors`).querySelector(`[data-pf-error-visible][data-pf-error-type="ad_hoc_server_error_6"]`).textContent
@@ -155,5 +162,12 @@ suite('Error Mapping', async () => {
155162
"‼️ Please confirm",
156163
document.getElementById(`confirm-email-field-errors`).querySelector(`[data-pf-error-visible][data-pf-error-type="unconfirmed_error"]`).textContent
157164
)
165+
166+
const richError = document.getElementById(`confirm-email-field-errors`).querySelector(`[data-pf-error-visible][data-pf-error-type="custom_error_with_html_content"]`)
167+
168+
assert.equal(
169+
`<span>‼️</span> <span data-pf-error-message=""><strong>Richly <a href="https://example.com">rendered</a> errors!</strong></span>`,
170+
richError.innerHTML
171+
)
158172
})
159173
})

test/rendering.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,30 @@ suite('Rendering', async () => {
8787
assert.equal(true, itemElement.hasAttribute(`data-pf-error-preserve`))
8888
})
8989

90+
test(`errorMessageListItemWithHTML`, async() => {
91+
const container = await fixture(html`
92+
<div>
93+
<p>Some content</p>
94+
95+
<template id="pf-error-list-item-template">
96+
<li><span>‼️</span> <span data-pf-error-message></span></li>
97+
</template>
98+
</div>
99+
`)
100+
101+
const htmlString = `<strong>Richly <a href="https://example.com">rendered</a> errors!</strong>`
102+
103+
const itemElement = Rendering.errorMessageListItemWithHTML(htmlString, "a-custom-type")
104+
105+
assert.equal("a-custom-type", itemElement.getAttribute(`data-pf-error-type`))
106+
assert.equal(htmlString, itemElement.querySelector(`[data-pf-error-message]`).innerHTML)
107+
assert.equal("‼️ Richly rendered errors!", itemElement.textContent)
108+
assert.equal(false, itemElement.hasAttribute(`data-pf-error-preserve`))
109+
110+
itemElement.setAttribute(`data-pf-error-preserve`, true)
111+
assert.equal(true, itemElement.hasAttribute(`data-pf-error-preserve`))
112+
})
113+
90114
test(`renderConstraintValidationMessageForElement: the input is valid`, async () => {
91115
const container = await fixture(html`
92116
<div>

0 commit comments

Comments
 (0)