mirror of
https://gitee.com/vuejs/vue.git
synced 2024-11-30 11:07:51 +08:00
support object looseEqual in v-model (fix #3673)
This commit is contained in:
parent
d6a7568891
commit
56960b5fbc
@ -89,6 +89,10 @@ declare interface Component {
|
||||
_n: (value: string) => number | string;
|
||||
// empty vnode
|
||||
_e: () => VNode;
|
||||
// loose equal
|
||||
_q: (a: mixed, b: mixed) => boolean;
|
||||
// loose indexOf
|
||||
_i: (arr: Array<mixed>, val: mixed) => number;
|
||||
// resolveFilter
|
||||
_f: (id: string) => Function;
|
||||
// renderList
|
||||
|
@ -5,7 +5,7 @@ import VNode, { emptyVNode, cloneVNode, cloneVNodes } from '../vdom/vnode'
|
||||
import { normalizeChildren } from '../vdom/helpers'
|
||||
import {
|
||||
warn, formatComponentName, bind, isObject, toObject,
|
||||
nextTick, resolveAsset, _toString, toNumber
|
||||
nextTick, resolveAsset, _toString, toNumber, looseEqual, looseIndexOf
|
||||
} from '../util/index'
|
||||
|
||||
import { createElement } from '../vdom/create-element'
|
||||
@ -94,6 +94,10 @@ export function renderMixin (Vue: Class<Component>) {
|
||||
Vue.prototype._n = toNumber
|
||||
// empty vnode
|
||||
Vue.prototype._e = emptyVNode
|
||||
// loose equal
|
||||
Vue.prototype._q = looseEqual
|
||||
// loose indexOf
|
||||
Vue.prototype._i = looseIndexOf
|
||||
|
||||
// render static tree by index
|
||||
Vue.prototype._m = function renderStatic (
|
||||
|
@ -40,8 +40,8 @@ function genCheckboxModel (el: ASTElement, value: string) {
|
||||
const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
|
||||
addProp(el, 'checked',
|
||||
`Array.isArray(${value})` +
|
||||
`?(${value}).indexOf(${valueBinding})>-1` +
|
||||
`:(${value})===(${trueValueBinding})`
|
||||
`?_i(${value},${valueBinding})>-1` +
|
||||
`:_q(${value},${trueValueBinding})`
|
||||
)
|
||||
addHandler(el, 'change',
|
||||
`var $$a=${value},` +
|
||||
@ -49,7 +49,7 @@ function genCheckboxModel (el: ASTElement, value: string) {
|
||||
`$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
|
||||
'if(Array.isArray($$a)){' +
|
||||
`var $$v=${valueBinding},` +
|
||||
'$$i=$$a.indexOf($$v);' +
|
||||
'$$i=_i($$a,$$v);' +
|
||||
`if($$c){$$i<0&&(${value}=$$a.concat($$v))}` +
|
||||
`else{$$i>-1&&(${value}=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}` +
|
||||
`}else{${value}=$$c}`,
|
||||
@ -67,7 +67,7 @@ function genRadioModel (el: ASTElement, value: string) {
|
||||
)
|
||||
}
|
||||
const valueBinding = getBindingAttr(el, 'value') || 'null'
|
||||
addProp(el, 'checked', `(${value})===(${valueBinding})`)
|
||||
addProp(el, 'checked', `_q(${value},${valueBinding})`)
|
||||
addHandler(el, 'change', `${value}=${valueBinding}`, null, true)
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
* properties to Elements.
|
||||
*/
|
||||
|
||||
import { looseEqual, looseIndexOf } from 'shared/util'
|
||||
import { warn } from 'core/util/index'
|
||||
import { isAndroid, isIE9 } from 'web/util/index'
|
||||
|
||||
@ -78,12 +79,12 @@ function setSelected (el, binding, vm) {
|
||||
for (let i = 0, l = el.options.length; i < l; i++) {
|
||||
option = el.options[i]
|
||||
if (isMultiple) {
|
||||
selected = value.indexOf(getValue(option)) > -1
|
||||
selected = looseIndexOf(value, getValue(option)) > -1
|
||||
if (option.selected !== selected) {
|
||||
option.selected = selected
|
||||
}
|
||||
} else {
|
||||
if (getValue(option) === value) {
|
||||
if (looseEqual(getValue(option), value)) {
|
||||
if (el.selectedIndex !== i) {
|
||||
el.selectedIndex = i
|
||||
}
|
||||
@ -98,7 +99,7 @@ function setSelected (el, binding, vm) {
|
||||
|
||||
function hasNoMatchingOption (value, options) {
|
||||
for (let i = 0, l = options.length; i < l; i++) {
|
||||
if (getValue(options[i]) === value) {
|
||||
if (looseEqual(getValue(options[i]), value)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ export function extend (to: Object, _from: ?Object): Object {
|
||||
* Objects from primitive values when we know the value
|
||||
* is a JSON-compliant type.
|
||||
*/
|
||||
export function isObject (obj: any): boolean {
|
||||
export function isObject (obj: mixed): boolean {
|
||||
return obj !== null && typeof obj === 'object'
|
||||
}
|
||||
|
||||
@ -197,3 +197,24 @@ export function genStaticKeys (modules: Array<ModuleOptions>): string {
|
||||
return keys.concat(m.staticKeys || [])
|
||||
}, []).join(',')
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two values are loosely equal - that is,
|
||||
* if they are plain objects, do they have the same shape?
|
||||
*/
|
||||
export function looseEqual (a: mixed, b: mixed): boolean {
|
||||
/* eslint-disable eqeqeq */
|
||||
return a == b || (
|
||||
isObject(a) && isObject(b)
|
||||
? JSON.stringify(a) === JSON.stringify(b)
|
||||
: false
|
||||
)
|
||||
/* eslint-enable eqeqeq */
|
||||
}
|
||||
|
||||
export function looseIndexOf (arr: Array<mixed>, val: mixed): number {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (looseEqual(arr[i], val)) return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
@ -105,6 +105,34 @@ describe('Directive v-model checkbox', () => {
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('bind to Array value with value bindings (object loose equal)', done => {
|
||||
const vm = new Vue({
|
||||
data: {
|
||||
test: [{ a: 1 }]
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<input type="checkbox" v-model="test" :value="{ a: 1 }">
|
||||
<input type="checkbox" v-model="test" :value="{ a: 2 }">
|
||||
</div>
|
||||
`
|
||||
}).$mount()
|
||||
document.body.appendChild(vm.$el)
|
||||
expect(vm.$el.children[0].checked).toBe(true)
|
||||
expect(vm.$el.children[1].checked).toBe(false)
|
||||
vm.$el.children[0].click()
|
||||
expect(vm.test.length).toBe(0)
|
||||
vm.$el.children[1].click()
|
||||
expect(vm.test).toEqual([{ a: 2 }])
|
||||
vm.$el.children[0].click()
|
||||
expect(vm.test).toEqual([{ a: 2 }, { a: 1 }])
|
||||
vm.test = [{ a: 1 }]
|
||||
waitForUpdate(() => {
|
||||
expect(vm.$el.children[0].checked).toBe(true)
|
||||
expect(vm.$el.children[1].checked).toBe(false)
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('warn inline checked', () => {
|
||||
const vm = new Vue({
|
||||
template: `<input type="checkbox" v-model="test" checked>`,
|
||||
|
@ -57,6 +57,34 @@ describe('Directive v-model radio', () => {
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('should respect value bindings (object loose equal)', done => {
|
||||
const vm = new Vue({
|
||||
data: {
|
||||
test: { a: 1 }
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<input type="radio" :value="{ a: 1 }" v-model="test" name="test">
|
||||
<input type="radio" :value="{ a: 2 }" v-model="test" name="test">
|
||||
</div>
|
||||
`
|
||||
}).$mount()
|
||||
document.body.appendChild(vm.$el)
|
||||
expect(vm.$el.children[0].checked).toBe(true)
|
||||
expect(vm.$el.children[1].checked).toBe(false)
|
||||
vm.test = { a: 2 }
|
||||
waitForUpdate(() => {
|
||||
expect(vm.$el.children[0].checked).toBe(false)
|
||||
expect(vm.$el.children[1].checked).toBe(true)
|
||||
vm.$el.children[0].click()
|
||||
expect(vm.$el.children[0].checked).toBe(true)
|
||||
expect(vm.$el.children[1].checked).toBe(false)
|
||||
expect(vm.test).toEqual({ a: 1 })
|
||||
}).then(() => {
|
||||
document.body.removeChild(vm.$el)
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('warn inline checked', () => {
|
||||
const vm = new Vue({
|
||||
template: `<input v-model="test" type="radio" value="1" checked>`,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Vue from 'vue'
|
||||
import { looseEqual } from 'shared/util'
|
||||
|
||||
/**
|
||||
* setting <select>'s value in IE9 doesn't work
|
||||
@ -8,15 +9,19 @@ function updateSelect (el, value) {
|
||||
var options = el.options
|
||||
var i = options.length
|
||||
while (i--) {
|
||||
/* eslint-disable eqeqeq */
|
||||
if (options[i].value == value) {
|
||||
/* eslint-enable eqeqeq */
|
||||
if (looseEqual(getValue(options[i]), value)) {
|
||||
options[i].selected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getValue (option) {
|
||||
return '_value' in option
|
||||
? option._value
|
||||
: option.value || option.text
|
||||
}
|
||||
|
||||
describe('Directive v-model select', () => {
|
||||
it('should work', done => {
|
||||
const vm = new Vue({
|
||||
@ -69,6 +74,34 @@ describe('Directive v-model select', () => {
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('should work with value bindings (object loose equal)', done => {
|
||||
const vm = new Vue({
|
||||
data: {
|
||||
test: { a: 2 }
|
||||
},
|
||||
template:
|
||||
'<select v-model="test">' +
|
||||
'<option value="1">a</option>' +
|
||||
'<option :value="{ a: 2 }">b</option>' +
|
||||
'<option :value="{ a: 3 }">c</option>' +
|
||||
'</select>'
|
||||
}).$mount()
|
||||
document.body.appendChild(vm.$el)
|
||||
expect(vm.$el.childNodes[1].selected).toBe(true)
|
||||
vm.test = { a: 3 }
|
||||
waitForUpdate(function () {
|
||||
expect(vm.$el.childNodes[2].selected).toBe(true)
|
||||
|
||||
updateSelect(vm.$el, '1')
|
||||
triggerEvent(vm.$el, 'change')
|
||||
expect(vm.test).toBe('1')
|
||||
|
||||
updateSelect(vm.$el, { a: 2 })
|
||||
triggerEvent(vm.$el, 'change')
|
||||
expect(vm.test).toEqual({ a: 2 })
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('should work with v-for', done => {
|
||||
const vm = new Vue({
|
||||
data: {
|
||||
|
Loading…
Reference in New Issue
Block a user