UNPKG

silk-gui

Version:

GUI for developers and Node OS

214 lines (200 loc) 6.19 kB
var _ = require('../util') var config = require('../config') var templateParser = require('../parsers/template') var transcludedFlagAttr = '__vue__transcluded' /** * 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) { if (options && options._asComponent) { // mutating the options object here assuming the same // object will be used for compile right after this options._transcludedAttrs = extractAttrs(el.attributes) // Mark content nodes and attrs so that the compiler // knows they should be compiled in parent scope. var i = el.childNodes.length while (i--) { var node = el.childNodes[i] if (node.nodeType === 1) { node.setAttribute(transcludedFlagAttr, '') } else if (node.nodeType === 3 && node.data.trim()) { // wrap transcluded textNodes in spans, because // raw textNodes can't be persisted through clones // by attaching attributes. var wrapper = document.createElement('span') wrapper.textContent = node.data wrapper.setAttribute('__vue__wrap', '') wrapper.setAttribute(transcludedFlagAttr, '') el.replaceChild(wrapper, node) } } } // 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) { // this is a block instance which has no root node. // however, the container in the parent template // (which is replaced here) may contain v-with and // paramAttributes that still need to be compiled // for the child. we store all the container // attributes on the options object and pass it down // to the compiler. var containerAttrs = options._containerAttrs = {} var i = el.attributes.length while (i--) { var attr = el.attributes[i] containerAttrs[attr.name] = attr.value } 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 <content> 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 function isDirectChild (node) { return node.parentNode === raw } // 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) if (selected.length) { // according to Shadow DOM spec, `select` can // only select direct children of the host node. // enforcing this also fixes #786. selected = [].filter.call(selected, isDirectChild) } outlet.content = selected.length ? selected : _.toArray(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 <content> 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) } /** * Helper to extract a component container's attribute names * into a map, and filtering out `v-with` in the process. * The resulting map will be used in compiler/compile to * determine whether an attribute is transcluded. * * @param {NameNodeMap} attrs */ function extractAttrs (attrs) { if (!attrs) return null var res = {} var vwith = config.prefix + 'with' var i = attrs.length while (i--) { var name = attrs[i].name if (name !== vwith) res[name] = true } return res }