var _ = require('../util') var dirParser = require('../parsers/directive') var propDef = require('../directives/internal/prop') var propBindingModes = require('../config')._propBindingModes var empty = {} // regexes var identRE = require('../parsers/path').identRE var settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/ /** * Compile props on a root element and return * a props link function. * * @param {Element|DocumentFragment} el * @param {Array} propOptions * @return {Function} propsLinkFn */ module.exports = function compileProps (el, propOptions) { var props = [] var names = Object.keys(propOptions) var i = names.length var options, name, attr, value, path, parsed, prop, isTitleBinding while (i--) { name = names[i] options = propOptions[name] || empty if (process.env.NODE_ENV !== 'production' && name === '$data') { _.warn('Do not use $data as prop.') continue } // props could contain dashes, which will be // interpreted as minus calculations by the parser // so we need to camelize the path here path = _.camelize(name) if (!identRE.test(path)) { process.env.NODE_ENV !== 'production' && _.warn( 'Invalid prop key: "' + name + '". Prop keys ' + 'must be valid identifiers.' ) continue } prop = { name: name, path: path, options: options, mode: propBindingModes.ONE_WAY } // IE title issues isTitleBinding = false if (name === 'title' && (el.getAttribute(':title') || el.getAttribute('v-bind:title'))) { isTitleBinding = true } // first check literal version attr = _.hyphenate(name) value = prop.raw = _.attr(el, attr) if (value === null || isTitleBinding) { // then check dynamic version if ((value = _.getBindAttr(el, attr)) === null) { if ((value = _.getBindAttr(el, attr + '.sync')) !== null) { prop.mode = propBindingModes.TWO_WAY } else if ((value = _.getBindAttr(el, attr + '.once')) !== null) { prop.mode = propBindingModes.ONE_TIME } } prop.raw = value if (value !== null) { parsed = dirParser.parse(value) value = parsed.expression prop.filters = parsed.filters // check binding type if (_.isLiteral(value)) { // for expressions containing literal numbers and // booleans, there's no need to setup a prop binding, // so we can optimize them as a one-time set. prop.optimizedLiteral = true } else { prop.dynamic = true // check non-settable path for two-way bindings if (process.env.NODE_ENV !== 'production' && prop.mode === propBindingModes.TWO_WAY && !settablePathRE.test(value)) { prop.mode = propBindingModes.ONE_WAY _.warn( 'Cannot bind two-way prop with non-settable ' + 'parent path: ' + value ) } } prop.parentPath = value // warn required two-way if ( process.env.NODE_ENV !== 'production' && options.twoWay && prop.mode !== propBindingModes.TWO_WAY ) { _.warn( 'Prop "' + name + '" expects a two-way binding type.' ) } } else if (options.required) { // warn missing required process.env.NODE_ENV !== 'production' && _.warn( 'Missing required prop: ' + name ) } } // push prop props.push(prop) } return makePropsLinkFn(props) } /** * Build a function that applies props to a vm. * * @param {Array} props * @return {Function} propsLinkFn */ function makePropsLinkFn (props) { return function propsLinkFn (vm, scope) { // store resolved props info vm._props = {} var i = props.length var prop, path, options, value, raw while (i--) { prop = props[i] raw = prop.raw path = prop.path options = prop.options vm._props[path] = prop if (raw === null) { // initialize absent prop _.initProp(vm, prop, getDefault(vm, options)) } else if (prop.dynamic) { // dynamic prop if (vm._context) { if (prop.mode === propBindingModes.ONE_TIME) { // one time binding value = (scope || vm._context).$get(prop.parentPath) _.initProp(vm, prop, value) } else { // dynamic binding vm._bindDir({ name: 'prop', def: propDef, prop: prop }, null, null, scope) // el, host, scope } } else { process.env.NODE_ENV !== 'production' && _.warn( 'Cannot bind dynamic prop on a root instance' + ' with no parent: ' + prop.name + '="' + raw + '"' ) } } else if (prop.optimizedLiteral) { // optimized literal, cast it and just set once raw = _.stripQuotes(raw) value = _.toBoolean(_.toNumber(raw)) _.initProp(vm, prop, value) } else { // string literal, but we need to cater for // Boolean props with no value value = options.type === Boolean && raw === '' ? true : raw _.initProp(vm, prop, value) } } } } /** * Get the default value of a prop. * * @param {Vue} vm * @param {Object} options * @return {*} */ function getDefault (vm, options) { // no default, return undefined if (!options.hasOwnProperty('default')) { // absent boolean value defaults to false return options.type === Boolean ? false : undefined } var def = options.default // warn against non-factory defaults for Object & Array if (_.isObject(def)) { process.env.NODE_ENV !== 'production' && _.warn( 'Object/Array as default prop values will be shared ' + 'across multiple instances. Use a factory function ' + 'to return the default value instead.' ) } // call factory function for non-Function types return typeof def === 'function' && options.type !== Function ? def.call(vm) : def }