transclude.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import { parseText } from '../parsers/text'
  2. import { parseTemplate } from '../parsers/template'
  3. import {
  4. warn,
  5. isTemplate,
  6. isFragment,
  7. prepend,
  8. extractContent,
  9. createAnchor,
  10. resolveAsset,
  11. toArray,
  12. addClass,
  13. hasBindAttr
  14. } from '../util/index'
  15. const specialCharRE = /[^\w\-:\.]/
  16. /**
  17. * Process an element or a DocumentFragment based on a
  18. * instance option object. This allows us to transclude
  19. * a template node/fragment before the instance is created,
  20. * so the processed fragment can then be cloned and reused
  21. * in v-for.
  22. *
  23. * @param {Element} el
  24. * @param {Object} options
  25. * @return {Element|DocumentFragment}
  26. */
  27. export function transclude (el, options) {
  28. // extract container attributes to pass them down
  29. // to compiler, because they need to be compiled in
  30. // parent scope. we are mutating the options object here
  31. // assuming the same object will be used for compile
  32. // right after this.
  33. if (options) {
  34. options._containerAttrs = extractAttrs(el)
  35. }
  36. // for template tags, what we want is its content as
  37. // a documentFragment (for fragment instances)
  38. if (isTemplate(el)) {
  39. el = parseTemplate(el)
  40. }
  41. if (options) {
  42. if (options._asComponent && !options.template) {
  43. options.template = '<slot></slot>'
  44. }
  45. if (options.template) {
  46. options._content = extractContent(el)
  47. el = transcludeTemplate(el, options)
  48. }
  49. }
  50. if (isFragment(el)) {
  51. // anchors for fragment instance
  52. // passing in `persist: true` to avoid them being
  53. // discarded by IE during template cloning
  54. prepend(createAnchor('v-start', true), el)
  55. el.appendChild(createAnchor('v-end', true))
  56. }
  57. return el
  58. }
  59. /**
  60. * Process the template option.
  61. * If the replace option is true this will swap the $el.
  62. *
  63. * @param {Element} el
  64. * @param {Object} options
  65. * @return {Element|DocumentFragment}
  66. */
  67. function transcludeTemplate (el, options) {
  68. var template = options.template
  69. var frag = parseTemplate(template, true)
  70. if (frag) {
  71. var replacer = frag.firstChild
  72. var tag = replacer.tagName && replacer.tagName.toLowerCase()
  73. if (options.replace) {
  74. /* istanbul ignore if */
  75. if (el === document.body) {
  76. process.env.NODE_ENV !== 'production' && warn(
  77. 'You are mounting an instance with a template to ' +
  78. '<body>. This will replace <body> entirely. You ' +
  79. 'should probably use `replace: false` here.'
  80. )
  81. }
  82. // there are many cases where the instance must
  83. // become a fragment instance: basically anything that
  84. // can create more than 1 root nodes.
  85. if (
  86. // multi-children template
  87. frag.childNodes.length > 1 ||
  88. // non-element template
  89. replacer.nodeType !== 1 ||
  90. // single nested component
  91. tag === 'component' ||
  92. resolveAsset(options, 'components', tag) ||
  93. hasBindAttr(replacer, 'is') ||
  94. // element directive
  95. resolveAsset(options, 'elementDirectives', tag) ||
  96. // for block
  97. replacer.hasAttribute('v-for') ||
  98. // if block
  99. replacer.hasAttribute('v-if')
  100. ) {
  101. return frag
  102. } else {
  103. options._replacerAttrs = extractAttrs(replacer)
  104. mergeAttrs(el, replacer)
  105. return replacer
  106. }
  107. } else {
  108. el.appendChild(frag)
  109. return el
  110. }
  111. } else {
  112. process.env.NODE_ENV !== 'production' && warn(
  113. 'Invalid template option: ' + template
  114. )
  115. }
  116. }
  117. /**
  118. * Helper to extract a component container's attributes
  119. * into a plain object array.
  120. *
  121. * @param {Element} el
  122. * @return {Array}
  123. */
  124. function extractAttrs (el) {
  125. if (el.nodeType === 1 && el.hasAttributes()) {
  126. return toArray(el.attributes)
  127. }
  128. }
  129. /**
  130. * Merge the attributes of two elements, and make sure
  131. * the class names are merged properly.
  132. *
  133. * @param {Element} from
  134. * @param {Element} to
  135. */
  136. function mergeAttrs (from, to) {
  137. var attrs = from.attributes
  138. var i = attrs.length
  139. var name, value
  140. while (i--) {
  141. name = attrs[i].name
  142. value = attrs[i].value
  143. if (!to.hasAttribute(name) && !specialCharRE.test(name)) {
  144. to.setAttribute(name, value)
  145. } else if (name === 'class' && !parseText(value) && (value = value.trim())) {
  146. value.split(/\s+/).forEach(function (cls) {
  147. addClass(to, cls)
  148. })
  149. }
  150. }
  151. }