Evan You
7 years ago
7 changed files with 254 additions and 43 deletions
@ -1,7 +1,9 @@ |
|||
import klass from './class' |
|||
import style from './style' |
|||
import model from './model' |
|||
|
|||
export default [ |
|||
klass, |
|||
style |
|||
style, |
|||
model |
|||
] |
|||
|
@ -0,0 +1,77 @@ |
|||
/* @flow */ |
|||
|
|||
/** |
|||
* Expand input[v-model] with dyanmic type bindings into v-if-else chains |
|||
* Turn this: |
|||
* <input v-model="data[type]" :type="type"> |
|||
* into this: |
|||
* <input v-if="type === 'checkbox'" type="checkbox" v-model="data[type]"> |
|||
* <input v-else-if="type === 'radio'" type="radio" v-model="data[type]"> |
|||
* <input v-else :type="type" v-model="data[type]"> |
|||
*/ |
|||
|
|||
import { |
|||
getBindingAttr, |
|||
getAndRemoveAttr |
|||
} from 'compiler/helpers' |
|||
|
|||
import { |
|||
processFor, |
|||
processElement, |
|||
addIfCondition, |
|||
createASTElement |
|||
} from 'compiler/parser/index' |
|||
|
|||
function preTransformNode (el: ASTElement, options: CompilerOptions) { |
|||
if (el.tag === 'input') { |
|||
const map = el.attrsMap |
|||
if (map['v-model'] && (map['v-bind:type'] || map[':type'])) { |
|||
const typeBinding: any = getBindingAttr(el, 'type') |
|||
const ifCondition = getAndRemoveAttr(el, 'v-if', true) |
|||
// 1. checkbox
|
|||
const branch0 = cloneASTElement(el) |
|||
// process for on the main node
|
|||
processFor(branch0) |
|||
addRawAttr(branch0, 'type', 'checkbox') |
|||
processElement(branch0, options) |
|||
branch0.processed = true // prevent it from double-processed
|
|||
branch0.if = `type==='checkbox'` + (ifCondition ? `&&(${ifCondition})` : ``) |
|||
addIfCondition(branch0, { |
|||
exp: branch0.if, |
|||
block: branch0 |
|||
}) |
|||
// 2. add radio else-if condition
|
|||
const branch1 = cloneASTElement(el) |
|||
getAndRemoveAttr(branch1, 'v-for', true) |
|||
addRawAttr(branch1, 'type', 'radio') |
|||
processElement(branch1, options) |
|||
addIfCondition(branch0, { |
|||
exp: `type==='radio'` + (ifCondition ? `&&(${ifCondition})` : ``), |
|||
block: branch1 |
|||
}) |
|||
// 3. other
|
|||
const branch2 = cloneASTElement(el) |
|||
getAndRemoveAttr(branch2, 'v-for', true) |
|||
addRawAttr(branch2, ':type', typeBinding) |
|||
processElement(branch2, options) |
|||
addIfCondition(branch0, { |
|||
exp: ifCondition, |
|||
block: branch2 |
|||
}) |
|||
return branch0 |
|||
} |
|||
} |
|||
} |
|||
|
|||
function cloneASTElement (el) { |
|||
return createASTElement(el.tag, el.attrsList.slice(), el.parent) |
|||
} |
|||
|
|||
function addRawAttr (el, name, value) { |
|||
el.attrsMap[name] = value |
|||
el.attrsList.push({ name, value }) |
|||
} |
|||
|
|||
export default { |
|||
preTransformNode |
|||
} |
@ -1,14 +1,122 @@ |
|||
import Vue from 'vue' |
|||
|
|||
describe('Directive v-model dynamic input type', () => { |
|||
it('should warn', function () { |
|||
new Vue({ |
|||
it('should work', done => { |
|||
const vm = new Vue({ |
|||
data: { |
|||
type: 'text', |
|||
text: 'hi' |
|||
type: null, |
|||
test: 'b' |
|||
}, |
|||
template: `<input :type="type" v-model="text">` |
|||
template: `<input :type="type" v-model="test">` |
|||
}).$mount() |
|||
expect(`v-model does not support dynamic input types`).toHaveBeenWarned() |
|||
document.body.appendChild(vm.$el) |
|||
|
|||
// test text
|
|||
assertInputWorks(vm).then(done) |
|||
}) |
|||
|
|||
it('with v-if', done => { |
|||
const vm = new Vue({ |
|||
data: { |
|||
ok: true, |
|||
type: null, |
|||
test: 'b' |
|||
}, |
|||
template: `<input v-if="ok" :type="type" v-model="test"><div v-else>haha</div>` |
|||
}).$mount() |
|||
document.body.appendChild(vm.$el) |
|||
|
|||
const chain = assertInputWorks(vm).then(() => { |
|||
vm.ok = false |
|||
}).then(() => { |
|||
expect(vm.$el.textContent).toBe('haha') |
|||
}).then(() => { |
|||
// reset
|
|||
vm.ok = true |
|||
vm.type = null |
|||
vm.test = 'b' |
|||
}) |
|||
|
|||
assertInputWorks(vm, chain).then(done) |
|||
}) |
|||
|
|||
it('with v-for', done => { |
|||
const vm = new Vue({ |
|||
data: { |
|||
data: { |
|||
text: 'foo', |
|||
checkbox: true |
|||
}, |
|||
types: ['text', 'checkbox'] |
|||
}, |
|||
template: `<div>
|
|||
<input v-for="type in types" :type="type" v-model="data[type]"> |
|||
</div>` |
|||
}).$mount() |
|||
document.body.appendChild(vm.$el) |
|||
|
|||
let el1 = vm.$el.children[0] |
|||
expect(el1.type).toBe('text') |
|||
expect(el1.value).toBe('foo') |
|||
el1.value = 'bar' |
|||
triggerEvent(el1, 'input') |
|||
expect(vm.data.text).toBe('bar') |
|||
|
|||
let el2 = vm.$el.children[1] |
|||
expect(el2.type).toBe('checkbox') |
|||
expect(el2.checked).toBe(true) |
|||
el2.click() |
|||
expect(vm.data.checkbox).toBe(false) |
|||
|
|||
// now in reverse!
|
|||
vm.types.reverse() |
|||
waitForUpdate(() => { |
|||
el1 = vm.$el.children[0] |
|||
expect(el1.type).toBe('checkbox') |
|||
expect(el1.checked).toBe(false) |
|||
el1.click() |
|||
expect(vm.data.checkbox).toBe(true) |
|||
|
|||
el2 = vm.$el.children[1] |
|||
expect(el2.type).toBe('text') |
|||
expect(el2.value).toBe('bar') |
|||
el2.value = 'foo' |
|||
triggerEvent(el2, 'input') |
|||
expect(vm.data.text).toBe('foo') |
|||
}).then(done) |
|||
}) |
|||
}) |
|||
|
|||
function assertInputWorks (vm, chain) { |
|||
if (!chain) chain = waitForUpdate() |
|||
chain.then(() => { |
|||
expect(vm.$el.value).toBe('b') |
|||
vm.test = 'a' |
|||
}).then(() => { |
|||
expect(vm.$el.value).toBe('a') |
|||
vm.$el.value = 'c' |
|||
triggerEvent(vm.$el, 'input') |
|||
expect(vm.test).toBe('c') |
|||
}).then(() => { |
|||
// change it to password
|
|||
vm.type = 'password' |
|||
vm.test = 'b' |
|||
}).then(() => { |
|||
expect(vm.$el.type).toBe('password') |
|||
expect(vm.$el.value).toBe('b') |
|||
vm.$el.value = 'c' |
|||
triggerEvent(vm.$el, 'input') |
|||
expect(vm.test).toBe('c') |
|||
}).then(() => { |
|||
// change it to checkbox...
|
|||
vm.type = 'checkbox' |
|||
}).then(() => { |
|||
expect(vm.$el.type).toBe('checkbox') |
|||
expect(vm.$el.checked).toBe(true) |
|||
}).then(() => { |
|||
vm.$el.click() |
|||
expect(vm.$el.checked).toBe(false) |
|||
expect(vm.test).toBe(false) |
|||
}) |
|||
return chain |
|||
} |
|||
|
Loading…
Reference in new issue