transclude.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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 block 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 block 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. _.warn('Invalid template option: ' + template)
  60. } else {
  61. var replacer = frag.firstChild
  62. var tag = replacer.tagName && replacer.tagName.toLowerCase()
  63. if (options.replace) {
  64. /* istanbul ignore if */
  65. if (el === document.body) {
  66. _.warn(
  67. 'You are mounting an instance with a template to ' +
  68. '<body>. This will replace <body> entirely. You ' +
  69. 'should probably use `replace: false` here.'
  70. )
  71. }
  72. if (
  73. // multi-children template
  74. frag.childNodes.length > 1 ||
  75. // non-element template
  76. replacer.nodeType !== 1 ||
  77. // when root node is <component>, is an element
  78. // directive, or has v-repeat, the instance could
  79. // end up having multiple top-level nodes, thus
  80. // becoming a block instance.
  81. tag === 'component' ||
  82. _.resolveAsset(options, 'elementDirectives', tag) ||
  83. replacer.hasAttribute(config.prefix + 'repeat')
  84. ) {
  85. return frag
  86. } else {
  87. options._replacerAttrs = extractAttrs(replacer)
  88. mergeAttrs(el, replacer)
  89. return replacer
  90. }
  91. } else {
  92. el.appendChild(frag)
  93. return el
  94. }
  95. }
  96. }
  97. /**
  98. * Helper to extract a component container's attribute names
  99. * into a map.
  100. *
  101. * @param {Element} el
  102. * @return {Object}
  103. */
  104. function extractAttrs (el) {
  105. if (el.nodeType === 1 && el.hasAttributes()) {
  106. var attrs = el.attributes
  107. var res = {}
  108. var i = attrs.length
  109. while (i--) {
  110. res[attrs[i].name] = attrs[i].value
  111. }
  112. return res
  113. }
  114. }
  115. /**
  116. * Merge the attributes of two elements, and make sure
  117. * the class names are merged properly.
  118. *
  119. * @param {Element} from
  120. * @param {Element} to
  121. */
  122. function mergeAttrs (from, to) {
  123. var attrs = from.attributes
  124. var i = attrs.length
  125. var name, value
  126. while (i--) {
  127. name = attrs[i].name
  128. value = attrs[i].value
  129. if (!to.hasAttribute(name)) {
  130. to.setAttribute(name, value)
  131. } else if (name === 'class') {
  132. to.className = to.className + ' ' + value
  133. }
  134. }
  135. }