Skip to content

Commit

Permalink
Support select:not([multiple]) elements
Browse files Browse the repository at this point in the history
First, introduce the `PersistElement` type to incorporate `<input>`,
`<textarea>`, and `<select>` elements.

Next, incorporate some special handling for `HTMLSelectElement`, since
they don't have a `.defaultValue` property.

Unfortunately, setting `HTMLSelectElement.value` to anything other than
a single `string` value clears it. This means that `<select>` elements
with the `[multiple]` attribute cannot restore previously selected
`<option>` elements.
  • Loading branch information
seanpdoyle committed Feb 1, 2024
1 parent 30de742 commit 71a2686
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 13 deletions.
34 changes: 25 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
// Last submitted HTMLFormElement that will cause a browser navigation.
let submittedForm: HTMLFormElement | null = null

function shouldResumeField(field: HTMLInputElement | HTMLTextAreaElement, filter: StorageFilter): boolean {
function shouldResumeField(field: PersistElement, filter: StorageFilter): boolean {
return !!field.id && filter(field) && field.form !== submittedForm
}

function valueIsUnchanged(field: HTMLInputElement | HTMLTextAreaElement): boolean {
return field.value !== field.defaultValue
function valueIsUnchanged(field: PersistElement): boolean {
if (field instanceof HTMLSelectElement) {
return true
} else {
return field.value !== field.defaultValue
}
}

function isPersistElement(node: Node | null): node is PersistElement {
if (node instanceof HTMLSelectElement) {
return !node.multiple
} else {
return node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement
}
}

type StorageFilter = (field: HTMLInputElement | HTMLTextAreaElement) => boolean
type PersistElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement

type StorageFilter = (field: PersistElement) => boolean

type PersistOptionsWithSelector = {
scope?: ParentNode
Expand Down Expand Up @@ -49,13 +63,12 @@ export function persistResumableFields(id: string, options?: PersistOptions): vo
const resumables = []

for (const el of elements) {
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
if (isPersistElement(el)) {
resumables.push(el)
}
}

let fields = resumables.filter(field => shouldResumeField(field, storageFilter)).map(field => [field.id, field.value])

if (fields.length) {
try {
const previouslyStoredFieldsJson = storage.getItem(key)
Expand Down Expand Up @@ -99,7 +112,7 @@ export function restoreResumableFields(id: string, options?: RestoreOptions): vo

if (!fields) return

const changedFields: Array<HTMLInputElement | HTMLTextAreaElement> = []
const changedFields: PersistElement[] = []
const storedFieldsNotFound: string[][] = []

for (const [fieldId, value] of JSON.parse(fields)) {
Expand All @@ -111,8 +124,11 @@ export function restoreResumableFields(id: string, options?: RestoreOptions): vo

if (document.dispatchEvent(resumeEvent)) {
const field = document.getElementById(fieldId)
if (field && (field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement)) {
if (field.value === field.defaultValue) {
if (isPersistElement(field)) {
if (field instanceof HTMLSelectElement) {
field.value = value
changedFields.push(field)
} else if (field.value === field.defaultValue) {
field.value = value
changedFields.push(field)
}
Expand Down
32 changes: 28 additions & 4 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,33 @@ describe('session-resume', function () {
<form>
<input id="my-first-field" value="first-field-value" class="js-session-resumable" />
<input id="my-second-field" value="second-field-value" class="js-session-resumable" />
<select id="my-single-select-field" class="js-session-resumable">
<option value="first">first</option>
<option value="second">second</option>
</select>
<select id="my-multiple-select-field" class="js-session-resumable" multiple>
<option value="first" selected>first</option>
<option value="second" selected>second</option>
</select>
</form>
`
window.addEventListener('submit', sessionStorage.setForm, {capture: true})
})

describe('restoreResumableFields', function () {
it('restores fields values from session storage by default', function () {
sessionStorage.setItem('session-resume:test-persist', JSON.stringify([['my-first-field', 'test2']]))
sessionStorage.setItem(
'session-resume:test-persist',
JSON.stringify([
['my-first-field', 'test2'],
['my-single-select-field', 'second']
])
)
restoreResumableFields('test-persist')

assert.equal(document.querySelector('#my-first-field').value, 'test2')
assert.equal(document.querySelector('#my-second-field').value, 'second-field-value')
assert.equal(document.querySelector('#my-single-select-field').value, 'second')
})

it('uses a Storage object when provided as an option', function () {
Expand All @@ -38,6 +53,7 @@ describe('session-resume', function () {

assert.equal(document.querySelector('#my-first-field').value, 'test2')
assert.equal(document.querySelector('#my-second-field').value, 'second-field-value')
assert.equal(document.querySelector('#my-single-select-field').value, 'second')
})

it('leaves unrestored values in session storage', function () {
Expand Down Expand Up @@ -104,13 +120,15 @@ describe('session-resume', function () {

assert.deepEqual(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
['my-first-field', 'test1'],
['my-second-field', 'test2']
['my-second-field', 'test2'],
['my-single-select-field', 'second']
])
})

it('uses a Storage object when provided as an option', function () {
document.querySelector('#my-first-field').value = 'test1'
document.querySelector('#my-second-field').value = 'test2'
document.querySelector('#my-single-select-field').value = 'second'

const fakeStorageBackend = {}
const fakeStorage = {
Expand All @@ -126,20 +144,23 @@ describe('session-resume', function () {

assert.deepEqual(JSON.parse(fakeStorage.getItem('session-resume:test-persist')), [
['my-first-field', 'test1'],
['my-second-field', 'test2']
['my-second-field', 'test2'],
['my-single-select-field', 'second']
])
})

it('holds onto existing values in the store', function () {
sessionStorage.setItem('session-resume:test-persist', JSON.stringify([['non-existant-field', 'test3']]))
document.querySelector('#my-first-field').value = 'test1'
document.querySelector('#my-second-field').value = 'test2'
document.querySelector('#my-single-select-field').value = 'second'

persistResumableFields('test-persist')

assert.deepEqual(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
['my-first-field', 'test1'],
['my-second-field', 'test2'],
['my-single-select-field', 'second'],
['non-existant-field', 'test3']
])
})
Expand All @@ -148,12 +169,14 @@ describe('session-resume', function () {
sessionStorage.setItem('session-resume:test-persist', JSON.stringify([['my-first-field', 'old data']]))
document.querySelector('#my-first-field').value = 'test1'
document.querySelector('#my-second-field').value = 'test2'
document.querySelector('#my-single-select-field').value = 'second'

persistResumableFields('test-persist')

assert.deepEqual(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
['my-first-field', 'test1'],
['my-second-field', 'test2']
['my-second-field', 'test2'],
['my-single-select-field', 'second']
])
})

Expand Down Expand Up @@ -188,6 +211,7 @@ describe('session-resume', function () {
['my-second-field', 'test2']
])
})

it('scopes fields based on the fields: option', function () {
document.getElementById('my-first-field').value = 'test1'
document.getElementById('my-second-field').value = 'test2'
Expand Down

0 comments on commit 71a2686

Please sign in to comment.