import { parseText } from '../parsers/text'
import { parseTemplate } from '../parsers/template'
import {
warn,
isTemplate,
isFragment,
prepend,
extractContent,
createAnchor,
resolveAsset,
toArray,
addClass,
hasBindAttr
} from '../util/index'
const specialCharRE = /[^\w\-:\.]/
/**
* 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-for.
*
* @param {Element} el
* @param {Object} options
* @return {Element|DocumentFragment}
*/
export function transclude (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 fragment instances)
if (isTemplate(el)) {
el = parseTemplate(el)
}
if (options) {
if (options._asComponent && !options.template) {
options.template = '<slot></slot>'
}
if (options.template) {
options._content = extractContent(el)
el = transcludeTemplate(el, options)
}
}
if (isFragment(el)) {
// anchors for fragment 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 = parseTemplate(template, true)
if (frag) {
var replacer = frag.firstChild
var tag = replacer.tagName && replacer.tagName.toLowerCase()
if (options.replace) {
/* istanbul ignore if */
if (el === document.body) {
process.env.NODE_ENV !== 'production' && warn(
'You are mounting an instance with a template to ' +
'<body>. This will replace <body> entirely. You ' +
'should probably use `replace: false` here.'
)
}
// there are many cases where the instance must
// become a fragment instance: basically anything that
// can create more than 1 root nodes.
if (
// multi-children template
frag.childNodes.length > 1 ||
// non-element template
replacer.nodeType !== 1 ||
// single nested component
tag === 'component' ||
resolveAsset(options, 'components', tag) ||
hasBindAttr(replacer, 'is') ||
// element directive
resolveAsset(options, 'elementDirectives', tag) ||
// for block
replacer.hasAttribute('v-for') ||
// if block
replacer.hasAttribute('v-if')
) {
return frag
} else {
options._replacerAttrs = extractAttrs(replacer)
mergeAttrs(el, replacer)
return replacer
}
} else {
el.appendChild(frag)
return el
}
} else {
process.env.NODE_ENV !== 'production' && warn(
'Invalid template option: ' + template
)
}
}
/**
* Helper to extract a component container's attributes
* into a plain object array.
*
* @param {Element} el
* @return {Array}
*/
function extractAttrs (el) {
if (el.nodeType === 1 && el.hasAttributes()) {
return toArray(el.attributes)
}
}
/**
* 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) && !specialCharRE.test(name)) {
to.setAttribute(name, value)
} else if (name === 'class' && !parseText(value) && (value = value.trim())) {
value.split(/\s+/).forEach(function (cls) {
addClass(to, cls)
})
}
}
}
|