var _ = require('../util')
var config = require('../config')
var templateParser = require('../parsers/template')
/**
* Process an element or a DocumentFragment based on a
* instance option object. This allows us to transclude
* a template node/fragment before the instance is created,
* so the processed fragment can then be cloned and reused
* in v-repeat.
*
* @param {Element} el
* @param {Object} options
* @return {Element|DocumentFragment}
*/
exports.transclude = function (el, options) {
// extract container attributes to pass them down
// to compiler, because they need to be compiled in
// parent scope. we are mutating the options object here
// assuming the same object will be used for compile
// right after this.
if (options) {
options._containerAttrs = extractAttrs(el)
}
// for template tags, what we want is its content as
// a documentFragment (for block instances)
if (_.isTemplate(el)) {
el = templateParser.parse(el)
}
if (options) {
if (options._asComponent && !options.template) {
options.template = ''
}
if (options.template) {
options._content = _.extractContent(el)
el = transcludeTemplate(el, options)
}
}
if (el instanceof DocumentFragment) {
// anchors for block instance
// passing in `persist: true` to avoid them being
// discarded by IE during template cloning
_.prepend(_.createAnchor('v-start', true), el)
el.appendChild(_.createAnchor('v-end', true))
}
return el
}
/**
* Process the template option.
* If the replace option is true this will swap the $el.
*
* @param {Element} el
* @param {Object} options
* @return {Element|DocumentFragment}
*/
function transcludeTemplate (el, options) {
var template = options.template
var frag = templateParser.parse(template, true)
if (!frag) {
_.warn('Invalid template option: ' + template)
} else {
var replacer = frag.firstChild
var tag = replacer.tagName && replacer.tagName.toLowerCase()
if (options.replace) {
/* istanbul ignore if */
if (el === document.body) {
_.warn(
'You are mounting an instance with a template to ' +
'
. This will replace entirely. You ' +
'should probably use `replace: false` here.'
)
}
if (
// multi-children template
frag.childNodes.length > 1 ||
// non-element template
replacer.nodeType !== 1 ||
// when root node is , is an element
// directive, or has v-repeat, the instance could
// end up having multiple top-level nodes, thus
// becoming a block instance.
tag === 'component' ||
_.resolveAsset(options, 'elementDirectives', tag) ||
replacer.hasAttribute(config.prefix + 'repeat')
) {
return frag
} else {
options._replacerAttrs = extractAttrs(replacer)
mergeAttrs(el, replacer)
return replacer
}
} else {
el.appendChild(frag)
return el
}
}
}
/**
* Helper to extract a component container's attribute names
* into a map.
*
* @param {Element} el
* @return {Object}
*/
function extractAttrs (el) {
if (el.nodeType === 1 && el.hasAttributes()) {
var attrs = el.attributes
var res = {}
var i = attrs.length
while (i--) {
res[attrs[i].name] = attrs[i].value
}
return res
}
}
/**
* Merge the attributes of two elements, and make sure
* the class names are merged properly.
*
* @param {Element} from
* @param {Element} to
*/
function mergeAttrs (from, to) {
var attrs = from.attributes
var i = attrs.length
var name, value
while (i--) {
name = attrs[i].name
value = attrs[i].value
if (!to.hasAttribute(name)) {
to.setAttribute(name, value)
} else if (name === 'class') {
to.className = to.className + ' ' + value
}
}
}