mirror of https://github.com/vuejs/vue.git
				
				
				
			v-component & v-with refactor
This commit is contained in:
		
							parent
							
								
									2059be5a63
								
							
						
					
					
						commit
						665520f59b
					
				|  | @ -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", | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -39,8 +39,6 @@ BindingProto.update = function (value) { | |||
|             execute: function () { | ||||
|                 if (!self.unbound) { | ||||
|                     self._update() | ||||
|                 } else { | ||||
|                     return false | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  |  | |||
|  | @ -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)) { | ||||
|         } else if (root !== true && (Component = this.resolveComponent(node, undefined, true))) { | ||||
| 
 | ||||
|             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) | ||||
|                 } | ||||
|             }) | ||||
|             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 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  |  | |||
|  | @ -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() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -13,6 +13,7 @@ module.exports = { | |||
|     style     : require('./style'), | ||||
|     partial   : require('./partial'), | ||||
|     view      : require('./view'), | ||||
|     component : require('./component'), | ||||
| 
 | ||||
|     attr: { | ||||
|         bind: function () { | ||||
|  |  | |||
|  | @ -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) | ||||
| 
 | ||||
|         var self      = this, | ||||
|             childKey  = self.arg, | ||||
|             parentKey = self.key, | ||||
|             compiler  = self.compiler, | ||||
|             owner     = self.binding.compiler | ||||
| 
 | ||||
|         if (compiler === owner) { | ||||
|             this.alone = true | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         if (childKey) { | ||||
|             if (!compiler.bindings[childKey]) { | ||||
|                 compiler.createBinding(childKey) | ||||
|             } | ||||
|         } else if (this.isEmpty) { | ||||
|             this.build() | ||||
|             // 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) | ||||
|             }) | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     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') | ||||
|     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 | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     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() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| // 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()
 | ||||
| //         }
 | ||||
| //     }
 | ||||
| 
 | ||||
| // }
 | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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}}' | ||||
|  |  | |||
|  | @ -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({ | ||||
|  |  | |||
|  | @ -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') | ||||
|     }) | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue