Browse Source

dynamic partial

Evan You 11 years ago
parent
commit
daa94dfc5b

+ 13 - 0
src/api/lifecycle.js

@@ -1,4 +1,5 @@
 var _ = require('../util')
+var compile = require('../compile/compile')
 
 /**
  * Set instance target element and kick off the compilation
@@ -113,4 +114,16 @@ exports.$destroy = function (remove) {
   this._callHook('destroyed')
   // turn off all instance listeners.
   this.$off()
+}
+
+/**
+ * Partially compile a piece of DOM and return a
+ * decompile function.
+ *
+ * @param {Element|DocumentFragment} el
+ * @return {Function}
+ */
+
+exports.$compile = function (el) {
+  return compile(el, this.$options, true)(this, el)
 }

+ 31 - 0
src/compile/compile.js

@@ -29,10 +29,41 @@ module.exports = function compile (el, options, partial) {
     el.hasChildNodes()
       ? compileNodeList(el.childNodes, options)
       : null
+
+  /**
+   * A linker function to be called on a already compiled
+   * piece of DOM, which instantiates all directive
+   * instances.
+   *
+   * @param {Vue} vm
+   * @param {Element|DocumentFragment} el
+   * @return {Function|undefined}
+   */
+
   return function link (vm, el) {
+    var originalDirCount = vm._directives.length
     if (paramsLinkFn) paramsLinkFn(vm, el)
     if (nodeLinkFn) nodeLinkFn(vm, el)
     if (childLinkFn) childLinkFn(vm, el.childNodes)
+
+    /**
+     * If this is a partial compile, the linker function
+     * returns an unlink function that tearsdown all
+     * directives instances generated during the partial
+     * linking.
+     */
+
+    if (partial) {
+      var dirs = vm._directives.slice(originalDirCount)
+      return function unlink () {
+        var i = dirs.length
+        while (i--) {
+          dirs[i]._teardown()
+        }
+        i = vm._directives.indexOf(dirs[0])
+        vm._directives.splice(i, dirs.length)
+      }
+    }
   }
 }
 

+ 21 - 4
src/directives/partial.js

@@ -1,5 +1,4 @@
 var _ = require('../util')
-var compile = require('../compile/compile')
 var templateParser = require('../parse/template')
 
 module.exports = {
@@ -7,7 +6,17 @@ module.exports = {
   isLiteral: true,
 
   bind: function () {
-    var id = this.expression
+    if (!this._isDynamicLiteral) {
+      this.compile(this.expression)
+    } 
+  },
+
+  update: function (id) {
+    this.unbind()
+    this.compile(id)
+  },
+
+  compile: function (id) {
     var partial = this.vm.$options.partials[id]
     _.assertAsset(partial, 'partial', id)
     if (!partial) {
@@ -18,13 +27,21 @@ module.exports = {
     var vm = this.vm
     if (el.nodeType === 8) {
       // comment ref node means inline partial
-      compile(partial, vm.$options)(vm, partial)
+      // which can only be static
+      vm.$compile(partial)
       _.replace(el, partial)
     } else {
       // just set innerHTML...
       el.innerHTML = ''
       el.appendChild(partial)
-      compile(el, vm.$options, true)(vm, el)
+      this.decompile = vm.$compile(el)
+    }
+  },
+
+  unbind: function () {
+    if (this.decompile) {
+      this.decompile()
+      this.decompile = null
     }
   }
 

+ 19 - 2
test/unit/specs/compile/compile_spec.js

@@ -7,14 +7,20 @@ var compile = require('../../../../src/compile/compile')
 if (_.inBrowser) {
   describe('Compile', function () {
 
-    var vm, el, data
+    var vm, el, data, directiveTeardown
     beforeEach(function () {
       // We mock vms here so we can assert what the generated
       // linker functions do.
       el = document.createElement('div')
       data = {}
+      directiveTeardown = jasmine.createSpy()
       vm = {
-        _bindDir: jasmine.createSpy(),
+        _directives: [],
+        _bindDir: function () {
+          this._directives.push({
+            _teardown: directiveTeardown
+          })
+        },
         $set: jasmine.createSpy(),
         $eval: function (value) {
           return data[value]
@@ -23,6 +29,7 @@ if (_.inBrowser) {
           return data[value]
         }
       }
+      spyOn(vm, '_bindDir').and.callThrough()
       spyOn(vm, '$eval').and.callThrough()
       spyOn(vm, '$interpolate').and.callThrough()
       spyOn(_, 'warn')
@@ -181,5 +188,15 @@ if (_.inBrowser) {
       expect(el2.innerHTML).toBe('B')
     })
 
+    it('partial compilation', function () {
+      el.innerHTML = '<div v-attr="test:abc">{{bcd}}<p v-show="ok"></p></div>'
+      var linker = compile(el, Vue.options, true)
+      var decompile = linker(vm, el)
+      expect(vm._directives.length).toBe(3)
+      decompile()
+      expect(directiveTeardown.calls.count()).toBe(3)
+      expect(vm._directives.length).toBe(0)
+    })
+
   })
 }

+ 38 - 0
test/unit/specs/directives/partial_spec.js

@@ -48,5 +48,43 @@ if (_.inBrowser) {
       expect(el.innerHTML).toBe('<div><!--v-partial--></div>')
     })
 
+    it('dynamic partial', function (done) {
+      var vm = new Vue({
+        el: el,
+        template: '<div v-partial="{{partial}}"></div>',
+        data: {
+          msg: 'hello',
+          partial: 'p1'
+        },
+        partials: {
+          p1: '{{msg}}',
+          p2: '<div v-component="child"></div>'
+        },
+        components: {
+          child: {
+            data: function () {
+              return {a:123}
+            },
+            template: '{{a}}'
+          }
+        }
+      })
+      expect(el.firstChild.innerHTML).toBe('hello')
+      expect(vm._directives.length).toBe(2)
+      vm.partial = 'p2'
+      _.nextTick(function () {
+        expect(el.firstChild.innerHTML).toBe('<div>123</div><!--v-component-->')
+        expect(vm._directives.length).toBe(2)
+        expect(vm._children.length).toBe(1)
+        vm.partial = 'p1'
+        _.nextTick(function () {
+          expect(el.firstChild.innerHTML).toBe('hello')
+          expect(vm._directives.length).toBe(2)
+          expect(vm._children.length).toBe(0)
+          done()
+        })
+      })
+    })
+
   })
 }