From b8cbb0802692a8b8e4a2d42707bf324a0f5028da Mon Sep 17 00:00:00 2001 From: Helen Hou-Sandi Date: Wed, 2 Mar 2022 20:15:50 -0500 Subject: [PATCH 1/8] Add support for checkboxes and radio buttons This feels a little rough around the edges, but it's a working start. --- src/index.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2852d45..3b18b46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,12 @@ let submittedForm: HTMLFormElement | null = null function shouldResumeField(field: HTMLInputElement | HTMLTextAreaElement, filter: StorageFilter): boolean { - return !!field.id && filter(field) && field.form !== submittedForm + return ( + !!field.id && + (field.value !== field.defaultValue || + (field instanceof HTMLInputElement && field.checked !== field.defaultChecked)) && + field.form !== submittedForm + ) } function valueIsUnchanged(field: HTMLInputElement | HTMLTextAreaElement): boolean { @@ -112,7 +117,10 @@ 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 (field instanceof HTMLInputElement && (field.type === 'checkbox' || field.type === 'radio')) { + field.checked = true + changedFields.push(field) + } else if (field.value === field.defaultValue) { field.value = value changedFields.push(field) } From ee09672377d7c46bd325dcc0bf4ac68bb296cfc5 Mon Sep 17 00:00:00 2001 From: Helen Hou-Sandi Date: Wed, 2 Mar 2022 20:25:13 -0500 Subject: [PATCH 2/8] Add example file Includes a button to set sessionStorage for easier testing/debugging --- examples/index.html | 87 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 examples/index.html diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..a9dd2b3 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,87 @@ + + + + + session-resume demo + + + +

session-resume

+ +

Test by filling out the form and then refreshing the page or navigating away and back.

+ +
+

+ +

+

+ +

+

+ +

+

+ +

