瀏覽代碼

Merge pull request #23 from ryangjchandler/feature/core-events

Add support for "watching" state updates
Ryan Chandler 5 年之前
父節點
當前提交
eaafc11d32
共有 11 個文件被更改,包括 86 次插入2 次删除
  1. 19 0
      README.md
  2. 0 0
      dist/spruce.js
  3. 0 0
      dist/spruce.js.map
  4. 0 0
      dist/spruce.module.js
  5. 0 0
      dist/spruce.module.js.map
  6. 0 0
      dist/spruce.umd.js
  7. 0 0
      dist/spruce.umd.js.map
  8. 34 0
      src/bus.js
  9. 7 1
      src/index.js
  10. 3 1
      src/observable.js
  11. 23 0
      tests/bus.spec.js

+ 19 - 0
README.md

@@ -123,6 +123,25 @@ Spruce.stores.application.name = 'Amazing Spruce Integration'
 
 This will trigger Alpine to re-evaluate your subscribed components and re-render.
 
+### Externally watching for changes
+
+You can register watchers in a similar fashion to Alpine. All you need is the full dot-notation representation of your piece of state and a callback.
+
+```html
+<script>
+    Spruce.store('form', {
+        name: 'Ryan',
+        email: 'support@ryangjchandler.co.uk'
+    })
+
+    Spruce.watch('form.email', (old, next) => {
+        // do something with the values here
+    })
+<script>
+```
+
+In the above snippet, when we change the value of `form.email` either from a component or externally in a separate JavaScript file, our callback will be invoked and will receive the old value, as well as the new value. This can be useful for running automatic inline validation when a property changes, or triggering an action elsewhere in another component without the need for dispatching events.
+
 ### Removing the need for `x-subscribe`
 
 Alpine offers a Config API. Using this API, you can enable an experimental global `$store` variable that is declared on the `window` object. This means your components do not need to manually "subscribe" to state changes:

文件差異過大導致無法顯示
+ 0 - 0
dist/spruce.js


文件差異過大導致無法顯示
+ 0 - 0
dist/spruce.js.map


文件差異過大導致無法顯示
+ 0 - 0
dist/spruce.module.js


文件差異過大導致無法顯示
+ 0 - 0
dist/spruce.module.js.map


文件差異過大導致無法顯示
+ 0 - 0
dist/spruce.umd.js


文件差異過大導致無法顯示
+ 0 - 0
dist/spruce.umd.js.map


+ 34 - 0
src/bus.js

@@ -1,4 +1,6 @@
 export default {
+    watchers: {},
+
     events: {},
 
     on(name, callback) {
@@ -22,5 +24,37 @@ export default {
             detail: data,
             bubbles: true
         }))
+    },
+
+    watch(dotNotation, callback) {
+        if (! this.watchers[dotNotation]) {
+            this.watchers[dotNotation] = []
+        }
+
+        this.watchers[dotNotation].push(callback)
+    },
+
+    runWatchers(stores, target, key, oldValue) {
+        const self = this
+
+        if (self.watchers[key]) {
+            return self.watchers[key].forEach(callback => callback(oldValue, target[key]))
+        }
+
+        Object.keys(self.watchers)
+            .filter(watcher => watcher.includes('.'))
+            .forEach(fullDotNotationKey => {
+                let dotNotationParts = fullDotNotationKey.split('.')
+
+                if (key !== dotNotationParts[dotNotationParts.length - 1]) return
+
+                dotNotationParts.reduce((comparison, part) => {
+                    if (comparison[key] === target[key] || Object.is(target, comparison)) {
+                        self.watchers[fullDotNotationKey].forEach(callback => callback(oldValue, target[key]))
+                    }
+
+                    return comparison[part]
+                }, stores)
+            })
     }
 }

+ 7 - 1
src/index.js

@@ -22,7 +22,9 @@ const Spruce = {
         })
 
         this.stores = createObservable(this.stores, {
-            set: (key, value) => {
+            set: (target, key, value, oldValue) => {
+                this.events.runWatchers(this.stores, target, key, oldValue)
+
                 this.updateSubscribers(key, value)
             }
         })
@@ -70,6 +72,10 @@ const Spruce = {
 
     emit(name, data = {}) {
         this.events.emit(name, { ...data, store: this.stores })
+    },
+
+    watch(dotNotation, callback) {
+        this.events.watch(dotNotation, callback)
     }
 }
 

+ 3 - 1
src/observable.js

@@ -16,11 +16,13 @@ export const createObservable = (target, callbacks) => {
             return target[key]
         },
         set(target, key, value) {
+            const old = target[key]
+
             if (! isNullOrUndefined(value) && typeof value === 'object') {
                 value = createObservable(value, callbacks)
             }
 
-            callbacks.set(key, target[key] = value)
+            callbacks.set(target, key, target[key] = value, old)
 
             return true
         }

+ 23 - 0
tests/bus.spec.js

@@ -64,4 +64,27 @@ test('.emit() > will dispatch browser event to window with spruce: prefix', asyn
     await waitFor(() => {
         expect(document.querySelector('span').innerText).toEqual('car')
     })
+})
+
+test('.watch() > can listen for changes to property', async () => {
+    let fixture = undefined
+    let oldFixture = undefined
+    
+    Spruce.store('example', {
+        cool: 'stuff'
+    })
+
+    Spruce.watch('example.cool', (previous, value) => {
+        oldFixture = previous
+        fixture = value
+    })
+
+    await Spruce.start()
+
+    expect(fixture).toBeUndefined()
+
+    Spruce.stores.example.cool = 'amazing'
+
+    expect(fixture).toEqual('amazing')
+    expect(oldFixture).toEqual('stuff')
 })

部分文件因文件數量過多而無法顯示