Преглед изворни кода

feature: add support for watching objects

Ryan Chandler пре 4 година
родитељ
комит
fa8e9f52e4
9 измењених фајлова са 86 додато и 31 уклоњено
  1. 0 0
      dist/spruce.js
  2. 0 0
      dist/spruce.js.map
  3. 0 0
      dist/spruce.module.js
  4. 0 0
      dist/spruce.module.js.map
  5. 0 0
      dist/spruce.umd.js
  6. 0 0
      dist/spruce.umd.js.map
  7. 12 0
      examples/index.html
  8. 71 24
      src/index.js
  9. 3 7
      src/observable.js

Разлика између датотеке није приказан због своје велике величине
+ 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


+ 12 - 0
examples/index.html

@@ -39,6 +39,10 @@
             </ul>
             </ul>
         </div>
         </div>
 
 
+        <div x-data>
+            <input type="text" x-model="$store.user.name">
+        </div>
+
         <script src="../dist/spruce.umd.js"></script>
         <script src="../dist/spruce.umd.js"></script>
 
 
         <script>
         <script>
@@ -66,6 +70,14 @@
                 }
                 }
             })
             })
 
 
+            Spruce.store('user', {
+                name: 'Ryan'
+            })
+
+            Spruce.watch('user', () => {
+                console.log('User changed.')
+            })
+
             Spruce.watch('application.name', () => {
             Spruce.watch('application.name', () => {
                 Spruce.store('persisted').example = 'World';
                 Spruce.store('persisted').example = 'World';
             });
             });

+ 71 - 24
src/index.js