+
+ Checkboxes +
+ +
+ +
+ + +
+
+
+ Radio +
+ +
+ + +
+
+ + +
+ + + + From 26fe19f54e2f545a10eae20a2bae22134c7cbaaf Mon Sep 17 00:00:00 2001 From: Helen Hou-Sandi Date: Wed, 2 Mar 2022 20:27:16 -0500 Subject: [PATCH 3/8] Add a checkbox test --- test/test.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index adcf236..c129ea3 100644 --- a/test/test.js +++ b/test/test.js @@ -8,6 +8,8 @@ describe('session-resume', function () {
+ +
` window.addEventListener('submit', sessionStorage.setForm, {capture: true}) @@ -15,11 +17,19 @@ describe('session-resume', function () { 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-first-checkbox', 'first-checkbox-value'] + ]) + ) 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-first-checkbox').checked, true) + assert.equal(document.querySelector('#my-second-checkbox').checked, false) }) it('uses a Storage object when provided as an option', function () { From a784967e72eb87b67ae938112fae1ddb451505f0 Mon Sep 17 00:00:00 2001 From: Helen Hou-Sandi Date: Wed, 2 Mar 2022 20:48:23 -0500 Subject: [PATCH 4/8] Default to the public built file for the test page --- examples/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/index.html b/examples/index.html index a9dd2b3..6b406b5 100644 --- a/examples/index.html +++ b/examples/index.html @@ -60,8 +60,8 @@

Test by filling out the form and then refreshing the page or - import {persistResumableFields, restoreResumableFields, setForm} from '../dist/index.js' - // import {persistResumableFields, restoreResumableFields, setForm} from 'https://unpkg.com/@github/session-resume/dist/index.js' + // import {persistResumableFields, restoreResumableFields, setForm} from '../dist/index.js' + import {persistResumableFields, restoreResumableFields, setForm} from 'https://unpkg.com/@github/session-resume/dist/index.js' // Listen for all form submit events and to see if their default submission // behavior is invoked. From b3f5260d1a89c2bfd62fce4c3f8dc4fb71455ab6 Mon Sep 17 00:00:00 2001 From: Helen Hou-Sandi Date: Wed, 9 Mar 2022 21:33:01 -0500 Subject: [PATCH 5/8] Account for default checked checkboxes --- examples/index.html | 8 ++++---- src/index.ts | 2 +- test/test.js | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/index.html b/examples/index.html index 6b406b5..127d918 100644 --- a/examples/index.html +++ b/examples/index.html @@ -38,8 +38,8 @@

Test by filling out the form and then refreshing the page or -
+ +

@@ -60,8 +60,8 @@

Test by filling out the form and then refreshing the page or - // import {persistResumableFields, restoreResumableFields, setForm} from '../dist/index.js' - import {persistResumableFields, restoreResumableFields, setForm} from 'https://unpkg.com/@github/session-resume/dist/index.js' + import {persistResumableFields, restoreResumableFields, setForm} from '../dist/index.js' + // import {persistResumableFields, restoreResumableFields, setForm} from 'https://unpkg.com/@github/session-resume/dist/index.js' // Listen for all form submit events and to see if their default submission // behavior is invoked. diff --git a/src/index.ts b/src/index.ts index 3b18b46..541aa37 100644 --- a/src/index.ts +++ b/src/index.ts @@ -118,7 +118,7 @@ export function restoreResumableFields(id: string, options?: RestoreOptions): vo const field = document.getElementById(fieldId) if (field && (field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement)) { if (field instanceof HTMLInputElement && (field.type === 'checkbox' || field.type === 'radio')) { - field.checked = true + field.checked = !field.defaultChecked changedFields.push(field) } else if (field.value === field.defaultValue) { field.value = value diff --git a/test/test.js b/test/test.js index c129ea3..c490361 100644 --- a/test/test.js +++ b/test/test.js @@ -10,6 +10,7 @@ describe('session-resume', function () { + ` window.addEventListener('submit', sessionStorage.setForm, {capture: true}) @@ -21,7 +22,8 @@ describe('session-resume', function () { 'session-resume:test-persist', JSON.stringify([ ['my-first-field', 'test2'], - ['my-first-checkbox', 'first-checkbox-value'] + ['my-first-checkbox', 'first-checkbox-value'], + ['my-checked-checkbox', 'checked-checkbox-value'] ]) ) restoreResumableFields('test-persist') @@ -30,6 +32,7 @@ describe('session-resume', function () { assert.equal(document.querySelector('#my-second-field').value, 'second-field-value') 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) }) it('uses a Storage object when provided as an option', function () { From f20740ab0cd6da755dc255702acdb5931888e429 Mon Sep 17 00:00:00 2001 From: Helen Hou-Sandi Date: Wed, 9 Mar 2022 21:34:05 -0500 Subject: [PATCH 6/8] Keep the external script for test page by default --- examples/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/index.html b/examples/index.html index 127d918..bfa0126 100644 --- a/examples/index.html +++ b/examples/index.html @@ -60,8 +60,8 @@

Test by filling out the form and then refreshing the page or - import {persistResumableFields, restoreResumableFields, setForm} from '../dist/index.js' - // import {persistResumableFields, restoreResumableFields, setForm} from 'https://unpkg.com/@github/session-resume/dist/index.js' + // import {persistResumableFields, restoreResumableFields, setForm} from '../dist/index.js' + import {persistResumableFields, restoreResumableFields, setForm} from 'https://unpkg.com/@github/session-resume/dist/index.js' // Listen for all form submit events and to see if their default submission // behavior is invoked. From 909d41cc2f6b2ac3c254f112e20c2de905dcb517 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Thu, 25 Jan 2024 19:50:54 -0500 Subject: [PATCH 7/8] Incorporate checkables into `storageFilter:` --- README.md | 2 +- src/index.ts | 25 +++++++++++++++++-------- test/test.js | 18 ++++++++++++++++-- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9bee76e..7f8b587 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ The `restoreResumableFields(id: string, options)` function supports optional con The `persistResumableFields(id: string, options)` function supports optional configurations: * `storage:` - [`Storage`][] instance (defaults to [`window.sessionStorage`][]) -* `storageFilter:` - `(field: HTMLInputElement | HTMLTextAreaElement) => boolean` predicate to determine whether or not to store a field (defaults to `(field) => field.value !== field.defaultValue`) +* `storageFilter:` - `(field: HTMLInputElement | HTMLTextAreaElement) => boolean` predicate to determine whether or not to store a field (defaults to `(field) => field.checked !== field.defaultChecked)` for checkbox and radio buttons, `(field) => field.value !== field.defaultValue` otherwise) * `keyPrefix:` - `string` prepended onto the storage key (defaults to `"session-resume"`) * `scope:` - `ParentNode` used to query field elements (defaults to `document`) * `selector:` - `string` used to query field elements (defaults to `".js-session-resumable"`) diff --git a/src/index.ts b/src/index.ts index 541aa37..4cab310 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,16 +2,15 @@ let submittedForm: HTMLFormElement | null = null function shouldResumeField(field: HTMLInputElement | HTMLTextAreaElement, filter: StorageFilter): boolean { - return ( - !!field.id && - (field.value !== field.defaultValue || - (field instanceof HTMLInputElement && field.checked !== field.defaultChecked)) && - field.form !== submittedForm - ) + return !!field.id && filter(field) && field.form !== submittedForm } function valueIsUnchanged(field: HTMLInputElement | HTMLTextAreaElement): boolean { - return field.value !== field.defaultValue + if (isHTMLCheckableInputElement(field)) { + return field.checked !== field.defaultChecked + } else { + return field.value !== field.defaultValue + } } type StorageFilter = (field: HTMLInputElement | HTMLTextAreaElement) => boolean @@ -34,6 +33,16 @@ type PersistOptions = (PersistOptionsWithSelector | PersistOptionsWithFields) & storageFilter?: StorageFilter } +type HTMLCheckableInputElement = HTMLInputElement & { + type: 'checkbox' | 'radio' +} + +function isHTMLCheckableInputElement( + field: HTMLInputElement | HTMLTextAreaElement +): field is HTMLCheckableInputElement { + return field instanceof HTMLInputElement && /checkbox|radio/.test(field.type) +} + // Write all ids and values of the selected fields on the page into sessionStorage. export function persistResumableFields(id: string, options?: PersistOptions): void { const scope = options?.scope ?? document @@ -117,7 +126,7 @@ 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 instanceof HTMLInputElement && (field.type === 'checkbox' || field.type === 'radio')) { + if (isHTMLCheckableInputElement(field)) { field.checked = !field.defaultChecked changedFields.push(field) } else if (field.value === field.defaultValue) { diff --git a/test/test.js b/test/test.js index c490361..faf4d33 100644 --- a/test/test.js +++ b/test/test.js @@ -97,8 +97,8 @@ describe('session-resume', function () { assert.deepEqual(fieldsRestored, {'my-first-field': 'test2'}) }) - it('fires off change for changed fields', function (done) { - for (const input of document.querySelectorAll('input')) { + it('fires off change for changed input[type=text] fields', function (done) { + for (const input of document.querySelectorAll('input[type=text]')) { input.addEventListener('change', function (event) { done(assert.equal(event.target.id, 'my-first-field')) }) @@ -107,6 +107,20 @@ describe('session-resume', function () { sessionStorage.setItem('session-resume:test-persist', JSON.stringify([['my-first-field', 'test2']])) restoreResumableFields('test-persist') }) + + it('fires off change for changed input[type=checkbox] fields', function (done) { + for (const input of document.querySelectorAll('input[type=checkbox]')) { + input.addEventListener('change', function (event) { + done(assert.equal(event.target.id, 'my-first-checkbox')) + }) + } + + sessionStorage.setItem( + 'session-resume:test-persist', + JSON.stringify([['my-first-checkbox', 'first-checkbox-value']]) + ) + restoreResumableFields('test-persist') + }) }) describe('persistResumableFields', function () { From 0ef13a766d18c31f586097dbee56e37196574a8f Mon Sep 17 00:00:00 2001 From: Ned Schwartz Date: Tue, 6 Feb 2024 17:28:02 -0500 Subject: [PATCH 8/8] fix tests --- test/test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test.js b/test/test.js index faf4d33..cd91d95 100644 --- a/test/test.js +++ b/test/test.js @@ -6,8 +6,8 @@ describe('session-resume', function () { // eslint-disable-next-line github/no-inner-html document.body.innerHTML = `
- - + + @@ -129,7 +129,7 @@ describe('session-resume', function () { document.querySelector('#my-second-field').value = 'test2' persistResumableFields('test-persist') - 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'] ]) @@ -151,7 +151,7 @@ describe('session-resume', function () { persistResumableFields('test-persist', {storage: fakeStorage}) - assert.deepEqual(JSON.parse(fakeStorage.getItem('session-resume:test-persist')), [ + assert.includeDeepMembers(JSON.parse(fakeStorage.getItem('session-resume:test-persist')), [ ['my-first-field', 'test1'], ['my-second-field', 'test2'] ]) @@ -164,7 +164,7 @@ describe('session-resume', function () { persistResumableFields('test-persist') - 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'], ['non-existant-field', 'test3'] @@ -178,7 +178,7 @@ describe('session-resume', function () { persistResumableFields('test-persist') - 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'] ])