ソースを参照

allow multiple VMs sharing one data object

Evan You 12 年 前
コミット
75a4850afb
3 ファイル変更69 行追加38 行削除
  1. 23 9
      examples/nested-props.html
  2. 33 28
      src/compiler.js
  3. 13 1
      src/observer.js

+ 23 - 9
examples/nested-props.html

@@ -6,18 +6,26 @@
         <script src="../dist/seed.js"></script>
     </head>
     <body>
-        <h1>a.b.c : <span sd-text="a.b.c"></span></h1>
-        <h2>a.c : <span sd-text="a.c"></span></h2>
-        <h3>Computed property that concats the two: <span sd-text="d"></span></h3>
-        <button sd-on="click:one">one</button>
-        <button sd-on="click:two">two</button>
-        <button sd-on="click:three">three</button>
-        <p><input sd-value="msg"></p>
+        <div id="a">
+            <h1>a.b.c : <span sd-text="a.b.c"></span></h1>
+            <h2>a.c : <span sd-text="a.c"></span></h2>
+            <h3>Computed property that concats the two: <span sd-text="d"></span></h3>
+            <button sd-on="click:one">one</button>
+            <button sd-on="click:two">two</button>
+            <button sd-on="click:three">three</button>
+            <p><input sd-value="msg"></p>
+        </div>
+        <div id="b">
+            <h1 sd-text="a.c"></h1>
+        </div>
         <script>
             seed.config({debug: true})
+            var data = { c: 555 }
             var Demo = seed.ViewModel.extend({
                 init: function () {
-                    this.msg = 'Yoyoyo'                },
+                    this.msg = 'Yoyoyo'
+                    this.a = data
+                },
                 props: {
                     one: function () {
                         this.a = {
@@ -42,7 +50,13 @@
                     }}
                 }
             })
-            var app = new Demo({ el: document.body })
+            var app = new Demo({ el: '#a' }),
+                app2 = new seed.ViewModel({
+                    el: '#b',
+                    data: {
+                        a: data
+                    }
+                })
         </script>
     </body>
 </html>

+ 33 - 28
src/compiler.js

@@ -5,12 +5,10 @@ var Emitter         = require('emitter'),
     Binding         = require('./binding'),
     DirectiveParser = require('./directive-parser'),
     TextParser      = require('./text-parser'),
