transclude.js 4.1 KB

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