28d12e554f994cf0da34e753324d91645a8724f2fd50ef3ea6a2c1992541892e9d4bbdaca4f0bf72986694757923e5a71b8c6d17f03f0d42b8d751a44f3e0c 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /* @flow */
  2. import config from 'core/config'
  3. import { addHandler, addProp, getBindingAttr } from 'compiler/helpers'
  4. import { genComponentModel, genAssignmentCode } from 'compiler/directives/model'
  5. let warn
  6. // in some cases, the event used has to be determined at runtime
  7. // so we used some reserved tokens during compile.
  8. export const RANGE_TOKEN = '__r'
  9. export const CHECKBOX_RADIO_TOKEN = '__c'
  10. export default function model (
  11. el: ASTElement,
  12. dir: ASTDirective,
  13. _warn: Function
  14. ): ?boolean {
  15. warn = _warn
  16. const value = dir.value
  17. const modifiers = dir.modifiers
  18. const tag = el.tag
  19. const type = el.attrsMap.type
  20. if (process.env.NODE_ENV !== 'production') {
  21. // inputs with type="file" are read only and setting the input's
  22. // value will throw an error.
  23. if (tag === 'input' && type === 'file') {
  24. warn(
  25. `<${el.tag} v-model="${value}" type="file">:\n` +
  26. `File inputs are read only. Use a v-on:change listener instead.`,
  27. el.rawAttrsMap['v-model']
  28. )
  29. }
  30. }
  31. if (el.component) {
  32. genComponentModel(el, value, modifiers)
  33. // component v-model doesn't need extra runtime
  34. return false
  35. } else if (tag === 'select') {
  36. genSelect(el, value, modifiers)
  37. } else if (tag === 'input' && type === 'checkbox') {
  38. genCheckboxModel(el, value, modifiers)
  39. } else if (tag === 'input' && type === 'radio') {
  40. genRadioModel(el, value, modifiers)
  41. } else if (tag === 'input' || tag === 'textarea') {
  42. genDefaultModel(el, value, modifiers)
  43. } else if (!config.isReservedTag(tag)) {
  44. genComponentModel(el, value, modifiers)
  45. // component v-model doesn't need extra runtime
  46. return false
  47. } else if (process.env.NODE_ENV !== 'production') {
  48. warn(
  49. `<${el.tag} v-model="${value}">: ` +
  50. `v-model is not supported on this element type. ` +
  51. 'If you are working with contenteditable, it\'s recommended to ' +
  52. 'wrap a library dedicated for that purpose inside a custom component.',
  53. el.rawAttrsMap['v-model']
  54. )
  55. }
  56. // ensure runtime directive metadata
  57. return true
  58. }
  59. function genCheckboxModel (
  60. el: ASTElement,
  61. value: string,
  62. modifiers: ?ASTModifiers
  63. ) {
  64. const number = modifiers && modifiers.number
  65. const valueBinding = getBindingAttr(el, 'value') || 'null'
  66. const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
  67. const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
  68. addProp(el, 'checked',
  69. `Array.isArray(${value})` +
  70. `?_i(${value},${valueBinding})>-1` + (
  71. trueValueBinding === 'true'
  72. ? `:(${value})`
  73. : `:_q(${value},${trueValueBinding})`
  74. )
  75. )
  76. addHandler(el, 'change',
  77. `var $$a=${value},` +
  78. '$$el=$event.target,' +
  79. `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
  80. 'if(Array.isArray($$a)){' +
  81. `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
  82. '$$i=_i($$a,$$v);' +
  83. `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` +
  84. `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` +
  85. `}else{${genAssignmentCode(value, '$$c')}}`,
  86. null, true
  87. )
  88. }
  89. function genRadioModel (
  90. el: ASTElement,
  91. value: string,
  92. modifiers: ?ASTModifiers
  93. ) {
  94. const number = modifiers && modifiers.number
  95. let valueBinding = getBindingAttr(el, 'value') || 'null'
  96. valueBinding = number ? `_n(${valueBinding})` : valueBinding
  97. addProp(el, 'checked', `_q(${value},${valueBinding})`)
  98. addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
  99. }
  100. function genSelect (
  101. el: ASTElement,
  102. value: string,
  103. modifiers: ?ASTModifiers
  104. ) {
  105. const number = modifiers && modifiers.number
  106. const selectedVal = `Array.prototype.filter` +
  107. `.call($event.target.options,function(o){return o.selected})` +
  108. `.map(function(o){var val = "_value" in o ? o._value : o.value;` +
  109. `return ${number ? '_n(val)' : 'val'}})`
  110. const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'
  111. let code = `var $$selectedVal = ${selectedVal};`
  112. code = `${code} ${genAssignmentCode(value, assignment)}`
  113. addHandler(el, 'change', code, null, true)
  114. }
  115. function genDefaultModel (
  116. el: ASTElement,
  117. value: string,
  118. modifiers: ?ASTModifiers
  119. ): ?boolean {
  120. const type = el.attrsMap.type
  121. // warn if v-bind:value conflicts with v-model
  122. // except for inputs with v-bind:type
  123. if (process.env.NODE_ENV !== 'production') {
  124. const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']
  125. const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']
  126. if (value && !typeBinding) {
  127. const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'
  128. warn(
  129. `${binding}="${value}" conflicts with v-model on the same element ` +
  130. 'because the latter already expands to a value binding internally',
  131. el.rawAttrsMap[binding]
  132. )
  133. }
  134. }
  135. const { lazy, number, trim } = modifiers || {}
  136. const needCompositionGuard = !lazy && type !== 'range'
  137. const event = lazy
  138. ? 'change'
  139. : type === 'range'
  140. ? RANGE_TOKEN
  141. : 'input'
  142. let valueExpression = '$event.target.value'
  143. if (trim) {
  144. valueExpression = `$event.target.value.trim()`
  145. }
  146. if (number) {
  147. valueExpression = `_n(${valueExpression})`
  148. }
  149. let code = genAssignmentCode(value, valueExpression)
  150. if (needCompositionGuard) {
  151. code = `if($event.target.composing)return;${code}`
  152. }
  153. addProp(el, 'value', `(${value})`)
  154. addHandler(el, event, code, null, true)
  155. if (trim || number) {
  156. addHandler(el, 'blur', '$forceUpdate()')
  157. }
  158. }