Skip to content

Commit

Permalink
Include functionality for select tags (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
JHStoops authored and kyle-west committed Aug 13, 2019
1 parent 04ab6dc commit 7503bd5
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 39 deletions.
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

[![Build Status](https://travis-ci.com/kyle-west/persistent-state.svg?branch=master)](https://travis-ci.com/kyle-west/persistent-state) [![Latest Version](https://img.shields.io/github/release/kyle-west/persistent-state.svg)](https://github.com/kyle-west/persistent-state/releases/latest) [![Licence](https://img.shields.io/github/license/kyle-west/persistent-state.svg)](https://github.com/kyle-west/persistent-state/blob/master/LICENSE) ![Size](https://img.shields.io/github/size/kyle-west/persistent-state/persistent-state.js.svg)

A native web component that holds onto the state of input elements during a
A native web component that holds onto the state of input elements during a
session and/or between sessions.

![Visual Example](./demo/example.gif)

# Installation
# Installation

Any of the following commands will install `persistent-state`. Just pick your
Any of the following commands will install `persistent-state`. Just pick your
package manager.

```sh
Expand Down Expand Up @@ -39,10 +39,10 @@ file is also available if you wish to use script:src sourcing instead of HTML im

Wrap your elements in a `<persistent-state>` tag to activate. The default case
uses `localStorage` to store state which will persist information between sessions.
If you wish to only store information for a session, add the `type="session"`
If you wish to only store information for a session, add the `type="session"`
attribute. For the best experience, please provide each element with an `id`.

If you have many `<persistent-state>` elements in a DOM, it is recommended that
If you have many `<persistent-state>` elements in a DOM, it is recommended that
you provide an `id` for each `<persistent-state>` to avoid name collisions.

```html
Expand All @@ -58,27 +58,26 @@ you provide an `id` for each `<persistent-state>` to avoid name collisions.
## Custom Storage Keys

Adding the `key` attribute will allow the input elements to have their values
each stored under a key computed from the given `key` and `id` attributes.
each stored under a key computed from the given `key` and `id` attributes.

```html
<persistent-state key="customKey">
<input id="has-custom-key" type="text">
</persistent-state>
```


## Supported Elements

Currently, the only supported elements are `<input>` and `<textarea>` tags.
If you have a custom element you wish to add support to, you can register it
Currently, the only supported elements are `<input>`, `<select>`, and `<textarea>` tags.
If you have a custom element you wish to add support to, you can register it
manually with the following:

```js
new PersistentStateRegistry().supportedTags.push('my-custom-input-element');
```

In this example, `<persistent-state>` will only work if `<my-custom-input-element>`
has a `value` attribute and fires an `input` event when the value changes.
has a `value` attribute and fires an `input` event when the value changes.

<details>
<summary><strong>Here is an exhaustive list of all the support <code>input</code> types</strong></summary>
Expand Down Expand Up @@ -116,7 +115,7 @@ Note that with `radio` buttons the name has to be consistent between the element

## Events

The `PersistentState::ElementInitialized` event is fired when `PersistentState` updates
The `PersistentState::ElementInitialized` event is fired when `PersistentState` updates
the value of an element.

```js
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "persistent-state",
"version": "1.5.2",
"version": "1.6.0",
"description": "A native web component that holds onto the state of input elements during a session and/or between sessions.",
"main": "persistent-state.js",
"authors": [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "persistent-state",
"version": "1.5.2",
"version": "1.6.0",
"description": "A native web component that holds onto the state of input elements during a session and/or between sessions.",
"main": "persistent-state.js",
"scripts": {
Expand Down
8 changes: 4 additions & 4 deletions persistent-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ window.PersistentStateRegistry = (() => {
class PersistentStateRegistry {
constructor () {
if (instance) return instance;
this.supportedTags = ["input", "textarea"];
this.supportedTags = ["input", "select", "textarea"];
this.supportedInputTypes = [
"checkbox",
"color",
Expand Down Expand Up @@ -119,10 +119,10 @@ class PersistentState extends HTMLElement {
if (!PersistentStateRegistry.supported(elem)) return;

let key = this.getKey(elem, idx);

this.initializeValue(key, elem);
this.setupObservers(key, elem);

elem.__persistent_state__initialized = true;
elem.dispatchEvent(new CustomEvent('PersistentState::ElementInitialized', {
bubbles: true,
Expand All @@ -133,7 +133,7 @@ class PersistentState extends HTMLElement {
}

setupObservers (key, elem) {
if ('radio' === elem.type) {
if ('radio' === elem.type || 'SELECT' === elem.tagName) {
elem.addEventListener('change', (e) => {
this.storage.set(key, e.currentTarget.value, this.type, this._storageId)
});
Expand Down
75 changes: 53 additions & 22 deletions persistent-state.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,34 @@ let server = null;

beforeAll((done) => {
const testApp = express()

// allow specific static files to be accessed only
testApp.use('/test', express.static('test'))
testApp.use('/persistent-state.js', (req, res) => res.sendFile(__dirname + '/persistent-state.js'))

server = testApp.listen(port, done)
});

let loadTestPage = new Promise((resolve) => {
setTimeout(() => { // give time for the server to load up before we begin the tests
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(`http://localhost:${port}/test/persistent-state_test.html`, {waitUntil : ['load', 'domcontentloaded']});
resolve({browser, page})
})();
}, 50)
})
const openBrowsers = [];

