dc650a59c22caece9aba6a73ef4f8e457e634a9387417822d35d22ba590334cf703bb30a8dafba4be75048216e587fcca710cd2cb37124186d1d84ce4ed17e 4.3 KB

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