transclude.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. var _ = require('../util')
  2. var config = require('../config')
  3. var templateParser = require('../parsers/template')
  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-repeat.
  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 = '<content></content>'
  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(config.prefix + 'component') ||
  82. // element directive
  83. _.resolveAsset(options, 'elementDirectives', tag) ||
  84. // repeat block
  85. replacer.hasAttribute(config.prefix + 'repeat')
  86. ) {
  87. return frag
  88. } else {
  89. options._replacerAttrs = extractAttrs(replacer)
  90. mergeAttrs(el, replacer)
  91. return replacer
  92. }
  93. } else {
  94. el.appendChild(frag)
  95. return el
  96. }
  97. } else {
  98. process.env.NODE_ENV !== 'production' && _.warn(
  99. 'Invalid template option: ' + template
  100. )
  101. }
  102. }
  103. /**
  104. * Helper to extract a component container's attribute names
  105. * into a map.
  106. *
  107. * @param {Element} el
  108. * @return {Object}
  109. */
  110. function extractAttrs (el) {
  111. if (el.nodeType === 1 && el.hasAttributes()) {
  112. var attrs = el.attributes
  113. var res = {}
  114. var i = attrs.length
  115. while (i--) {
  116. res[attrs[i].name] = attrs[i].value
  117. }
  118. return res
  119. }
  120. }
  121. /**
  122. * Merge the attributes of two elements, and make sure
  123. * the class names are merged properly.
  124. *
  125. * @param {Element} from
  126. * @param {Element} to
  127. */
  128. function mergeAttrs (from, to) {
  129. var attrs = from.attributes
  130. var i = attrs.length
  131. var name, value
  132. while (i--) {
  133. name = attrs[i].name
  134. value = attrs[i].value
  135. if (!to.hasAttribute(name)) {
  136. to.setAttribute(name, value)
  137. } else if (name === 'class') {
  138. to.className = to.className + ' ' + value
  139. }
  140. }
  141. }