Skip to content

Commit

Permalink
Merge pull request #55 from seanpdoyle/html-select-element-support
Browse files Browse the repository at this point in the history
Support `select` elements
  • Loading branch information
theinterned authored Feb 8, 2024
2 parents bfb858e + 8154f26 commit 7b4243a
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 20 deletions.
40 changes: 30 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
// 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: PersistableElement, filter: StorageFilter): boolean {
return !!field.id && filter(field) && field.form !== submittedForm
}

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

type StorageFilter = (field: HTMLInputElement | HTMLTextAreaElement) => boolean
function isPersistableElement(node: Node | null): node is PersistableElement {
return node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement || node instanceof HTMLSelectElement
}

type PersistableElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement

type StorageFilter = (field: PersistableElement) => boolean

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

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

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

let fields = resumables
.filter(field => shouldResumeField(field, storageFilter))
.map(field => {
if (field instanceof HTMLSelectElement) {
return [field.id, Array.from(field.selectedOptions).map(option => option.value)]
} else {
return [field.id, field.value]
}
})
if (fields.length) {
try {
const previouslyStoredFieldsJson = storage.getItem(key)
Expand Down Expand Up @@ -113,7 +128,7 @@ export function restoreResumableFields(id: string, options?: RestoreOptions): vo

if (!fields) return

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

for (const [fieldId, value] of JSON.parse(fields)) {
Expand All @@ -125,8 +140,13 @@ 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 (isHTMLCheckableInputElement(field)) {
if (isPersistableElement(field)) {
if (field instanceof HTMLSelectElement) {
for (const option of field.options) {
option.selected = value.includes(option.value)
}
changedFields.push(field)
} else if (isHTMLCheckableInputElement(field)) {
field.checked = !field.defaultChecked
changedFields.push(field)
} else if (field.value === field.defaultValue) {
Expand Down
74 changes: 64 additions & 10 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ describe('session-resume', function () {
<input id="my-first-checkbox" type="checkbox" value="first-checkbox-value" class="js-session-resumable" />
<input id="my-second-checkbox" type="checkbox" value="second-checkbox-value" class="js-session-resumable" />
<input id="my-checked-checkbox" type="checkbox" value="checked-checkbox-value" class="js-session-resumable" checked />
<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">first</option>
<option value="second">second</option>
</select>
</form>
`
window.addEventListener('submit', sessionStorage.setForm, {capture: true})
Expand All @@ -23,7 +31,9 @@ describe('session-resume', function () {
JSON.stringify([
['my-first-field', 'test2'],
['my-first-checkbox', 'first-checkbox-value'],
['my-checked-checkbox', 'checked-checkbox-value']
['my-checked-checkbox', 'checked-checkbox-value'],
['my-single-select-field', ['second']],
['my-multiple-select-field', ['first', 'second']]
])
)
restoreResumableFields('test-persist')
Expand All @@ -33,6 +43,9 @@ describe('session-resume', function () {
assert.equal(document.querySelector('#my-first-checkbox').checked, true)
assert.equal(document.querySelector('#my-second-checkbox').checked, false)
assert.equal(document.querySelector('#my-checked-checkbox').checked, false)
assert.equal(document.querySelector('#my-single-select-field').value, 'second')
assert.equal(document.querySelector('#my-multiple-select-field option[value=first]').selected, true)
assert.equal(document.querySelector('#my-multiple-select-field option[value=second]').selected, true)
})

it('uses a Storage object when provided as an option', function () {
Expand All @@ -46,11 +59,21 @@ describe('session-resume', function () {
}
}

fakeStorage.setItem('session-resume:test-persist', JSON.stringify([['my-first-field', 'test2']]))
fakeStorage.setItem(
'session-resume:test-persist',
JSON.stringify([
['my-first-field', 'test2'],
['my-single-select-field', ['second']],
['my-multiple-select-field', ['first', 'second']]
])
)
restoreResumableFields('test-persist', {storage: fakeStorage})

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')
assert.equal(document.querySelector('#my-multiple-select-field option[value=first]').selected, true)
assert.equal(document.querySelector('#my-multiple-select-field option[value=second]').selected, true)
})

it('leaves unrestored values in session storage', function () {
Expand All @@ -71,7 +94,7 @@ describe('session-resume', function () {

// Some fields we want to restore are not always present in the DOM
// and may be added later. We hold onto the values until they're needed.
assert.deepEqual(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
assert.includeDeepMembers(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
['non-existant-field', 'test3']
])
})
Expand Down Expand Up @@ -127,17 +150,25 @@ describe('session-resume', function () {
it('persist fields values to session storage by default', function () {
document.querySelector('#my-first-field').value = 'test1'
document.querySelector('#my-second-field').value = 'test2'
document.querySelector('#my-single-select-field').value = 'first'
document.querySelector('#my-multiple-select-field option[value=first]').selected = true
document.querySelector('#my-multiple-select-field option[value=second]').selected = true
persistResumableFields('test-persist')

assert.includeDeepMembers(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
['my-first-field', 'test1'],
['my-second-field', 'test2']
['my-second-field', 'test2'],
['my-single-select-field', ['first']],
['my-multiple-select-field', ['first', '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'
document.querySelector('#my-multiple-select-field option[value=first]').selected = true
document.querySelector('#my-multiple-select-field option[value=second]').selected = true

const fakeStorageBackend = {}
const fakeStorage = {
Expand All @@ -153,34 +184,54 @@ describe('session-resume', function () {

assert.includeDeepMembers(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']],
['my-multiple-select-field', ['first', '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'
document.querySelector('#my-multiple-select-field option[value=first]').selected = true
document.querySelector('#my-multiple-select-field option[value=second]').selected = true

persistResumableFields('test-persist')

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

it('replaces old values with the latest field values', function () {
sessionStorage.setItem('session-resume:test-persist', JSON.stringify([['my-first-field', 'old data']]))
sessionStorage.setItem(
'session-resume:test-persist',
JSON.stringify([
['my-first-field', 'old data'],
['my-second-field', 'old data'],
['my-single-select-field', 'first'],
['my-multiple-select-field', ['first', 'second']]
])
)
document.querySelector('#my-first-field').value = 'test1'
document.querySelector('#my-second-field').value = 'test2'
document.querySelector('#my-single-select-field').value = 'second'
document.querySelector('#my-multiple-select-field option[value=first]').selected = false
document.querySelector('#my-multiple-select-field option[value=second]').selected = true

persistResumableFields('test-persist')

assert.includeDeepMembers(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']],
['my-multiple-select-field', ['second']]
])
})

Expand All @@ -191,7 +242,9 @@ describe('session-resume', function () {
sessionStorage.clear()
persistResumableFields('test-persist', {selector: '#my-first-field'})

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

it('scopes fields based on the scope: option', function () {
Expand All @@ -210,19 +263,20 @@ describe('session-resume', function () {
sessionStorage.clear()
persistResumableFields('test-persist', {scope: document.querySelector('form')})

assert.deepEqual(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
assert.includeDeepMembers(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
['my-first-field', 'test1'],
['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'

sessionStorage.clear()
persistResumableFields('test-persist', {fields: document.querySelectorAll('#my-second-field')})

assert.deepEqual(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
assert.includeDeepMembers(JSON.parse(sessionStorage.getItem('session-resume:test-persist')), [
['my-second-field', 'test2']
])
})
Expand Down

0 comments on commit 7b4243a

Please sign in to comment.