-    DepsParser      = require('./deps-parser')
-
-var slice           = Array.prototype.slice
-
-// late bindings
-var vmAttr, eachAttr
+    DepsParser      = require('./deps-parser'),
+    slice           = Array.prototype.slice,
+    vmAttr,
+    eachAttr
 
 /*
  *  The DOM compiler
@@ -59,17 +57,19 @@ function Compiler (vm, options) {
 
     utils.log('\nnew VM instance: ', el, '\n')
 
-    // set el
-    vm.$el = el
-    // link it up!
+    // set stuff on the ViewModel
+    vm.$el       = el
     vm.$compiler = this
     vm.$parent   = options.parentCompiler && options.parentCompiler.vm
 
     // now for the compiler itself...
-    this.vm              = vm
-    this.el              = vm.$el
-    this.directives      = []
+    this.vm         = vm
+    this.el         = el
+    this.directives = []
 
+    // Store things during parsing to be processed afterwards,
+    // because we want to have created all bindings before
+    // observing values / parsing dependencies.
     var observables = this.observables = []
     var computed = this.computed = [] // computed props to parse deps from
     var ctxBindings = this.contextBindings = [] // computed props with dynamic context
@@ -86,25 +86,28 @@ function Compiler (vm, options) {
     // setup observer
     this.setupObserver()
 
-    // call user init
+    // call user init. this will capture some initial values.
     if (options.init) {
         options.init.apply(vm, options.args || [])
     }
 
-    // now parse the DOM
+    // now parse the DOM, during which we will create necessary bindings
+    // and bind the parsed directives
     this.compileNode(this.el, true)
 
-    // for anything in viewmodel but not binded in DOM, create bindings for them
+    // for anything in viewmodel but not binded in DOM, also create bindings for them
     for (key in vm) {
         if (vm.hasOwnProperty(key) &&
             key.charAt(0) !== '$' &&
-            !this.bindings[key])
+            !this.bindings.hasOwnProperty(key))
         {
             this.createBinding(key)
         }
     }
 
-    // observe root keys
+    // observe root values so that they emit events when
+    // their nested values change (for an Object)
+    // or when they mutate (for an Array)
     var i = observables.length, binding
     while (i--) {
         binding = observables[i]
@@ -112,11 +115,10 @@ function Compiler (vm, options) {
     }
     // extract dependencies for computed properties
     if (computed.length) DepsParser.parse(computed)
-    this.computed = null
     // extract dependencies for computed properties with dynamic context
     if (ctxBindings.length) this.bindContexts(ctxBindings)
-    this.contextBindings = null
-    
+
+    this.observables = this.computed = this.contextBindings = null
 }
 
 var CompilerProto = Compiler.prototype
@@ -129,7 +131,8 @@ var CompilerProto = Compiler.prototype
 CompilerProto.setupObserver = function () {
 
     var bindings = this.bindings,
-        observer = this.observer = new Emitter()
+        observer = this.observer = new Emitter(),
+        depsOb   = DepsParser.observer
 
     // a hash to hold event proxies for each root level key
     // so they can be referenced and removed later
@@ -138,15 +141,15 @@ CompilerProto.setupObserver = function () {
     // add own listeners which trigger binding updates
     observer
         .on('get', function (key) {
-            if (DepsParser.observer.isObserving) {
-                DepsParser.observer.emit('get', bindings[key])
+            if (depsOb.isObserving && bindings[key]) {
+                depsOb.emit('get', bindings[key])
             }
         })
         .on('set', function (key, val) {
-            bindings[key].update(val)
+            if (bindings[key]) bindings[key].update(val)
         })
         .on('mutate', function (key) {
-            bindings[key].pub()
+            if (bindings[key]) bindings[key].pub()
         })
 }
 
@@ -406,11 +409,13 @@ CompilerProto.define = function (key, binding) {
                     binding.value.set(value)
                 }
             } else if (value !== binding.value) {
+                // unwatch the old value
+                Observer.unobserve(binding.value, key, compiler.observer)
+                // set new value
                 binding.value = value
                 compiler.observer.emit('set', key, value)
-                // unwatch the old value!
-                Observer.unobserve(binding.value, key, compiler.observer)
-                // now watch the new one instead
+                // now watch the new value, which in turn emits 'set'
+                // for all its nested values
                 Observer.observe(value, key, compiler.observer)
             }
         }

+ 13 - 1
src/observer.js

@@ -58,6 +58,9 @@ function bind (obj, key, path, observer) {
         values = obj.__values__,
         fullKey = (path ? path + '.' : '') + key
     values[fullKey] = val
+    // emit set on bind
+    // this means when an object is observed it will emit
+    // a first batch of set events.
     observer.emit('set', fullKey, val)
     def(obj, key, {
         enumerable: true,
@@ -89,6 +92,13 @@ function isWatchable (obj) {
     return type === 'Object' || type === 'Array'
 }
 
+function emitSet (obj, observer) {
+    var values = obj.__values__
+    for (var key in values) {
+        observer.emit('set', key, values[key])
+    }
+}
+
 module.exports = {
 
     observe: function (obj, path, observer) {
@@ -114,7 +124,9 @@ module.exports = {
                 .on('get', proxies.get)
                 .on('set', proxies.set)
                 .on('mutate', proxies.mutate)
-            if (!alreadyConverted) {
+            if (alreadyConverted) {
+                emitSet(obj, obj.__observer__)
+            } else {
                 watch(obj, null, ob)
             }
         }