compile-props.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. var _ = require('../util')
  2. var dirParser = require('../parsers/directive')
  3. var propDef = require('../directives/internal/prop')
  4. var propBindingModes = require('../config')._propBindingModes
  5. var empty = {}
  6. // regexes
  7. var identRE = require('../parsers/path').identRE
  8. var settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/
  9. /**
  10. * Compile props on a root element and return
  11. * a props link function.
  12. *
  13. * @param {Element|DocumentFragment} el
  14. * @param {Array} propOptions
  15. * @return {Function} propsLinkFn
  16. */
  17. module.exports = function compileProps (el, propOptions) {
  18. var props = []
  19. var names = Object.keys(propOptions)
  20. var i = names.length
  21. var options, name, attr, value, path, parsed, prop
  22. while (i--) {
  23. name = names[i]
  24. options = propOptions[name] || empty
  25. if (process.env.NODE_ENV !== 'production' && name === '$data') {
  26. _.warn('Do not use $data as prop.')
  27. continue
  28. }
  29. // props could contain dashes, which will be
  30. // interpreted as minus calculations by the parser
  31. // so we need to camelize the path here
  32. path = _.camelize(name)
  33. if (!identRE.test(path)) {
  34. process.env.NODE_ENV !== 'production' && _.warn(
  35. 'Invalid prop key: "' + name + '". Prop keys ' +
  36. 'must be valid identifiers.'
  37. )
  38. continue
  39. }
  40. prop = {
  41. name: name,
  42. path: path,
  43. options: options,
  44. mode: propBindingModes.ONE_WAY,
  45. raw: null
  46. }
  47. attr = _.hyphenate(name)
  48. // first check dynamic version
  49. if ((value = _.getBindAttr(el, attr)) === null) {
  50. if ((value = _.getBindAttr(el, attr + '.sync')) !== null) {
  51. prop.mode = propBindingModes.TWO_WAY
  52. } else if ((value = _.getBindAttr(el, attr + '.once')) !== null) {
  53. prop.mode = propBindingModes.ONE_TIME
  54. }
  55. }
  56. if (value !== null) {
  57. // has dynamic binding!
  58. prop.raw = value
  59. parsed = dirParser.parse(value)
  60. value = parsed.expression
  61. prop.filters = parsed.filters
  62. // check binding type
  63. if (_.isLiteral(value)) {
  64. // for expressions containing literal numbers and
  65. // booleans, there's no need to setup a prop binding,
  66. // so we can optimize them as a one-time set.
  67. prop.optimizedLiteral = true
  68. } else {
  69. prop.dynamic = true
  70. // check non-settable path for two-way bindings
  71. if (process.env.NODE_ENV !== 'production' &&
  72. prop.mode === propBindingModes.TWO_WAY &&
  73. !settablePathRE.test(value)) {
  74. prop.mode = propBindingModes.ONE_WAY
  75. _.warn(
  76. 'Cannot bind two-way prop with non-settable ' +
  77. 'parent path: ' + value
  78. )
  79. }
  80. }
  81. prop.parentPath = value
  82. // warn required two-way
  83. if (
  84. process.env.NODE_ENV !== 'production' &&
  85. options.twoWay &&
  86. prop.mode !== propBindingModes.TWO_WAY
  87. ) {
  88. _.warn(
  89. 'Prop "' + name + '" expects a two-way binding type.'
  90. )
  91. }
  92. } else if ((value = _.attr(el, attr)) !== null) {
  93. // has literal binding!
  94. prop.raw = value
  95. } else if (options.required) {
  96. // warn missing required
  97. process.env.NODE_ENV !== 'production' && _.warn(
  98. 'Missing required prop: ' + name
  99. )
  100. }
  101. // push prop
  102. props.push(prop)
  103. }
  104. return makePropsLinkFn(props)
  105. }
  106. /**
  107. * Build a function that applies props to a vm.
  108. *
  109. * @param {Array} props
  110. * @return {Function} propsLinkFn
  111. */
  112. function makePropsLinkFn (props) {
  113. return function propsLinkFn (vm, scope) {
  114. // store resolved props info
  115. vm._props = {}
  116. var i = props.length
  117. var prop, path, options, value, raw
  118. while (i--) {
  119. prop = props[i]
  120. raw = prop.raw
  121. path = prop.path
  122. options = prop.options
  123. vm._props[path] = prop
  124. if (raw === null) {
  125. // initialize absent prop
  126. _.initProp(vm, prop, getDefault(vm, options))
  127. } else if (prop.dynamic) {
  128. // dynamic prop
  129. if (vm._context) {
  130. if (prop.mode === propBindingModes.ONE_TIME) {
  131. // one time binding
  132. value = (scope || vm._context).$get(prop.parentPath)
  133. _.initProp(vm, prop, value)
  134. } else {
  135. // dynamic binding
  136. vm._bindDir({
  137. name: 'prop',
  138. def: propDef,
  139. prop: prop
  140. }, null, null, scope) // el, host, scope
  141. }
  142. } else {
  143. process.env.NODE_ENV !== 'production' && _.warn(
  144. 'Cannot bind dynamic prop on a root instance' +
  145. ' with no parent: ' + prop.name + '="' +
  146. raw + '"'
  147. )
  148. }
  149. } else if (prop.optimizedLiteral) {
  150. // optimized literal, cast it and just set once
  151. var stripped = _.stripQuotes(raw)
  152. value = stripped === raw
  153. ? _.toBoolean(_.toNumber(raw))
  154. : stripped
  155. _.initProp(vm, prop, value)
  156. } else {
  157. // string literal, but we need to cater for
  158. // Boolean props with no value
  159. value = options.type === Boolean && raw === ''
  160. ? true
  161. : raw
  162. _.initProp(vm, prop, value)
  163. }
  164. }
  165. }
  166. }
  167. /**
  168. * Get the default value of a prop.
  169. *
  170. * @param {Vue} vm
  171. * @param {Object} options
  172. * @return {*}
  173. */
  174. function getDefault (vm, options) {
  175. // no default, return undefined
  176. if (!_.hasOwn(options, 'default')) {
  177. // absent boolean value defaults to false
  178. return options.type === Boolean
  179. ? false
  180. : undefined
  181. }
  182. var def = options.default
  183. // warn against non-factory defaults for Object & Array
  184. if (_.isObject(def)) {
  185. process.env.NODE_ENV !== 'production' && _.warn(
  186. 'Object/Array as default prop values will be shared ' +
  187. 'across multiple instances. Use a factory function ' +
  188. 'to return the default value instead.'
  189. )
  190. }
  191. // call factory function for non-Function types
  192. return typeof def === 'function' && options.type !== Function
  193. ? def.call(vm)
  194. : def
  195. }