| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- 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
- }
|