model.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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. )
  28. }
  29. }
  30. if (el.component) {
  31. genComponentModel(el, value, modifiers)
  32. // component v-model doesn't need extra runtime
  33. return false
  34. } else if (tag === 'select') {
  35. genSelect(el, value, modifiers)
  36. } else if (tag === 'input' && type === 'checkbox') {
  37. genCheckboxModel(el, value, modifiers)
  38. } else if (tag === 'input' && type === 'radio') {
  39. genRadioModel(el, value, modifiers)
  40. } else if (tag === 'input' || tag === 'textarea') {
  41. genDefaultModel(el, value, modifiers)
  42. } else if (!config.isReservedTag(tag)) {
  43. genComponentModel(el, value, modifiers)
  44. // component v-model doesn't need extra runtime
  45. return false
  46. } else if (process.env.NODE_ENV !== 'production') {
  47. warn(
  48. `<${el.tag} v-model="${value}">: ` +
  49. `v-model is not supported on this element type. ` +
  50. 'If you are working with contenteditable, it\'s recommended to ' +
  51. 'wrap a library dedicated for that purpose inside a custom component.'
  52. )
  53. }
  54. // ensure runtime directive metadata
  55. return true
  56. }
  57. function genCheckboxModel (
  58. el: ASTElement,
  59. value: string,
  60. modifiers: ?ASTModifiers
  61. ) {
  62. const number = modifiers && modifiers.number
  63. const valueBinding = getBindingAttr(el, 'value') || 'null'
  64. const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
  65. const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
  66. addProp(el, 'checked',
  67. `Array.isArray(${value})` +
  68. `?_i(${value},${valueBinding})>-1` + (
  69. trueValueBinding === 'true'
  70. ? `:(${value})`
  71. : `:_q(${value},${trueValueBinding})`
  72. )
  73. )
  74. addHandler(el, 'change',
  75. `var $$a=${value},` +
  76. '$$el=$event.target,' +
  77. `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
  78. 'if(Array.isArray($$a)){' +
  79. `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
  80. '$$i=_i($$a,$$v);' +
  81. `if($$el.checked){$$i<0&&(${value}=$$a.concat([$$v]))}` +
  82. `else{$$i>-1&&(${value}=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}` +
  83. `}else{${genAssignmentCode(value, '$$c')}}`,
  84. null, true
  85. )
  86. }
  87. function genRadioModel (
  88. el: ASTElement,
  89. value: string,
  90. modifiers: ?ASTModifiers
  91. ) {
  92. const number = modifiers && modifiers.number
  93. let valueBinding = getBindingAttr(el, 'value') || 'null'
  94. valueBinding = number ? `_n(${valueBinding})` : valueBinding
  95. addProp(el, 'checked', `_q(${value},${valueBinding})`)
  96. addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
  97. }
  98. function genSelect (
  99. el: ASTElement,
  100. value: string,
  101. modifiers: ?ASTModifiers
  102. ) {
  103. const number = modifiers && modifiers.number
  104. const selectedVal = `Array.prototype.filter` +
  105. `.call($event.target.options,function(o){return o.selected})` +
  106. `.map(function(o){var val = "_value" in o ? o._value : o.value;` +
  107. `return ${number ? '_n(val)' : 'val'}})`
  108. const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'
  109. let code = `var $$selectedVal = ${selectedVal};`
  110. code = `${code} ${genAssignmentCode(value, assignment)}`
  111. addHandler(el, 'change', code, null, true)
  112. }
  113. function genDefaultModel (
  114. el: ASTElement,
  115. value: string,
  116. modifiers: ?ASTModifiers
  117. ): ?boolean {
  118. const type = el.attrsMap.type
  119. // warn if v-bind:value conflicts with v-model
  120. if (process.env.NODE_ENV !== 'production') {
  121. const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']
  122. if (value) {
  123. const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'
  124. warn(
  125. `${binding}="${value}" conflicts with v-model on the same element ` +
  126. 'because the latter already expands to a value binding internally'
  127. )
  128. }
  129. }
  130. const { lazy, number, trim } = modifiers || {}
  131. const needCompositionGuard = !lazy && type !== 'range'
  132. const event = lazy
  133. ? 'change'
  134. : type === 'range'
  135. ? RANGE_TOKEN
  136. : 'input'
  137. let valueExpression = '$event.target.value'
  138. if (trim) {
  139. valueExpression = `$event.target.value.trim()`
  140. }
  141. if (number) {
  142. valueExpression = `_n(${valueExpression})`
  143. }
  144. let code = genAssignmentCode(value, valueExpression)
  145. if (needCompositionGuard) {
  146. code = `if($event.target.composing)return;${code}`
  147. }
  148. addProp(el, 'value', `(${value})`)
  149. addHandler(el, event, code, null, true)
  150. if (trim || number) {
  151. addHandler(el, 'blur', '$forceUpdate()')
  152. }
  153. }