| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- var _ = require('../util')
- var textParser = require('../parsers/text')
- var propDef = require('../directives/prop')
- var propBindingModes = require('../config')._propBindingModes
- // regexes
- var identRE = require('../parsers/path').identRE
- var dataAttrRE = /^data-/
- var settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/
- var literalValueRE = /^(true|false)$|^\d.*/
- /**
- * Compile props on a root element and return
- * a props link function.
- *
- * @param {Element|DocumentFragment} el
- * @param {Array} propOptions
- * @return {Function} propsLinkFn
- */
- // TODO: 1.0.0 we can just loop through el.attributes and
- // check for prop- prefixes.
- module.exports = function compileProps (el, propOptions) {
- var props = []
- var i = propOptions.length
- var options, name, attr, value, path, prop, literal, single
- while (i--) {
- options = propOptions[i]
- name = options.name
- if (process.env.NODE_ENV !== 'production') {
- if (name === '$data') {
- _.deprecation.DATA_AS_PROP()
- }
- }
- // 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.replace(dataAttrRE, ''))
- if (!identRE.test(path)) {
- process.env.NODE_ENV !== 'production' && _.warn(
- 'Invalid prop key: "' + name + '". Prop keys ' +
- 'must be valid identifiers.'
- )
- continue
- }
- attr = _.hyphenate(name)
- value = el.getAttribute(attr)
- if (value === null) {
- value = el.getAttribute('data-' + attr)
- if (value !== null) {
- attr = 'data-' + attr
- if (process.env.NODE_ENV !== 'production') {
- _.deprecation.DATA_PROPS(attr, value)
- }
- }
- }
- // create a prop descriptor
- prop = {
- name: name,
- raw: value,
- path: path,
- options: options,
- mode: propBindingModes.ONE_WAY
- }
- if (value !== null) {
- // important so that this doesn't get compiled
- // again as a normal attribute binding
- el.removeAttribute(attr)
- var tokens = textParser.parse(value)
- if (tokens) {
- if (process.env.NODE_ENV !== 'production') {
- _.deprecation.PROPS(attr, value)
- }
- prop.dynamic = true
- prop.parentPath = textParser.tokensToExp(tokens)
- // check prop binding type.
- single = tokens.length === 1
- literal = literalValueRE.test(prop.parentPath)
- // one time: {{* prop}}
- if (literal || (single && tokens[0].oneTime)) {
- prop.mode = propBindingModes.ONE_TIME
- } else if (
- !literal &&
- (single && tokens[0].twoWay)
- ) {
- if (settablePathRE.test(prop.parentPath)) {
- prop.mode = propBindingModes.TWO_WAY
- } else {
- process.env.NODE_ENV !== 'production' && _.warn(
- 'Cannot bind two-way prop with non-settable ' +
- 'parent path: ' + prop.parentPath
- )
- }
- }
- }
- } else {
- // new syntax
- attr = 'bind-' + attr
- value = prop.raw = el.getAttribute(attr)
- if (value !== null) {
- // mark it so we know this is a bind
- prop.bindSyntax = true
- el.removeAttribute(attr)
- value = value.trim()
- // check binding type
- if (literalValueRE.test(value)) {
- prop.mode = propBindingModes.ONE_TIME
- } else {
- prop.dynamic = true
- if (value.charAt(0) === '*') {
- prop.mode = propBindingModes.ONE_TIME
- value = value.slice(1).trim()
- } else if (value.charAt(0) === '@') {
- value = value.slice(1).trim()
- if (settablePathRE.test(value)) {
- prop.mode = propBindingModes.TWO_WAY
- } else {
- process.env.NODE_ENV !== 'production' && _.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.'
- )
- }
- // warn missing required
- if (value === null && options && options.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
- while (i--) {
- prop = props[i]
- path = prop.path
- vm._props[path] = prop
- options = prop.options
- if (prop.raw === null) {
- // initialize absent prop
- _.initProp(vm, prop, getDefault(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('prop', null, prop, propDef, null, scope)
- }
- } else {
- process.env.NODE_ENV !== 'production' && _.warn(
- 'Cannot bind dynamic prop on a root instance' +
- ' with no parent: ' + prop.name + '="' +
- prop.raw + '"'
- )
- }
- } else {
- // literal, cast it and just set once
- var raw = prop.raw
- if (options.type === Boolean && raw === '') {
- value = true
- } else if (raw.trim()) {
- value = _.toBoolean(_.toNumber(raw))
- if (process.env.NODE_ENV !== 'production' &&
- !prop.bindSyntax &&
- value !== raw) {
- _.deprecation.PROP_CASTING(prop.name, prop.raw)
- }
- } else {
- value = raw
- }
- _.initProp(vm, prop, value)
- }
- }
- }
- }
- /**
- * Get the default value of a prop.
- *
- * @param {Object} options
- * @return {*}
- */
- function getDefault (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()
- : def
- }
|