Browse Source

Persist to custom storage (#2006)

* Add failing test

* Implement support for custom storages

* Update docs

* Update persist.md

* Change .to -> .using

Co-authored-by: Caleb Porzio <calebporzio@gmail.com>
Simone Todaro 3 years ago
parent
commit
b2de778708

+ 45 - 0
packages/docs/src/en/plugins/persist.md

@@ -137,6 +137,51 @@ Here's a view of Chrome Devtools to see for yourself:
 
 <img src="/img/persist_custom_key_devtools.png" alt="Chrome devtools showing the localStorage view with count set to 0">
 
+<a name="custom-storage"></a>
+## Using a custom storage
+
+By default, data is saved to localStorage, it does not have an expiration time and it's kept even when the page is closed.
+
+Consider the scenario where you want to clear the data once the user close the tab. In this case you can persist data to sessionStorage using the `.using` modifier like so:
+
+
+```alpine
+<div x-data="{ count: $persist(0).using(sessionStorage) }">
+    <button x-on:click="count++">Increment</button>
+
+    <span x-text="count"></span>
+</div>
+```
+
+You can also define your custom storage object exposing a getItem function and a setItem function. For example, you can decide to use a session cookie as storage doing so:
+
+
+```alpine
+<script>
+    window.cookieStorage = {
+        getItem(key) {
+            let cookies = document.cookie.split(";");
+            for (let i = 0; i < cookies.length; i++) {
+                let cookie = cookies[i].split("=");
+                if (key == cookie[0].trim()) {
+                    return decodeURIComponent(cookie[1]);
+                }
+            }
+            return null;
+        },
+        setItem(key, value) {
+            document.cookie = key+' = '+encodeURIComponent(value)
+        }
+    }
+</script>
+
+<div x-data="{ count: $persist(0).using(cookieStorage) }">
+    <button x-on:click="count++">Increment</button>
+
+    <span x-text="count"></span>
+</div>
+```
+
 <a name="using-persist-with-alpine-data"></a>
 ## Using $persist with Alpine.data
 

+ 12 - 10
packages/persist/src/index.js

@@ -2,12 +2,13 @@
 export default function (Alpine) {
     Alpine.magic('persist', (el, { interceptor }) => {
         let alias
+        let storage = localStorage
 
         return interceptor((initialValue, getter, setter, path, key) => {
             let lookup = alias || `_x_${path}`
 
-            let initial = storageHas(lookup)
-                ? storageGet(lookup)
+            let initial = storageHas(lookup, storage)
+                ? storageGet(lookup, storage)
                 : initialValue
 
             setter(initial)
@@ -15,26 +16,27 @@ export default function (Alpine) {
             Alpine.effect(() => {
                 let value = getter()
 
-                storageSet(lookup, value)
+                storageSet(lookup, value, storage)
 
                 setter(value)
             })
 
             return initial
         }, func => {
-            func.as = key => { alias = key; return func }
+            func.as = key => { alias = key; return func },
+            func.using = target => { storage = target; return func }
         })
     })
 }
 
-function storageHas(key) {
-    return localStorage.getItem(key) !== null
+function storageHas(key, storage) {
+    return storage.getItem(key) !== null
 }
 
-function storageGet(key) {
-    return JSON.parse(localStorage.getItem(key))
+function storageGet(key, storage) {
+    return JSON.parse(storage.getItem(key, storage))
 }
 
-function storageSet(key, value) {
-    localStorage.setItem(key, JSON.stringify(value))
+function storageSet(key, value, storage) {
+    storage.setItem(key, JSON.stringify(value))
 }

+ 36 - 2
tests/cypress/integration/plugins/persist.spec.js

@@ -1,4 +1,4 @@
-import { beVisible, haveText, html, notBeVisible, test } from '../../utils'
+import { beEqualTo, beVisible, haveText, html, notBeVisible, test } from '../../utils'
 
 test('can persist number',
     [html`
@@ -139,7 +139,7 @@ test('can persist using an alias',
     },
 )
 
-test('aliases do not affect other persist in the same page',
+test('aliases do not affect other $persist calls',
     [html`
         <div x-data="{ show: $persist(false).as('foo') }">
             <button id="test" @click="show = true"></button>
@@ -165,3 +165,37 @@ test('aliases do not affect other persist in the same page',
         get('span#two').should(beVisible())
     },
 )
+
+test('can persist to custom storage',
+    [html`
+        <div x-data="{ message: $persist('foo').using(sessionStorage) }">
+            <input x-model="message">
+
+            <span x-text="message"></span>
+        </div>
+    `],
+    ({ get, window }, reload) => {
+        get('span').should(haveText('foo'))
+        get('input').clear().type('bar')
+        get('span').should(haveText('bar'))
+        reload()
+        get('span').should(haveText('bar'))
+        window().its('sessionStorage._x_message').should(beEqualTo(JSON.stringify('bar')))
+    },
+)
+
+test('can persist to custom storage using an alias',
+    [html`
+        <div x-data="{ message: $persist('foo').as('mymessage').using(sessionStorage) }">
+            <input x-model="message">
+
+            <span x-text="message"></span>
+        </div>
+    `],
+    ({ get, window }, reload) => {
+        get('span').should(haveText('foo'))
+        get('input').clear().type('bar')
+        get('span').should(haveText('bar'))
+        window().its('sessionStorage.mymessage').should(beEqualTo(JSON.stringify('bar')))
+    },
+)

+ 2 - 0
tests/cypress/utils.js

@@ -101,6 +101,8 @@ export let haveValue = value => el => expect(el).to.have.value(value)
 
 export let haveLength = length => el => expect(el).to.have.length(length)
 
+export let beEqualTo = value => el => expect(el).to.eq(value)
+
 export function root(el) {
     if (el._x_dataStack) return el