model.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /**
  2. * Not type checking this file because flow doesn't like attaching
  3. * properties to Elements.
  4. */
  5. import { isTextInputType } from 'web/util/element'
  6. import { looseEqual, looseIndexOf } from 'shared/util'
  7. import { warn, isAndroid, isIE9, isIE, isEdge } from 'core/util/index'
  8. /* istanbul ignore if */
  9. if (isIE9) {
  10. // http://www.matts411.com/post/internet-explorer-9-oninput/
  11. document.addEventListener('selectionchange', () => {
  12. const el = document.activeElement
  13. if (el && el.vmodel) {
  14. trigger(el, 'input')
  15. }
  16. })
  17. }
  18. export default {
  19. inserted (el, binding, vnode) {
  20. if (vnode.tag === 'select') {
  21. setSelected(el, binding, vnode.context)
  22. el._vOptions = [].map.call(el.options, getValue)
  23. } else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
  24. el._vModifiers = binding.modifiers
  25. if (!binding.modifiers.lazy) {
  26. // Safari < 10.2 & UIWebView doesn't fire compositionend when
  27. // switching focus before confirming composition choice
  28. // this also fixes the issue where some browsers e.g. iOS Chrome
  29. // fires "change" instead of "input" on autocomplete.
  30. el.addEventListener('change', onCompositionEnd)
  31. if (!isAndroid) {
  32. el.addEventListener('compositionstart', onCompositionStart)
  33. el.addEventListener('compositionend', onCompositionEnd)
  34. }
  35. /* istanbul ignore if */
  36. if (isIE9) {
  37. el.vmodel = true
  38. }
  39. }
  40. }
  41. },
  42. componentUpdated (el, binding, vnode) {
  43. if (vnode.tag === 'select') {
  44. setSelected(el, binding, vnode.context)
  45. // in case the options rendered by v-for have changed,
  46. // it's possible that the value is out-of-sync with the rendered options.
  47. // detect such cases and filter out values that no longer has a matching
  48. // option in the DOM.
  49. const prevOptions = el._vOptions
  50. const curOptions = el._vOptions = [].map.call(el.options, getValue)
  51. if (curOptions.some((o, i) => !looseEqual(o, prevOptions[i]))) {
  52. // trigger change event if
  53. // no matching option found for at least one value
  54. const needReset = el.multiple
  55. ? binding.value.some(v => hasNoMatchingOption(v, curOptions))
  56. : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions)
  57. if (needReset) {
  58. trigger(el, 'change')
  59. }
  60. }
  61. }
  62. }
  63. }
  64. function setSelected (el, binding, vm) {
  65. actuallySetSelected(el, binding, vm)
  66. /* istanbul ignore if */
  67. if (isIE || isEdge) {
  68. setTimeout(() => {
  69. actuallySetSelected(el, binding, vm)
  70. }, 0)
  71. }
  72. }
  73. function actuallySetSelected (el, binding, vm) {
  74. const value = binding.value
  75. const isMultiple = el.multiple
  76. if (isMultiple && !Array.isArray(value)) {
  77. process.env.NODE_ENV !== 'production' && warn(
  78. `<select multiple v-model="${binding.expression}"> ` +
  79. `expects an Array value for its binding, but got ${
  80. Object.prototype.toString.call(value).slice(8, -1)
  81. }`,
  82. vm
  83. )
  84. return
  85. }
  86. let selected, option
  87. for (let i = 0, l = el.options.length; i < l; i++) {
  88. option = el.options[i]
  89. if (isMultiple) {
  90. selected = looseIndexOf(value, getValue(option)) > -1
  91. if (option.selected !== selected) {
  92. option.selected = selected
  93. }
  94. } else {
  95. if (looseEqual(getValue(option), value)) {
  96. if (el.selectedIndex !== i) {
  97. el.selectedIndex = i
  98. }
  99. return
  100. }
  101. }
  102. }
  103. if (!isMultiple) {
  104. el.selectedIndex = -1
  105. }
  106. }
  107. function hasNoMatchingOption (value, options) {
  108. return options.every(o => !looseEqual(o, value))
  109. }
  110. function getValue (option) {
  111. return '_value' in option
  112. ? option._value
  113. : option.value
  114. }
  115. function onCompositionStart (e) {
  116. e.target.composing = true
  117. }
  118. function onCompositionEnd (e) {
  119. // prevent triggering an input event for no reason
  120. if (!e.target.composing) return
  121. e.target.composing = false
  122. trigger(e.target, 'input')
  123. }
  124. function trigger (el, type) {
  125. const e = document.createEvent('HTMLEvents')
  126. e.initEvent(type, true, true)
  127. el.dispatchEvent(e)
  128. }