Browse Source

trying to fix nested props

Evan You 12 years ago
parent
commit
6ec70eb4a4
8 changed files with 123 additions and 133 deletions
  1. 1 0
      TODO.md
  2. 3 18
      examples/nested-props.html
  3. 84 84
      src/compiler.js
  4. 27 21
      src/deps-parser.js
  5. 2 1
      src/directives/each.js
  6. 5 1
      src/directives/on.js
  7. 1 0
      src/observer.js
  8. 0 8
      src/viewmodel.js

+ 1 - 0
TODO.md

@@ -1,4 +1,5 @@
 - fix architecture: objects as single source of truth...
+- auto add .length binding for Arrays
 - $watch / $unwatch & $destroy(now much easier)
 - add a few util methods, e.g. extend, inherits
 - prototypal scope/binding inheritance (Object.create)

+ 3 - 18
examples/nested-props.html

@@ -15,24 +15,9 @@
         <p><input sd-value="msg"></p>
         <script>
             seed.config({debug: true})
-            var data = {
-                    c: 0,
-                    b: {
-                        c: 'zero'
-                    }
-                }
             var Demo = seed.ViewModel.extend({
                 init: function () {
-                    this.msg = 'Yoyoyo'
-
-                    // example of async compilation
-                    var vm = this
-                    vm.$wait()
-                    setTimeout(function () {
-                        vm.a = data
-                        vm.$ready()
-                    }, 30)
-                },
+                    this.msg = 'Yoyoyo'                },
                 props: {
                     one: function () {
                         this.a = {
@@ -52,8 +37,8 @@
                         this.a.b.c = 'three'
                         this.a.c = 3
                     },
-                    d: {get: function (ctx) {
-                        return (ctx.vm.msg + this.a.b.c + this.a.c) || ''
+                    d: {get: function () {
+                        return this.msg + (this.a.b.c || '') + (this.a.c || '')
                     }}
                 }
             })

+ 84 - 84
src/compiler.js

@@ -24,8 +24,16 @@ function Compiler (vm, options) {
 
     // copy options
     options = options || {}
-    for (var op in options) {
-        this[op] = options[op]
+    for (var key in options) {
+        this[key] = options[key]
+    }
+
+    // copy data if any
+    var data = options.data
+    if (data) {
+        for (key in data) {
+            vm[key] = data[key]
+        }
     }
 
     // determine el
@@ -61,8 +69,10 @@ function Compiler (vm, options) {
     this.vm              = vm
     this.el              = vm.$el
     this.directives      = []
-    this.computed        = [] // computed props to parse deps from
-    this.contextBindings = [] // computed props with dynamic context
+
+    var observables = this.observables = []
+    var computed = this.computed = [] // computed props to parse deps from
+    var ctxBindings = this.contextBindings = [] // computed props with dynamic context
 
     // prototypal inheritance of bindings
     var parent = this.parentCompiler
@@ -76,32 +86,36 @@ function Compiler (vm, options) {
     // setup observer
     this.setupObserver()
 
-    // copy data if any
-    var key, data = options.data
-    if (data) {
-        if (data instanceof vm.constructor) {
-            data = utils.dump(data)
-        }
-        for (key in data) {
-            vm[key] = data[key]
-        }
-    }
-
     // call user init
     if (options.init) {
         options.init.apply(vm, options.args || [])
     }
 
-    // check for async compilation (vm.$wait())
-    if (vm.__wait__) {
-        var self = this
-        this.observer.once('ready', function () {
-            vm.__wait__ = null
-            self.compile()
-        })
-    } else {
-        this.compile()
+    // now parse the DOM
+    this.compileNode(this.el, true)
+
+    // for anything in viewmodel but not binded in DOM, create bindings for them
+    for (key in vm) {
+        if (vm.hasOwnProperty(key) &&
+            key.charAt(0) !== '$' &&
+            !this.bindings[key])
+        {
+            this.createBinding(key)
+        }
     }
+
+    // define root keys
+    var i = observables.length, binding
+    while (i--) {
+        binding = observables[i]
+        Observer.observe(binding.value, binding.key, this.observer)
+    }
+    // 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
     
 }
 
@@ -114,8 +128,7 @@ var CompilerProto = Compiler.prototype
  */
 CompilerProto.setupObserver = function () {
 
-    var compiler = this,
-        bindings = this.bindings,
+    var bindings = this.bindings,
         observer = this.observer = new Emitter()
 
     // a hash to hold event proxies for each root level key
@@ -130,51 +143,13 @@ CompilerProto.setupObserver = function () {
             }
         })
         .on('set', function (key, val) {
-            if (key.match(/todo\./)) {
-                console.log(key, val)
-            }
-            if (!bindings[key]) compiler.createBinding(key)
             bindings[key].update(val)
         })
         .on('mutate', function (key) {
-            bindings[key].refresh()
+            bindings[key].pub()
         })
 }
 
-/*
- *  Actually parse the DOM nodes for directives, create bindings,
- *  and parse dependencies afterwards. For the dependency extraction to work,
- *  this has to happen after all user-set values are present in the VM.
- */
-CompilerProto.compile = function () {
-
-    var key,
-        vm = this.vm,
-        computed = this.computed,
-        contextBindings = this.contextBindings
-
-    // parse the DOM
-    this.compileNode(this.el, true)
-
-    // for anything in viewmodel but not binded in DOM, create bindings for them
-    for (key in vm) {
-        if (vm.hasOwnProperty(key) &&
-            key.charAt(0) !== '$' &&
-            !this.bindings[key])
-        {
-            this.createBinding(key)
-        }
-    }
-
-    // extract dependencies for computed properties
-    if (computed.length) DepsParser.parse(computed)
-    this.computed = null
-    
-    // extract dependencies for computed properties with dynamic context
-    if (contextBindings.length) this.bindContexts(contextBindings)
-    this.contextBindings = null
-}
-
 /*
  *  Compile a DOM node (recursive)
  */
@@ -335,26 +310,41 @@ CompilerProto.createBinding = function (key) {
     
     utils.log('  created binding: ' + key)
 
+    // make sure the key exists in the object so it can be observed
+    // by the Observer!
+    this.ensurePath(key)
+
     var bindings = this.bindings,
         binding = new Binding(this, key)
     bindings[key] = binding
 
-    var baseKey = key.split('.')[0]
     if (binding.root) {
         // this is a root level binding. we need to define getter/setters for it.
-        this.define(baseKey, binding)
+        this.define(key, binding)
     } else {
-        // TODO create placeholder objects
-        if (!bindings[baseKey]) {
-            // this is a nested value binding, but the binding for its root
+        var parentKey = key.slice(0, key.lastIndexOf('.'))
+        if (!bindings.hasOwnProperty(parentKey)) {
+            // this is a nested value binding, but the binding for its parent
             // has not been created yet. We better create that one too.
-            this.createBinding(baseKey)
+            this.createBinding(parentKey)
         }
     }
-
     return binding
 }
 
+CompilerProto.ensurePath = function (key) {
+    var path = key.split('.'), sec,
+        i = 0, depth = path.length - 1,
+        obj = this.vm
+    while (i < depth) {
+        sec = path[i]
+        if (!obj[sec]) obj[sec] = {}
+        obj = obj[sec]
+        i++
+    }
+    obj[path[i]] = obj[path[i]] || undefined
+}
+
 /*
  *  Defines the getter/setter for a top-level binding on the VM
  *  and observe the initial value
@@ -365,22 +355,32 @@ CompilerProto.define = function (key, binding) {
 
     var compiler = this,
         vm = this.vm,
-        value = binding.value = vm[key] // save the value before redefinening it
-
-    if (utils.typeOf(value) === 'Object' && value.get) {
-        binding.isComputed = true
-        binding.rawGet = value.get
-        value.get = value.get.bind(vm)
-        this.computed.push(binding)
-    } else {
-        Observer.observe(value, key, compiler.observer) // start observing right now
+        value = binding.value = vm[key], // save the value before redefinening it
+        type = utils.typeOf(value)
+
+    if (type === 'Object') {
+        if (value.get) {// computed property
+            binding.isComputed = true
+            binding.rawGet = value.get
+            value.get = value.get.bind(vm)
+            this.computed.push(binding)
+        } else {
+            // observe objects later, becase there might be more keys
+            // to be added to it
+            this.observables.push(binding)
+        }
+    } else if (type === 'Array') {
+        // observe arrays right now, because they will be needed in
+        // sd-each directives.
+        Observer.observe(value, key, compiler.observer)
     }
 
     Object.defineProperty(vm, key, {
         enumerable: true,
         get: function () {
-            if (!binding.isComputed && !binding.value.__observer__) {
-                // only emit non-computed, non-observed values
+            var value = binding.value
+            if ((!binding.isComputed && (value === undefined || !value.__observer__)) || Array.isArray(value)) {
+                // only emit non-computed, non-observed (tip) values, or Arrays.
                 // because these are the cleanest dependencies
                 compiler.observer.emit('get', key)
             }
@@ -389,7 +389,7 @@ CompilerProto.define = function (key, binding) {
                     el: compiler.el,
                     vm: compiler.vm,
                     item: compiler.each
-                        ? compiler.vm[compiler.eachPrefix.slice(0, -1)]
+                        ? compiler.vm[compiler.eachPrefix]
                         : null
                 })
                 : binding.value

+ 27 - 21
src/deps-parser.js

@@ -1,5 +1,5 @@
 var Emitter  = require('emitter'),
-    config   = require('./config'),
+    //config   = require('./config'),
     utils    = require('./utils'),
     observer = new Emitter()
 
@@ -17,8 +17,11 @@ var dummyEl = document.createElement('div'),
  *  second pass in injectDeps()
  */
 function catchDeps (binding) {
+    utils.log('\n─ ' + binding.key)
     observer.on('get', function (dep) {
+        utils.log('  └─ ' + dep.key)
         binding.deps.push(dep)
+        dep.subs.push(binding)
     })
     parseContextDependency(binding)
     binding.value.get({
@@ -28,29 +31,32 @@ function catchDeps (binding) {
     observer.off('get')
 }
 
+// Second pass seems no longer necessary because now we have control
+// over what values to emit (only non-computed values)
+
 /*
  *  The second pass of dependency extraction.
  *  Only include dependencies that don't have dependencies themselves.
  */
-function filterDeps (binding) {
-    var i = binding.deps.length, dep
-    utils.log('\n─ ' + binding.key)
-    while (i--) {
-        dep = binding.deps[i]
-        if (!dep.deps.length) {
-            utils.log('  └─ ' + dep.key)
-            dep.subs.push(binding)
-        } else {
-            binding.deps.splice(i, 1)
-        }
-    }
-    var ctxDeps = binding.contextDeps
-    if (!ctxDeps || !config.debug) return
-    i = ctxDeps.length
-    while (i--) {
-        utils.log('  └─ ctx:' + ctxDeps[i])
-    }
-}
+// function filterDeps (binding) {
+//     var i = binding.deps.length, dep
+//     utils.log('\n─ ' + binding.key)
+//     while (i--) {
+//         dep = binding.deps[i]
+//         if (!dep.deps.length) {
+//             utils.log('  └─ ' + dep.key)
+//             dep.subs.push(binding)
+//         } else {
+//             binding.deps.splice(i, 1)
+//         }
+//     }
+//     var ctxDeps = binding.contextDeps
+//     if (!ctxDeps || !config.debug) return
+//     i = ctxDeps.length
+//     while (i--) {
+//         utils.log('  └─ ctx:' + ctxDeps[i])
+//     }
+// }
 
 /*
  *  We need to invoke each binding's getter for dependency parsing,
@@ -120,7 +126,7 @@ module.exports = {
         utils.log('\nparsing dependencies...')
         observer.isObserving = true
         bindings.forEach(catchDeps)
-        bindings.forEach(filterDeps)
+        //bindings.forEach(filterDeps)
         observer.isObserving = false
         utils.log('\ndone.')
     }

+ 2 - 1
src/directives/each.js

@@ -102,6 +102,7 @@ module.exports = {
         this.collection = collection
         this.vms = []
 
+        console.log(collection)
         // listen for collection mutation events
         // the collection has been augmented during Binding.set()
         collection.__observer__.on('mutate', this.mutationListener)
@@ -123,7 +124,7 @@ module.exports = {
         var item = new ChildVM({
             el: node,
             each: true,
-            eachPrefix: this.arg + '.',
+            eachPrefix: this.arg,
             parentCompiler: this.compiler,
             delegator: this.container,
             data: {

+ 5 - 1
src/directives/on.js

@@ -45,8 +45,9 @@ module.exports = {
             dHandler = delegator.sd_dHandlers[identifier] = function (e) {
                 var target = delegateCheck(e.target, delegator, identifier)
                 if (target) {
+                    e.el = target
                     e.vm = target.sd_viewmodel
-                    e.item = e.vm[compiler.eachPrefix.slice(0, -1)]
+                    e.item = e.vm[compiler.eachPrefix]
                     handler.call(ownerVM, e)
                 }
             }
@@ -60,6 +61,9 @@ module.exports = {
             this.handler = function (e) {
                 e.el = e.currentTarget
                 e.vm = vm
+                if (compiler.each) {
+                    e.item = vm[compiler.eachPrefix]
+                }
                 handler.call(vm, e)
             }
             this.el.addEventListener(event, this.handler)

+ 1 - 0
src/observer.js

@@ -76,6 +76,7 @@ function bind (obj, key, path, observer) {
 }
 
 function defProtected (obj, key, val) {
+    if (obj.hasOwnProperty(key)) return
     def(obj, key, {
         enumerable: false,
         configurable: false,

+ 0 - 8
src/viewmodel.js

@@ -12,14 +12,6 @@ function ViewModel (options) {
 
 var VMProto = ViewModel.prototype
 
-VMProto.$wait = function () {
-    this.__wait__ = true
-}
-
-VMProto.$ready = function () {
-    this.$compiler.observer.emit('ready')
-}
-
 VMProto.$set = function (key, value) {
     var path = key.split('.'),
         level = 0,