| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- /**
- * Not type checking this file because flow doesn't like attaching
- * properties to Elements.
- */
- import { looseEqual, looseIndexOf } from 'shared/util'
- import { warn, nextTick, isAndroid, isIE9, isIE, isEdge } from 'core/util/index'
- const modelableTagRE = /^input|select|textarea|vue-component-[0-9]+(-[0-9a-zA-Z_\-]*)?$/
- /* istanbul ignore if */
- if (isIE9) {
- // http://www.matts411.com/post/internet-explorer-9-oninput/
- document.addEventListener('selectionchange', () => {
- const el = document.activeElement
- if (el && el.vmodel) {
- trigger(el, 'input')
- }
- })
- }
- export default {
- bind (el, binding, vnode) {
- if (process.env.NODE_ENV !== 'production') {
- if (!modelableTagRE.test(vnode.tag)) {
- warn(
- `v-model is not supported on element type: <${vnode.tag}>. ` +
- 'If you are working with contenteditable, it\'s recommended to ' +
- 'wrap a library dedicated for that purpose inside a custom component.',
- vnode.context
- )
- }
- }
- if (vnode.tag === 'select') {
- setSelected(el, binding, vnode.context)
- /* istanbul ignore if */
- if (isIE || isEdge) {
- nextTick(() => {
- setSelected(el, binding, vnode.context)
- })
- }
- } else if (vnode.tag === 'textarea' || el.type === 'text') {
- if (!isAndroid) {
- el.addEventListener('compositionstart', onCompositionStart)
- el.addEventListener('compositionend', onCompositionEnd)
- }
- /* istanbul ignore if */
- if (isIE9) {
- el.vmodel = true
- }
- }
- },
- componentUpdated (el, binding, vnode) {
- if (vnode.tag === 'select') {
- setSelected(el, binding, vnode.context)
- // in case the options rendered by v-for have changed,
- // it's possible that the value is out-of-sync with the rendered options.
- // detect such cases and filter out values that no longer has a matchig
- // option in the DOM.
- const needReset = el.multiple
- ? binding.value.some(v => hasNoMatchingOption(v, el.options))
- : hasNoMatchingOption(binding.value, el.options)
- if (needReset) {
- trigger(el, 'change')
- }
- }
- }
- }
- function setSelected (el, binding, vm) {
- const value = binding.value
- const isMultiple = el.multiple
- if (isMultiple && !Array.isArray(value)) {
- process.env.NODE_ENV !== 'production' && warn(
- `<select multiple v-model="${binding.expression}"> ` +
- `expects an Array value for its binding, but got ${
- Object.prototype.toString.call(value).slice(8, -1)
- }`,
- vm
- )
- return
- }
- let selected, option
- for (let i = 0, l = el.options.length; i < l; i++) {
- option = el.options[i]
- if (isMultiple) {
- selected = looseIndexOf(value, getValue(option)) > -1
- if (option.selected !== selected) {
- option.selected = selected
- }
- } else {
- if (looseEqual(getValue(option), value)) {
- if (el.selectedIndex !== i) {
- el.selectedIndex = i
- }
- return
- }
- }
- }
- if (!isMultiple) {
- el.selectedIndex = -1
- }
- }
- function hasNoMatchingOption (value, options) {
- for (let i = 0, l = options.length; i < l; i++) {
- if (looseEqual(getValue(options[i]), value)) {
- return false
- }
- }
- return true
- }
- function getValue (option) {
- return '_value' in option
- ? option._value
- : option.value
- }
- function onCompositionStart (e) {
- e.target.composing = true
- }
- function onCompositionEnd (e) {
- e.target.composing = false
- trigger(e.target, 'input')
- }
- function trigger (el, type) {
- const e = document.createEvent('HTMLEvents')
- e.initEvent(type, true, true)
- el.dispatchEvent(e)
- }
|