@@ -1,4 +1,4 @@
-import { getMethods, checkForAlpine } from './utils'
+import { getMethods, checkForAlpine, isObject, isArray } from './utils'
 import { createObservable } from './observable'
 import { createObservable } from './observable'
 
 
 const Spruce = {
 const Spruce = {
@@ -18,20 +18,22 @@ const Spruce = {
 
 
     startedCallbacks: [],
     startedCallbacks: [],
 
 
+    hasStarted: false,
+
     start() {
     start() {
         this.startingCallbacks.forEach(fn => fn())
         this.startingCallbacks.forEach(fn => fn())
 
 
         this.attach()
         this.attach()
 
 
         this.stores = createObservable(this.stores, {
         this.stores = createObservable(this.stores, {
-            set: (target, key, value) => {
+            set: (target, key, value, receiver) => {
                 if (this.disableReactivity) {
                 if (this.disableReactivity) {
                     return
                     return
                 }
                 }
 
 
                 this.updateSubscribers()
                 this.updateSubscribers()
 
 
-                this.runWatchers(this.stores, target, key, value)
+                this.runWatchers(target, key, value, receiver)
 
 
                 this.disableReactivity = true
                 this.disableReactivity = true
 
 
@@ -45,6 +47,16 @@ const Spruce = {
             }
             }
         })
         })
 
 
+        this.hasStarted = true
+
+        this.disableReactivity = true
+
+        Object.entries(this.watchers).forEach(([name, callbacks]) => {
+            callbacks.forEach(callback => this.watch(name, callback))
+        })
+
+        this.disableReactivity = false
+
         this.startedCallbacks.forEach(fn => fn())
         this.startedCallbacks.forEach(fn => fn())
     },
     },
 
 
@@ -123,47 +135,82 @@ const Spruce = {
 
 
         if (typeof storage === 'object') {
         if (typeof storage === 'object') {
             storage = Object.assign(methods, storage)
             storage = Object.assign(methods, storage)
+
+            delete storage.__watchers
+            delete storage.__key_name
         }
         }
 
 
         return storage
         return storage
     },
     },
 
 
     updateLocalStorage(name) {
     updateLocalStorage(name) {
+        const store = this.store(name)
+
+        delete store.__watchers
+        delete store.__key_name
+
         this.persistenceDriver.setItem(`__spruce:${name}`, JSON.stringify(this.store(name)))
         this.persistenceDriver.setItem(`__spruce:${name}`, JSON.stringify(this.store(name)))
     },
     },
 
 
+    get(name) {
+        return name.split('.').reduce((target, part) => target[part], this.stores)
+    },
+
     watch(name, callback) {
     watch(name, callback) {
-        if (!this.watchers[name]) {
-            this.watchers[name] = []
+        if (! this.hasStarted) {
+            this.watchers[name] || (this.watchers[name] = [])
+
+            this.watchers[name].push(callback)
+
+            return
         }
         }
 
 
-        this.watchers[name].push(callback)
-    },
+        const nameParts = name.split('.')
 
 
-    runWatchers(stores, target, key, value) {
-        key = target['__key_name'] || key
+        const target = nameParts.reduce((target, part) => {
+            const sub = target[part]
 
 
-        const self = this
+            if (isObject(sub) || isArray(sub)) {
+                return sub
+            }
+
+            return target
+        }, this.stores)
+
+        /**
+         * If the target object / array is the property
+         * that needs to be watched, a magic `__self` key is
+         * used so that runner can pick up on it later.
+         */
+        const part = Object.is(target, this.get(name)) ? '__self' : nameParts[nameParts.length - 1]
 
 
-        if (self.watchers[key]) {
-            return self.watchers[key].forEach(callback => callback(value))
+        if (! target.__watchers) {
+            target.__watchers = new Map
+        }
+        
+        if (! target.__watchers.has(part)) {
+            target.__watchers.set(part, new Set)
         }
         }
 
 
-        Object.keys(self.watchers)
-            .filter(watcher => watcher.includes('.'))
-            .forEach(fullDotNotationKey => {
-                let dotNotationParts = fullDotNotationKey.split('.')
+        target.__watchers.get(part).add(callback)
+    },
 
 
-                if (key !== dotNotationParts[dotNotationParts.length - 1]) return
+    runWatchers(target, key, value) {
+        if (! target.__watchers) {
+            return
+        }
 
 
-                dotNotationParts.reduce((comparison, part) => {
-                    if (comparison[key] === target[key] || Object.is(target, comparison)) {
-                        self.watchers[fullDotNotationKey].forEach(callback => callback(value))
-                    }
+        if (target.__watchers.has(key)) {
+            target.__watchers.get(key).forEach(f => f(value))
+        }
 
 
-                    return comparison[part]
-                }, stores)
-            })
+        /**
+         * The `__self` key is used for watchers that are registered
+         * to the object or array being updated.
+         */
+        if (target.__watchers.has('__self')) {
+            target.__watchers.get('__self').forEach(f => f(value, key))
+        }
     },
     },
 
 
     persistUsing(driver) {
     persistUsing(driver) {

+ 3 - 7
src/observable.js

@@ -2,22 +2,18 @@ import { isNullOrUndefined, isObject, isArray } from './utils'
 
 
 export const createObservable = (target, callbacks) => {
 export const createObservable = (target, callbacks) => {
     Object.entries(target).forEach(([key, value]) => {
     Object.entries(target).forEach(([key, value]) => {
-        if (! isNullOrUndefined(value) && (isObject(value) || isArray(value))) {
-            if (isArray(value)) {
-                value['__key_name'] = key
-            }
-            
+        if (! isNullOrUndefined(value) && (isObject(value) || isArray(value))) {            
             target[key] = createObservable(value, callbacks)
             target[key] = createObservable(value, callbacks)
         }
         }
     })
     })
 
 
     return new Proxy(target, {
     return new Proxy(target, {
-        set(target, key, value) {
+        set(target, key, value, receiver) {
             if (! isNullOrUndefined(value) && isObject(value)) {
             if (! isNullOrUndefined(value) && isObject(value)) {
                 value = createObservable(value, callbacks)
                 value = createObservable(value, callbacks)
             }
             }
 
 
-            callbacks.set(target, key, target[key] = value)
+            callbacks.set(target, key, target[key] = value, receiver)
 
 
             return true
             return true
         }
         }

Неке датотеке нису приказане због велике количине промена