var _ = require('../util') 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} */ module.exports = function transclude (el, options) { // for template tags, what we want is its content as // a documentFragment (for block instances) if (el.tagName === 'TEMPLATE') { el = templateParser.parse(el) } if (options && options.template) { el = transcludeTemplate(el, options) } if (el instanceof DocumentFragment) { _.prepend(document.createComment('v-start'), el) el.appendChild(document.createComment('v-end')) } 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 rawContent = options._content || _.extractContent(el) if (options.replace) { if (frag.childNodes.length > 1) { transcludeContent(frag, rawContent) return frag } else { var replacer = frag.firstChild _.copyAttributes(el, replacer) transcludeContent(replacer, rawContent) return replacer } } else { el.appendChild(frag) transcludeContent(el, rawContent) return el } } } /** * Resolve insertion points mimicking the behavior * of the Shadow DOM spec: * * http://w3c.github.io/webcomponents/spec/shadow/#insertion-points * * @param {Element|DocumentFragment} el * @param {Element} raw */ function transcludeContent (el, raw) { var outlets = getOutlets(el) var i = outlets.length if (!i) return var outlet, select, selected, j, main // first pass, collect corresponding content // for each outlet. while (i--) { outlet = outlets[i] if (raw) { select = outlet.getAttribute('select') if (select) { // select content selected = raw.querySelectorAll(select) outlet.content = _.toArray( selected.length ? selected : outlet.childNodes ) } else { // default content main = outlet } } else { // fallback content outlet.content = _.toArray(outlet.childNodes) } } // second pass, actually insert the contents for (i = 0, j = outlets.length; i < j; i++) { outlet = outlets[i] if (outlet !== main) { insertContentAt(outlet, outlet.content) } } // finally insert the main content if (main) { insertContentAt(main, _.toArray(raw.childNodes)) } } /** * Get outlets from the element/list * * @param {Element|Array} el * @return {Array} */ var concat = [].concat function getOutlets (el) { return _.isArray(el) ? concat.apply([], el.map(getOutlets)) : el.querySelectorAll ? _.toArray(el.querySelectorAll('content')) : [] } /** * Insert an array of nodes at outlet, * then remove the outlet. * * @param {Element} outlet * @param {Array} contents */ function insertContentAt (outlet, contents) { // not using util DOM methods here because // parentNode can be cached var parent = outlet.parentNode for (var i = 0, j = contents.length; i < j; i++) { parent.insertBefore(contents[i], outlet) } parent.removeChild(outlet) }