diff --git a/component.json b/component.json index b60fe6763..5bdf4ab27 100644 --- a/component.json +++ b/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", diff --git a/src/batcher.js b/src/batcher.js index 7b4ac2c0e..1082ba1cb 100644 --- a/src/batcher.js +++ b/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() diff --git a/src/binding.js b/src/binding.js index c105e19a9..8861645f2 100644 --- a/src/binding.js +++ b/src/binding.js @@ -39,8 +39,6 @@ BindingProto.update = function (value) { execute: function () { if (!self.unbound) { self._update() - } else { - return false } } }) diff --git a/src/compiler.js b/src/compiler.js index f77d1d7e0..3eb7bee80 100644 --- a/src/compiler.js +++ b/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)) { + } 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 } /** diff --git a/src/directives/component.js b/src/directives/component.js new file mode 100644 index 000000000..8d0b57b83 --- /dev/null +++ b/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() + } + } + +} \ No newline at end of file diff --git a/src/directives/index.js b/src/directives/index.js index aa170adbd..598d40923 100644 --- a/src/directives/index.js +++ b/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 () { diff --git a/src/directives/with.js b/src/directives/with.js index 17071fc4b..1c0d8faad 100644 --- a/src/directives/with.js +++ b/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) + + 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() - } } -} \ No newline at end of file +} + +// 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() +// } +// } + +// } \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index 4d9441389..5ec21b1f0 100644 --- a/src/utils.js +++ b/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 diff --git a/test/functional/fixtures/component.html b/test/functional/fixtures/component.html index 27b52dddd..413eb51e0 100644 --- a/test/functional/fixtures/component.html +++ b/test/functional/fixtures/component.html @@ -5,8 +5,8 @@ - -
{{hi}} {{name}}
+ +
{{hi}} {{name}}
@@ -15,15 +15,16 @@ -
+
{{childHi}} {{childName}}
-
-
+ +
+
@@ -31,9 +32,9 @@