فهرست منبع

v-component & v-with refactor

Evan You 12 سال پیش
والد
کامیت
665520f59b

+ 1 - 0
component.json

@@ -23,6 +23,7 @@
         "src/transition.js",
         "src/batcher.js",
         "src/directives/index.js",
+        "src/directives/component.js",
         "src/directives/if.js",
         "src/directives/repeat.js",
         "src/directives/on.js",

+ 2 - 3
src/batcher.js

@@ -29,9 +29,8 @@ BatcherProto.flush = function () {
     // as we execute existing jobs
     for (var i = 0; i < this.queue.length; i++) {
         var job = this.queue[i]
-        if (job.cancelled) continue
-        if (job.execute() !== false) {
-            this.has[job.id] = false
+        if (!job.cancelled) {
+            job.execute()
         }
     }
     this.reset()

+ 0 - 2
src/binding.js

@@ -39,8 +39,6 @@ BindingProto.update = function (value) {
             execute: function () {
                 if (!self.unbound) {
                     self._update()
-                } else {
-                    return false
                 }
             }
         })

+ 38 - 24
src/compiler.js

@@ -325,6 +325,8 @@ CompilerProto.observeData = function (data) {
  */
 CompilerProto.compile = function (node, root) {
 
+    /* jshint boss: true */
+
     var compiler = this,
         nodeType = node.nodeType,
         tagName  = node.tagName
@@ -335,22 +337,15 @@ CompilerProto.compile = function (node, root) {
         if (utils.attr(node, 'pre') !== null) return
 
         // special attributes to check
-        var repeatExp,
-            viewExp,
-            withExp,
-            directive,
-            // resolve a standalone child component with no inherited data
-            hasComponent = this.resolveComponent(node, undefined, true)
+        var directive, repeatExp, viewExp, Component
 
         // It is important that we access these attributes
         // procedurally because the order matters.
-        //
         // `utils.attr` removes the attribute once it gets the
         // value, so we should not access them all at once.
 
         // v-repeat has the highest priority
         // and we need to preserve all other attributes for it.
-        /* jshint boss: true */
         if (repeatExp = utils.attr(node, 'repeat')) {
 
             // repeat block cannot have v-id at the same time.
@@ -370,18 +365,26 @@ CompilerProto.compile = function (node, root) {
             }
 
         // Child component has 2nd highest priority
-        } else if (root !== true && ((withExp = utils.attr(node, 'with')) || hasComponent)) {
-
-            withExp = Directive.split(withExp || '')
-            withExp.forEach(function (exp, i) {
-                var directive = Directive.parse('with', exp, compiler, node)
-                if (directive) {
-                    // notify the directive that this is the
-                    // last expression in the group
-                    directive.last = i === withExp.length - 1
-                    compiler.deferred.push(directive)
-                }
-            })
+        } else if (root !== true && (Component = this.resolveComponent(node, undefined, true))) {
+
+            directive = Directive.parse('component', '', compiler, node)
+            if (directive) {
+                directive.Ctor = Component
+                compiler.deferred.push(directive)
+            }
+
+            // should build component
+
+            // withExp = Directive.split(withExp || '')
+            // withExp.forEach(function (exp, i) {
+            //     var directive = Directive.parse('with', exp, compiler, node)
+            //     if (directive) {
+            //         // notify the directive that this is the
+            //         // last expression in the group
+            //         directive.last = i === withExp.length - 1
+            //         compiler.deferred.push(directive)
+            //     }
+            // })
 
         } else {
 
@@ -432,7 +435,13 @@ CompilerProto.compileNode = function (node) {
                 exp = exps[j]
                 dirname = attr.name.slice(prefix.length)
                 directive = Directive.parse(dirname, exp, this, node)
-                this.bindDirective(directive)
+
+                if (dirname === 'with') {
+                    this.bindDirective(directive, this.parent)
+                } else {
+                    this.bindDirective(directive)
+                }
+                
             }
         } else if (config.interpolate) {
             // non directive attribute, check interpolation tags
@@ -811,7 +820,10 @@ CompilerProto.eval = function (exp, data) {
  */
 CompilerProto.resolveComponent = function (node, data, test) {
 
-    var exp     = utils.attr(node, 'component', test),
+    // late require to avoid circular deps
+    ViewModel = ViewModel || require('./viewmodel')
+
+    var exp     = utils.attr(node, 'component'),
         tagName = node.tagName,
         id      = this.eval(exp, data),
         tagId   = (tagName.indexOf('-') > 0 && tagName.toLowerCase()),
@@ -822,8 +834,10 @@ CompilerProto.resolveComponent = function (node, data, test) {
     }
 
     return test
-        ? Ctor
-        : Ctor || (ViewModel || (ViewModel = require('./viewmodel')))
+        ? exp === ''
+            ? ViewModel
+            : Ctor
+        : Ctor || ViewModel
 }
 
 /**

+ 20 - 0
src/directives/component.js

@@ -0,0 +1,20 @@
+module.exports = {
+
+    isLiteral: true,
+
+    bind: function () {
+        if (!this.el.vue_vm) {
+            this.component = new this.Ctor({
+                el: this.el,
+                parent: this.vm
+            })
+        }
+    },
+
+    unbind: function () {
+        if (this.component) {
+            this.component.$destroy()
+        }
+    }
+
+}

+ 1 - 0
src/directives/index.js

@@ -13,6 +13,7 @@ module.exports = {
     style     : require('./style'),
     partial   : require('./partial'),
     view      : require('./view'),
+    component : require('./component'),
 
     attr: {
         bind: function () {

+ 123 - 75
src/directives/with.js

@@ -3,88 +3,136 @@ var utils = require('../utils')
 module.exports = {
 
     bind: function () {
-        if (this.el.vue_vm) {
-            this.subVM = this.el.vue_vm
-            var compiler = this.subVM.$compiler
-            if (this.arg && !compiler.bindings[this.arg]) {
-                compiler.createBinding(this.arg)
-            }
-        } else if (this.isEmpty) {
-            this.build()
-        }
-    },
 
-    update: function (value, init) {
-        var vm = this.subVM,
-            key = this.arg || '$data'
-        if (!vm) {
-            this.build(value)
-        } else if (!this.lock && vm[key] !== value) {
-            vm[key] = value
-        }
-        if (init) {
-            // watch after first set
-            this.watch()
-            // The v-with directive can have multiple expressions,
-            // and we want to make sure when the ready hook is called
-            // on the subVM, all these clauses have been properly set up.
-            // So this is a hack that sniffs whether we have reached
-            // the last expression. We hold off the subVM's ready hook
-            // until we are actually ready.
-            if (this.last) {
-                this.subVM.$compiler.execHook('ready')
-            }
-        }
-    },
+        var self      = this,
+            childKey  = self.arg,
+            parentKey = self.key,
+            compiler  = self.compiler,
+            owner     = self.binding.compiler
 
-    build: function (value) {
-        var data = value
-        if (this.arg) {
-            data = {}
-            data[this.arg] = value
+        if (compiler === owner) {
+            this.alone = true
+            return
         }
-        var Ctor = this.compiler.resolveComponent(this.el, data)
-        this.subVM = new Ctor({
-            el     : this.el,
-            data   : data,
-            parent : this.vm,
-            compilerOptions: {
-                // it is important to delay the ready hook
-                // so that when it's called, all `v-with` wathcers
-                // would have been set up.
-                delayReady: !this.last
-            }
-        })
-        // mark that this VM is created by v-with
-        utils.defProtected(this.subVM, '$with', true)
-    },
 
-    /**
-     *  For inhertied keys, need to watch
-     *  and sync back to the parent
-     */
-    watch: function () {
-        if (!this.arg) return
-        var self    = this,
-            key     = self.key,
-            ownerVM = self.binding.compiler.vm
-        this.subVM.$compiler.observer.on('change:' + this.arg, function (val) {
-            if (!self.lock) {
-                self.lock = true
-                utils.nextTick(function () {
-                    self.lock = false
-                })
+        if (childKey) {
+            if (!compiler.bindings[childKey]) {
+                compiler.createBinding(childKey)
             }
-            ownerVM.$set(key, val)
-        })
+            // sync changes on child back to parent
+            compiler.observer.on('change:' + childKey, function (val) {
+                if (compiler.init) return
+                if (!self.lock) {
+                    self.lock = true
+                    utils.nextTick(function () {
+                        self.lock = false
+                    })
+                }
+                owner.vm.$set(parentKey, val)
+            })
+        }
     },
 
-    unbind: function () {
-        // all watchers are turned off during destroy
-        // so no need to worry about it
-        if (this.subVM.$with) {
-            this.subVM.$destroy()
+    update: function (value) {
+        // sync from parent
+        if (!this.alone && !this.lock) {
+            if (this.arg) {
+                this.vm.$set(this.arg, value)
+            } else {
+                this.vm.$data = value
+            }
         }
     }
 
-}
+}
+
+// var utils = require('../utils')
+
+// module.exports = {
+
+//     bind: function () {
+//         if (this.el.vue_vm) {
+//             this.subVM = this.el.vue_vm
+//             var compiler = this.subVM.$compiler
+//             if (this.arg && !compiler.bindings[this.arg]) {
+//                 compiler.createBinding(this.arg)
+//             }
+//         } else if (this.isEmpty) {
+//             this.build()
+//         }
+//     },
+
+//     update: function (value, init) {
+//         var vm = this.subVM,
+//             key = this.arg || '$data'
+//         if (!vm) {
+//             this.build(value)
+//         } else if (!this.lock && vm[key] !== value) {
+//             vm[key] = value
+//         }
+//         if (init) {
+//             // watch after first set
+//             this.watch()
+//             // The v-with directive can have multiple expressions,
+//             // and we want to make sure when the ready hook is called
+//             // on the subVM, all these clauses have been properly set up.
+//             // So this is a hack that sniffs whether we have reached
+//             // the last expression. We hold off the subVM's ready hook
+//             // until we are actually ready.
+//             if (this.last) {
+//                 this.subVM.$compiler.execHook('ready')
+//             }
+//         }
+//     },
+
+//     build: function (value) {
+//         var data = value
+//         if (this.arg) {
+//             data = {}
+//             data[this.arg] = value
+//         }
+//         var Ctor = this.compiler.resolveComponent(this.el, data)
+//         this.subVM = new Ctor({
+//             el     : this.el,
+//             data   : data,
+//             parent : this.vm,
+//             compilerOptions: {
+//                 // it is important to delay the ready hook
+//                 // so that when it's called, all `v-with` wathcers
+//                 // would have been set up.
+//                 delayReady: !this.last
+//             }
+//         })
+//         // mark that this VM is created by v-with
+//         utils.defProtected(this.subVM, '$with', true)
+//     },
+
+//     /**
+//      *  For inhertied keys, need to watch
+//      *  and sync back to the parent
+//      */
+//     watch: function () {
+//         if (!this.arg) return
+//         var self    = this,
+//             key     = self.key,
+//             ownerVM = self.binding.compiler.vm
+//         this.subVM.$compiler.observer.on('change:' + this.arg, function (val) {
+//             if (!self.lock) {
+//                 self.lock = true
+//                 utils.nextTick(function () {
+//                     self.lock = false
+//                 })
+//             }
+//             ownerVM.$set(key, val)
+//         })
+//     },
+
+//     unbind: function () {
+//         // all watchers are turned off during destroy
+//         // so no need to worry about it
+//         if (this.subVM.$with) {
+//             this.subVM.$destroy()
+//         }
+//     }
+
+// }

+ 2 - 2
src/utils.js

@@ -63,10 +63,10 @@ var utils = module.exports = {
     /**
      *  get an attribute and remove it.
      */
-    attr: function (el, type, preserve) {
+    attr: function (el, type) {
         var attr = config.prefix + '-' + type,
             val = el.getAttribute(attr)
-        if (!preserve && val !== null) {
+        if (val !== null) {
             el.removeAttribute(attr)
         }
         return val

+ 9 - 8
test/functional/fixtures/component.html

@@ -5,8 +5,8 @@
     <!-- custom element + v-with -->
     <my-avatar id="element-and-with" v-with="user"></my-avatar>
 
-    <!-- v-with alone -->
-    <div id="with" v-with="user">{{hi}} {{name}}</div>
+    <!-- v-with with default Ctor -->
+    <div id="with" v-component v-with="user">{{hi}} {{name}}</div>
 
     <!-- v-component alone -->
     <div id="component" v-component="my-element"></div>
@@ -15,15 +15,16 @@
     <my-element id="element"></my-element>
 
     <!-- v-with + binding sync -->
-    <div id="with-sync" v-with="childHi:hi, childName:user.name">
+    <div id="with-sync" v-component v-with="childHi:hi, childName:user.name">
         {{childHi}} {{childName}}
     </div>
 
-    <div id="component-with-sync" v-component="sync" v-with="childHi:hi, childName:user.name"></div>
-
     <!-- conditional component -->
     <div id="conditional" v-component="{{ok ? 'my-element' : 'nope'}}"></div>
 
+    <!-- this one will change everything for everyone else... -->
+    <div id="component-with-sync" v-component="sync" v-with="childHi:hi, childName:user.name"></div>
+
     <!-- conditional component with v-repeat! -->
     <div class="repeat-conditional {{type}}" v-repeat="items" v-component="{{type}}"></div>
 </div>
@@ -31,9 +32,9 @@
 <script src="../../../dist/vue.js"></script>
 <script>
 
-    Vue.config({
-        debug: true
-    })
+    // Vue.config({
+    //     debug: true
+    // })
 
     Vue.component('my-avatar', {
         template: '{{hi}} {{name}}'

+ 3 - 3
test/functional/fixtures/extend.html

@@ -12,7 +12,7 @@
 
 <script src="../../../dist/vue.js"></script>
 <script>
-    Vue.config({debug:true})
+    // Vue.config({debug:true})
     var log = document.getElementById('log')
     var T = Vue.extend({
         created: function () {
@@ -40,8 +40,8 @@
     })
     T.partial('partial-test', '{{partialMsg}}')
     T.component('vm-w-model', {
-        data : {
-            selfMsg: 'component with model '
+        ready: function () {
+            this.selfMsg = 'component + with '
         }
     })
     var C = T.extend({

+ 1 - 1
test/functional/specs/extend.js

@@ -7,7 +7,7 @@ casper.test.begin('Encapsulation & Inheritance', 8, function (test) {
         test.assertSelectorHasText('.filter', 'filter works')
         test.assertSelectorHasText('.partial', 'partial works')
         test.assertSelectorHasText('.vm', 'component works')
-        test.assertSelectorHasText('.vm-w-model', 'component with model works')
+        test.assertSelectorHasText('.vm-w-model', 'component + with works')
         test.assertSelectorHasText('#log', 'T created T ready T created C created T ready C ready', 'hook inheritance works')
         test.assertSelectorHasText('.cvm', 'component works', 'Child should have access to Parent options')
     })

+ 3 - 3
test/unit/specs/directives.js

@@ -609,7 +609,7 @@ describe('Directives', function () {
         
         it('should create a child viewmodel with given data', function () {
             var testId = 'with-test'
-            mock(testId, '<span v-with="test">{{msg}}</span>')
+            mock(testId, '<span v-component v-with="test">{{msg}}</span>')
             var t = new Vue({
                 el: '#' + testId,
                 data: {
@@ -625,7 +625,7 @@ describe('Directives', function () {
             var t = new Vue({
                 template:
                     '<span>{{test.msg}} {{n}}</span>'
-                    + '<p v-with="childMsg:test.msg, n:n" v-ref="child">{{childMsg}} {{n}}</p>',
+                    + '<p v-component v-with="childMsg:test.msg, n:n" v-ref="child">{{childMsg}} {{n}}</p>',
                 data: {
                     n: 1,
                     test: {
@@ -707,7 +707,7 @@ describe('Directives', function () {
 
         it('should work with interpolation', function () {
             t = new Vue({
-                template: '<div v-with="obj" v-ref="{{ok ? \'a\' : \'b\'}}"></div>',
+                template: '<div v-component v-with="obj" v-ref="{{ok ? \'a\' : \'b\'}}"></div>',
                 data: { obj: { a: 123 } }
             })
             assert.equal(t.$.b.a, 123)