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 fragment 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 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 = templateParser.parse(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 ' + '. This will replace 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) || replacer.hasAttribute(config.prefix + 'component') || // element directive _.resolveAsset(options, 'elementDirectives', tag) || // repeat block replacer.hasAttribute(config.prefix + 'repeat') ) { 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)) { to.setAttribute(name, value) } else if (name === 'class') { value = to.getAttribute(name) + ' ' + value to.setAttribute(name, value) } } }