Преглед на файлове

Finalize and document $persist

Caleb Porzio преди 3 години
родител
ревизия
6e1b46a8a4
променени са 3 файла, в които са добавени 191 реда и са изтрити 4 реда
  1. 134 0
      packages/docs/src/en/plugins/persist.md
  2. 22 3
      packages/persist/src/index.js
  3. 35 1
      tests/cypress/integration/plugins/persist.spec.js

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

@@ -0,0 +1,134 @@
+---
+order: 2
+title: Persist
+description:
+graph_image: https://alpinejs.dev/social_persist.jpg
+---
+
+# Persist Plugin
+
+Alpine's Persist plugin allows you to persist Alpine state across page loads.
+
+This is useful for persisting search filters, active tabs, and other features where users will be frustrated if their configuration is reset after refreshing or leaving and revisiting a page.
+
+<a name="installation"></a>
+## Installation
+
+You can use this plugin by either including it from a `<script>` tag or installing it via NPM:
+
+### Via CDN
+
+You can include the CDN build of this plugin as a `<script>` tag, just make sure to include it BEFORE Alpine's core JS file.
+
+```html
+<!-- Alpine Plugins -->
+<script defer src="https://unpkg.com/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script>
+
+<!-- Alpine Core -->
+<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
+```
+
+### Via NPM
+
+You can install Persist from NPM for use inside your bundle like so:
+
+```bash
+npm install @alpinejs/persist
+```
+
+Then initialize it from your bundle:
+
+```js
+import Alpine from 'alpinejs'
+import persist from '@alpinejs/persist'
+
+Alpine.plugin(persist)
+
+...
+```
+
+<a name="magic-persist"></a>
+## $persist
+
+The primary API for using this plugin is the magic `$persist` method.
+
+You can wrap any value inside `x-data` with `$persist` like below to persist its value across page loads:
+
+```html
+<div x-data="{ count: $persist(0) }">
+    <button x-on:click="count++">Increment</button>
+
+    <span x-text="count"></span>
+</div>
+```
+
+<!-- START_VERBATIM -->
+<div class="demo">
+    <div x-data="{ count: $persist('0').as('yoyo') }">
+        <button x-on:click="count++">Increment</button>
+        <span x-text="count"></span>
+    </div>
+</div>
+<!-- END_VERBATIM -->
+
+In the above example, because we wrapped `0` in `$persist()`, Alpine will now intercept changes made to `count` and persist them across page loads.
+
+You can try this for yourself by incrementing the "count" in the above example, then refreshing this page and observing that the "count" maintains its state and isn't reset to "0".
+
+<a name="how-it-works"></a>
+## How does it work?
+
+If a value is wrapped in `$persist`, on initialization Alpine will register it's own watcher for that value. Now everytime that value changes for any reason, Alpine will store the new value in [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
+
+Now when a page is reloaded, Alpine will check localStorage (using the name of the property as the key) for a value. If it finds one, it will set the property value from localStorage immediately.
+
+You can observe this behavior by opening your browser devtool's localStorage viewer:
+
+<a href="https://developer.chrome.com/docs/devtools/storage/localstorage/"><img src="/img/persist_devtools.png" alt="Chrome devtools showing the localStorage view with count set to 0"></a>
+
+You'll observe that by simply visiting this page, Alpine already set the value of "count" in localStorage. You'll also notice it prefixes the property name "count" with "_x_" as a way of namespacing these values so Alpine doesn't conflict with other tools using localStorage.
+
+Now change the "count" in the following example and observe the changes made by Alpine to localStorage:
+
+```html
+<div x-data="{ count: $persist(0) }">
+    <button x-on:click="count++">Increment</button>
+
+    <span x-text="count"></span>
+</div>
+```
+
+<!-- START_VERBATIM -->
+<div class="demo">
+    <div x-data="{ count: $persist(0) }">
+        <button x-on:click="count++">Increment</button>
+        <span x-text="count"></span>
+    </div>
+</div>
+<!-- END_VERBATIM -->
+
+<a name="custom-key"></a>
+## Setting a custom key
+
+By default, Alpine uses the property key that `$persist(...)` is being assigned to ("count" in the above examples).
+
+Consider the scenario where you have multiple Alpine components across pages or even on the same page that all use "count" as the property key.
+
+Alpine will have no way of differentiating between these components.
+
+In these cases, you can set your own custom key for any persisted value using the `.as` modifier like so:
+
+
+```html
+<div x-data="{ count: $persist(0).as('other-count') }">
+    <button x-on:click="count++">Increment</button>
+
+    <span x-text="count"></span>
+</div>
+```
+
+Now Alpine will store and retrieve the above "count" value using the key "other-count".
+
+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">

+ 22 - 3
packages/persist/src/index.js

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

+ 35 - 1
tests/cypress/integration/plugins/persist.spec.js

@@ -1,6 +1,6 @@
 import { haveText, html, test } from '../../utils'
 
-test('can perist data',
+test('can perist number',
     [html`
         <div x-data="{ count: $persist(1) }">
             <button @click="count++">Inc</button>
@@ -15,3 +15,37 @@ test('can perist data',
         get('span').should(haveText('2'))
     },
 )
+
+test('can perist string',
+    [html`
+        <div x-data="{ message: $persist('foo') }">
+            <input x-model="message">
+
+            <span x-text="message"></span>
+        </div>
+    `],
+    ({ get }, reload) => {
+        get('span').should(haveText('foo'))
+        get('input').clear().type('bar')
+        get('span').should(haveText('bar'))
+        reload()
+        get('span').should(haveText('bar'))
+    },
+)
+
+test('can perist array',
+    [html`
+        <div x-data="{ things: $persist(['foo', 'bar']) }">
+            <button @click="things.push('baz')"></button>
+
+            <span x-text="things.join('-')"></span>
+        </div>
+    `],
+    ({ get }, reload) => {
+        get('span').should(haveText('foo-bar'))
+        get('button').click()
+        get('span').should(haveText('foo-bar-baz'))
+        reload()
+        get('span').should(haveText('foo-bar-baz'))
+    },
+)