| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- var _ = require('../util')
- var config = require('../config')
- var textParser = require('../parse/text')
- var dirParser = require('../parse/directive')
- var templateParser = require('../parse/template')
- /**
- * Compile a template and return a reusable composite link
- * function, which recursively contains more link functions
- * inside. This top level compile function should only be
- * called on instance root nodes.
- *
- * @param {Element|DocumentFragment} el
- * @param {Object} options
- * @param {Boolean} partial
- * @return {Function}
- */
- module.exports = function compile (el, options, partial) {
- var params = !partial && options.paramAttributes
- var paramsLinkFn = params
- ? compileParamAttributes(el, params, options)
- : null
- var nodeLinkFn = el instanceof DocumentFragment
- ? null
- : compileNode(el, options)
- var childLinkFn =
- (!nodeLinkFn || !nodeLinkFn.terminal) &&
- el.hasChildNodes()
- ? compileNodeList(el.childNodes, options)
- : null
- /**
- * A linker function to be called on a already compiled
- * piece of DOM, which instantiates all directive
- * instances.
- *
- * @param {Vue} vm
- * @param {Element|DocumentFragment} el
- * @return {Function|undefined}
- */
- return function link (vm, el) {
- var originalDirCount = vm._directives.length
- if (paramsLinkFn) paramsLinkFn(vm, el)
- if (nodeLinkFn) nodeLinkFn(vm, el)
- if (childLinkFn) childLinkFn(vm, el.childNodes)
- /**
- * If this is a partial compile, the linker function
- * returns an unlink function that tearsdown all
- * directives instances generated during the partial
- * linking.
- */
- if (partial) {
- var dirs = vm._directives.slice(originalDirCount)
- return function unlink () {
- var i = dirs.length
- while (i--) {
- dirs[i]._teardown()
- }
- i = vm._directives.indexOf(dirs[0])
- vm._directives.splice(i, dirs.length)
- }
- }
- }
- }
- /**
- * Compile a node and return a nodeLinkFn based on the
- * node type.
- *
- * @param {Node} node
- * @param {Object} options
- * @return {Function|undefined}
- */
- function compileNode (node, options) {
- var type = node.nodeType
- if (type === 1 && node.tagName !== 'SCRIPT') {
- return compileElement(node, options)
- } else if (type === 3 && config.interpolate) {
- return compileTextNode(node, options)
- }
- }
- /**
- * Compile an element and return a nodeLinkFn.
- *
- * @param {Element} el
- * @param {Object} options
- * @return {Function|null}
- */
- function compileElement (el, options) {
- var linkFn, tag, component
- // check custom element component, but only on non-root
- if (!el.__vue__) {
- tag = el.tagName.toLowerCase()
- component =
- tag.indexOf('-') > 0 &&
- options.components[tag]
- if (component) {
- el.setAttribute(config.prefix + 'component', tag)
- }
- }
- if (component || el.hasAttributes()) {
- // check terminal direcitves
- linkFn = checkTerminalDirectives(el, options)
- // if not terminal, build normal link function
- if (!linkFn) {
- var directives = collectDirectives(el, options)
- linkFn = directives.length
- ? makeDirectivesLinkFn(directives)
- : null
- }
- }
- // if the element is a textarea, we need to interpolate
- // its content on initial render.
- if (el.tagName === 'TEXTAREA') {
- var realLinkFn = linkFn
- linkFn = function (vm, el) {
- el.value = vm.$interpolate(el.value)
- if (realLinkFn) realLinkFn(vm, el)
- }
- linkFn.terminal = true
- }
- return linkFn
- }
- /**
- * Build a multi-directive link function.
- *
- * @param {Array} directives
- * @return {Function} directivesLinkFn
- */
- function makeDirectivesLinkFn (directives) {
- return function directivesLinkFn (vm, el) {
- // reverse apply because it's sorted low to high
- var i = directives.length
- var dir, j, k
- while (i--) {
- dir = directives[i]
- if (dir._link) {
- // custom link fn
- dir._link(vm, el)
- } else {
- k = dir.descriptors.length
- for (j = 0; j < k; j++) {
- vm._bindDir(dir.name, el,
- dir.descriptors[j], dir.def)
- }
- }
- }
- }
- }
- /**
- * Compile a textNode and return a nodeLinkFn.
- *
- * @param {TextNode} node
- * @param {Object} options
- * @return {Function|null} textNodeLinkFn
- */
- function compileTextNode (node, options) {
- var tokens = textParser.parse(node.nodeValue)
- if (!tokens) {
- return null
- }
- var frag = document.createDocumentFragment()
- var dirs = options.directives
- var el, token, value
- for (var i = 0, l = tokens.length; i < l; i++) {
- token = tokens[i]
- value = token.value
- if (token.tag) {
- if (token.oneTime) {
- el = document.createTextNode(value)
- } else {
- if (token.html) {
- el = document.createComment('v-html')
- token.type = 'html'
- token.def = dirs.html
- token.descriptor = dirParser.parse(value)[0]
- } else if (token.partial) {
- el = document.createComment('v-partial')
- token.type = 'partial'
- token.def = dirs.partial
- token.descriptor = dirParser.parse(value)[0]
- } else {
- // IE will clean up empty textNodes during
- // frag.cloneNode(true), so we have to give it
- // something here...
- el = document.createTextNode(' ')
- token.type = 'text'
- token.def = dirs.text
- token.descriptor = dirParser.parse(value)[0]
- }
- }
- } else {
- el = document.createTextNode(value)
- }
- frag.appendChild(el)
- }
- return makeTextNodeLinkFn(tokens, frag, options)
- }
- /**
- * Build a function that processes a textNode.
- *
- * @param {Array<Object>} tokens
- * @param {DocumentFragment} frag
- */
- function makeTextNodeLinkFn (tokens, frag) {
- return function textNodeLinkFn (vm, el) {
- var fragClone = frag.cloneNode(true)
- var childNodes = _.toArray(fragClone.childNodes)
- var token, value, node
- for (var i = 0, l = tokens.length; i < l; i++) {
- token = tokens[i]
- value = token.value
- if (token.tag) {
- node = childNodes[i]
- if (token.oneTime) {
- value = vm.$eval(value)
- if (token.html) {
- _.replace(node, templateParser.parse(value, true))
- } else {
- node.nodeValue = value
- }
- } else {
- vm._bindDir(token.type, node,
- token.descriptor, token.def)
- }
- }
- }
- _.replace(el, fragClone)
- }
- }
- /**
- * Compile a node list and return a childLinkFn.
- *
- * @param {NodeList} nodeList
- * @param {Object} options
- * @return {Function|undefined}
- */
- function compileNodeList (nodeList, options) {
- var linkFns = []
- var nodeLinkFn, childLinkFn, node
- for (var i = 0, l = nodeList.length; i < l; i++) {
- node = nodeList[i]
- nodeLinkFn = compileNode(node, options)
- childLinkFn =
- (!nodeLinkFn || !nodeLinkFn.terminal) &&
- node.hasChildNodes()
- ? compileNodeList(node.childNodes, options)
- : null
- linkFns.push(nodeLinkFn, childLinkFn)
- }
- return linkFns.length
- ? makeChildLinkFn(linkFns)
- : null
- }
- /**
- * Make a child link function for a node's childNodes.
- *
- * @param {Array<Function>} linkFns
- * @return {Function} childLinkFn
- */
- function makeChildLinkFn (linkFns) {
- return function childLinkFn (vm, nodes) {
- // stablize nodes
- nodes = _.toArray(nodes)
- var node, nodeLinkFn, childrenLinkFn
- for (var i = 0, n = 0, l = linkFns.length; i < l; n++) {
- node = nodes[n]
- nodeLinkFn = linkFns[i++]
- childrenLinkFn = linkFns[i++]
- if (nodeLinkFn) {
- nodeLinkFn(vm, node)
- }
- if (childrenLinkFn) {
- childrenLinkFn(vm, node.childNodes)
- }
- }
- }
- }
- /**
- * Compile param attributes on a root element and return
- * a paramAttributes link function.
- *
- * @param {Element} el
- * @param {Array} attrs
- * @param {Object} options
- * @return {Function} paramsLinkFn
- */
- function compileParamAttributes (el, attrs, options) {
- var params = []
- var i = attrs.length
- var name, value, param
- while (i--) {
- name = attrs[i]
- value = el.getAttribute(name)
- if (value !== null) {
- param = {
- name: name,
- value: value
- }
- var tokens = textParser.parse(value)
- if (tokens) {
- el.removeAttribute(name)
- if (tokens.length > 1) {
- _.warn(
- 'Invalid param attribute binding: "' +
- name + '="' + value + '"' +
- '\nDon\'t mix binding tags with plain text ' +
- 'in param attribute bindings.'
- )
- continue
- } else {
- param.dynamic = true
- param.value = tokens[0].value
- }
- }
- params.push(param)
- }
- }
- return makeParamsLinkFn(params, options)
- }
- /**
- * Build a function that applies param attributes to a vm.
- *
- * @param {Array} params
- * @param {Object} options
- * @return {Function} paramsLinkFn
- */
- function makeParamsLinkFn (params, options) {
- var def = options.directives['with']
- return function paramsLinkFn (vm, el) {
- var i = params.length
- var param
- while (i--) {
- param = params[i]
- if (param.dynamic) {
- // dynamic param attribtues are bound as v-with.
- // we can directly duck the descriptor here beacuse
- // param attributes cannot use expressions or
- // filters.
- vm._bindDir('with', el, {
- arg: param.name,
- expression: param.value
- }, def)
- } else {
- // just set once
- vm.$set(param.name, param.value)
- }
- }
- }
- }
- /**
- * Check an element for terminal directives in fixed order.
- * If it finds one, return a terminal link function.
- *
- * @param {Element} el
- * @param {Object} options
- * @return {Function} terminalLinkFn
- */
- var terminalDirectives = [
- 'repeat',
- 'if',
- 'component'
- ]
- function skip () {}
- skip.terminal = true
- function checkTerminalDirectives (el, options) {
- if (_.attr(el, 'pre') !== null) {
- return skip
- }
- var value, dirName
- /* jshint boss: true */
- for (var i = 0; i < 3; i++) {
- dirName = terminalDirectives[i]
- if (value = _.attr(el, dirName)) {
- return makeTeriminalLinkFn(el, dirName, value, options)
- }
- }
- }
- /**
- * Build a link function for a terminal directive.
- *
- * @param {Element} el
- * @param {String} dirName
- * @param {String} value
- * @param {Object} options
- * @return {Function} terminalLinkFn
- */
- function makeTeriminalLinkFn (el, dirName, value, options) {
- var descriptor = dirParser.parse(value)[0]
- var def = options.directives[dirName]
- var terminalLinkFn = function (vm, el) {
- vm._bindDir(dirName, el, descriptor, def)
- }
- terminalLinkFn.terminal = true
- return terminalLinkFn
- }
- /**
- * Collect the directives on an element.
- *
- * @param {Element} el
- * @param {Object} options
- * @return {Array}
- */
- function collectDirectives (el, options) {
- var attrs = _.toArray(el.attributes)
- var i = attrs.length
- var dirs = []
- var attr, attrName, dir, dirName, dirDef
- while (i--) {
- attr = attrs[i]
- attrName = attr.name
- if (attrName.indexOf(config.prefix) === 0) {
- dirName = attrName.slice(config.prefix.length)
- dirDef = options.directives[dirName]
- _.assertAsset(dirDef, 'directive', dirName)
- if (dirDef) {
- if (dirName !== 'cloak') {
- el.removeAttribute(attrName)
- }
- dirs.push({
- name: dirName,
- descriptors: dirParser.parse(attr.value),
- def: dirDef
- })
- }
- } else if (config.interpolate) {
- dir = collectAttrDirective(el, attrName, attr.value,
- options)
- if (dir) {
- dirs.push(dir)
- }
- }
- }
- // sort by priority, LOW to HIGH
- dirs.sort(directiveComparator)
- return dirs
- }
- /**
- * Check an attribute for potential dynamic bindings,
- * and return a directive object.
- *
- * @param {Element} el
- * @param {String} name
- * @param {String} value
- * @param {Object} options
- * @return {Object}
- */
- function collectAttrDirective (el, name, value, options) {
- var tokens = textParser.parse(value)
- if (tokens) {
- var def = options.directives.attr
- var i = tokens.length
- var allOneTime = true
- while (i--) {
- var token = tokens[i]
- if (token.tag && !token.oneTime) {
- allOneTime = false
- }
- }
- return {
- def: def,
- _link: allOneTime
- ? function (vm, el) {
- el.setAttribute(name, vm.$interpolate(value))
- }
- : function (vm, el) {
- var value = textParser.tokensToExp(tokens, vm)
- var desc = dirParser.parse(name + ':' + value)[0]
- vm._bindDir('attr', el, desc, def)
- }
- }
- }
- }
- /**
- * Directive priority sort comparator
- *
- * @param {Object} a
- * @param {Object} b
- */
- function directiveComparator (a, b) {
- a = a.def.priority || 0
- b = b.def.priority || 0
- return a > b ? 1 : -1
- }
|