function getNewPage() {
return new Promise((resolve) => {
setTimeout(() => { // give time for the server to load up before we begin the tests
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(`http://localhost:${port}/test/persistent-state_test.html`, {waitUntil : ['load', 'domcontentloaded']});
openBrowsers.push(browser)
resolve({browser, page})
})();
}, 50)
})
}

let loadTestPage = getNewPage();

afterAll(async (done) => {
await loadTestPage.then(({browser}) => browser.close())
await Promise.all(openBrowsers.map(browser => browser.close()))
server.close(done)
});

Expand All @@ -52,7 +59,7 @@ test(`data is saved to the localStorage when type=""`, () => {
return loadTestPage.then(async ({ page }) => {
const KEY = 'PersistentStateRegistry::[test-2]::INPUT#0'
const VALUE = 'this was written in test 2';

let { element, localBefore } = await page.evaluate(() => ({
element: document.querySelector('persistent-state#test-2 input'),
localBefore: Object.entries(window.localStorage)
Expand All @@ -76,7 +83,7 @@ test(`data is saved to the sessionStorage when type="session"`, () => {
return loadTestPage.then(async ({ page }) => {
const KEY = 'PersistentStateRegistry::[test-3]::INPUT#0'
const VALUE = 'this was written in test 3';

let { element, sessionBefore } = await page.evaluate(() => ({
element: document.querySelector('persistent-state#test-3 input'),
sessionBefore: Object.entries(window.sessionStorage)
Expand All @@ -102,7 +109,7 @@ test(`data is saved under a computed key between the key="" attr and the input i
const TEST3_VALUE = 'this was written in test 3';
const KEY = 'PersistentStateRegistry::[test-key-attribute]::INPUT#the-input-tag-id'
const VALUE = 'this was written in test 4';

let { element, sessionBefore } = await page.evaluate(() => ({
element: document.querySelector('persistent-state#test-many input'),
sessionBefore: Object.entries(window.sessionStorage)
Expand All @@ -125,17 +132,17 @@ test(`data is saved under a computed key between the key="" attr and the input i
test(`data is loaded from sessionStorage by the WC when initialized`, () => {
return loadTestPage.then(async ({ page }) => {
const KEY = 'PersistentStateRegistry::[test-key-attribute]::INPUT#the-input-tag-id'

await page.evaluate(() => {
document.getElementById('root').innerHTML = '';
})

let { beforeInit } = await page.evaluate(() => ({
beforeInit: document.querySelector('persistent-state#test-many input')
}))

expect(beforeInit).toBe(null);

await page.evaluate(() => {
document.getElementById('root').innerHTML = `
<persistent-state id="test-many" key="test-key-attribute" type="session">
Expand All @@ -147,7 +154,31 @@ test(`data is loaded from sessionStorage by the WC when initialized`, () => {
let { afterInitValue } = await page.evaluate(() => ({
afterInitValue: document.querySelector('persistent-state#test-many input').value
}))

expect(afterInitValue).toBe('this was written in test 4');
})
});
});

// @kyle-west reviewed over my shoulder and said he will revisit this later.
xtest(`select tags are supported`, () => {
return getNewPage().then(async ({ browser, page }) => {
let { beforeInit, allPersistentStates } = await page.evaluate(() => ({
beforeInit: document.querySelector('persistent-state#test-select select'),
allPersistentStates: document.querySelectorAll('persistent-state')
}))

console.log('ALL', allPersistentStates[4]._elements);
expect(beforeInit.value).toBe("Banana");

await page.evaluate(() => {
document.querySelector('persistent-state#test-select select#select-foods [value="Bacon"]').selected = true;
});

const refreshedPage = await browser.newPage();
await refreshedPage.goto(`http://localhost:${port}/test/persistent-state_test.html`, {waitUntil : ['load', 'domcontentloaded']});
let { afterInit } = await refreshedPage.evaluate(() => ({
afterInit: document.querySelector('persistent-state#test-select select#select-foods')
}))
expect(afterInit.value).toBe("Bacon");
})
});
9 changes: 9 additions & 0 deletions test/persistent-state_test.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@
<persistent-state id="test-many" key="test-key-attribute" type="session">
<input type="text" id="the-input-tag-id"/>
</persistent-state>

<persistent-state id="test-select">
<select id="select-foods">
<option value="Banana">Banana</option>
<option value="Bacon">Bacon</option>
<option value="Taco">Taco</option>
<option value="Fried Rice">Fried Rice</option>
</select>
</persistent-state>
</div>
</body>
</html>

0 comments on commit 7503bd5

Please sign in to comment.