Просмотр исходного кода

ensure slot compilation order inside conditional fragments (fix #1965)

Evan You 10 лет назад
Родитель
Сommit
2b9e072d82

+ 7 - 1
src/compiler/compile.js

@@ -15,7 +15,8 @@ import {
   findRef,
   defineReactive,
   assertAsset,
-  getAttr
+  getAttr,
+  hasBindAttr
 } from '../util/index'
 
 // special binding prefixes
@@ -514,6 +515,11 @@ function makeChildLinkFn (linkFns) {
 function checkElementDirectives (el, options) {
   var tag = el.tagName.toLowerCase()
   if (commonTagRE.test(tag)) return
+  // special case: give named slot a higher priority
+  // than unnamed slots
+  if (tag === 'slot' && hasBindAttr(el, 'name')) {
+    tag = '_namedSlot'
+  }
   var def = resolveAsset(options, 'elementDirectives', tag)
   if (def) {
     return makeTerminalNodeLinkFn(el, tag, '', options, def)

+ 3 - 4
src/compiler/transclude.js

@@ -7,7 +7,8 @@ import {
   createAnchor,
   resolveAsset,
   toArray,
-  addClass
+  addClass,
+  hasBindAttr
 } from '../util/index'
 
 const specialCharRE = /[^\w\-:\.]/
@@ -92,9 +93,7 @@ function transcludeTemplate (el, options) {
         // single nested component
         tag === 'component' ||
         resolveAsset(options, 'components', tag) ||
-        replacer.hasAttribute('is') ||
-        replacer.hasAttribute(':is') ||
-        replacer.hasAttribute('v-bind:is') ||
+        hasBindAttr(replacer, 'is') ||
         // element directive
         resolveAsset(options, 'elementDirectives', tag) ||
         // for block

+ 2 - 1
src/directives/element/index.js

@@ -1,7 +1,8 @@
-import slot from './slot'
+import { slot, namedSlot as _namedSlot } from './slot'
 import partial from './partial'
 
 export default {
   slot,
+  _namedSlot, // same as slot but with higher priority
   partial
 }

+ 28 - 32
src/directives/element/slot.js

@@ -4,6 +4,7 @@ import {
 } from '../../parsers/template'
 
 import {
+  extend,
   extractContent,
   replace,
   remove,
@@ -15,60 +16,46 @@ import {
 // instance being stored as `$options._content` during
 // the transclude phase.
 
-export default {
+// We are exporting two versions, one for named and one
+// for unnamed, because the unnamed slots must be compiled
+// AFTER all named slots have selected their content. So
+// we need to give them different priorities in the compilation
+// process. (See #1965)
 
-  priority: 1750,
+export const slot = {
 
-  params: ['name'],
+  priority: 1750,
 
   bind () {
     var host = this.vm
     var raw = host.$options._content
-    var content
     if (!raw) {
       this.fallback()
       return
     }
     var context = host._context
-    var slotName = this.params.name
+    var slotName = this.params && this.params.name
     if (!slotName) {
-      // Default content
-      var self = this
-      var compileDefaultContent = function () {
-        var content = extractFragment(raw.childNodes, raw, true)
-        if (content.hasChildNodes()) {
-          self.compile(content, context, host)
-        } else {
-          self.fallback()
-        }
-      }
-      if (!host._isCompiled) {
-        // defer until the end of instance compilation,
-        // because the default outlet must wait until all
-        // other possible outlets with selectors have picked
-        // out their contents.
-        host.$once('hook:compiled', compileDefaultContent)
-      } else {
-        compileDefaultContent()
-      }
+      // Default slot
+      this.tryCompile(extractFragment(raw.childNodes, raw, true), context, host)
     } else {
+      // Named slot
       var selector = '[slot="' + slotName + '"]'
       var nodes = raw.querySelectorAll(selector)
       if (nodes.length) {
-        content = extractFragment(nodes, raw)
-        if (content.hasChildNodes()) {
-          this.compile(content, context, host)
-        } else {
-          this.fallback()
-        }
+        this.tryCompile(extractFragment(nodes, raw), context, host)
       } else {
         this.fallback()
       }
     }
   },
 
-  fallback () {
-    this.compile(extractContent(this.el, true), this.vm)
+  tryCompile (content, context, host) {
+    if (content.hasChildNodes()) {
+      this.compile(content, context, host)
+    } else {
+      this.fallback()
+    }
   },
 
   compile (content, context, host) {
@@ -87,6 +74,10 @@ export default {
     }
   },
 
+  fallback () {
+    this.compile(extractContent(this.el, true), this.vm)
+  },
+
   unbind () {
     if (this.unlink) {
       this.unlink()
@@ -94,6 +85,11 @@ export default {
   }
 }
 
+export const namedSlot = extend(extend({}, slot), {
+  priority: slot.priority + 1,
+  params: ['name']
+})
+
 /**
  * Extract qualified content nodes from a node list.
  *

+ 14 - 0
src/util/dom.js

@@ -75,6 +75,20 @@ export function getBindAttr (node, name) {
   return val
 }
 
+/**
+ * Check the presence of a bind attribute.
+ *
+ * @param {Node} node
+ * @param {String} name
+ * @return {Boolean}
+ */
+
+export function hasBindAttr (node, name) {
+  return node.hasAttribute(name) ||
+    node.hasAttribute(':' + name) ||
+    node.hasAttribute('v-bind:' + name)
+}
+
 /**
  * Insert el before target
  *

+ 6 - 5
test/unit/specs/directives/element/slot_spec.js

@@ -169,27 +169,28 @@ describe('Slot Distribution', function () {
       el: el,
       data: {
         a: 1,
+        b: 2,
         show: true
       },
-      template: '<test :show="show">{{a}}</test>',
+      template: '<test :show="show"><p slot="b">{{b}}</a><p>{{a}}</p></test>',
       components: {
         test: {
           props: ['show'],
-          template: '<div v-if="show"><slot></cotent></div>'
+          template: '<div v-if="show"><slot></slot><slot name="b"></slot></div>'
         }
       }
     })
-    expect(el.textContent).toBe('1')
+    expect(el.textContent).toBe('12')
     vm.a = 2
     _.nextTick(function () {
-      expect(el.textContent).toBe('2')
+      expect(el.textContent).toBe('22')
       vm.show = false
       _.nextTick(function () {
         expect(el.textContent).toBe('')
         vm.show = true
         vm.a = 3
         _.nextTick(function () {
-          expect(el.textContent).toBe('3')
+          expect(el.textContent).toBe('32')
           done()
         })
       })