Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions flow/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ declare type ModuleOptions = {
staticKeys?: Array<string>; // AST properties to be considered static
}

declare type ASTModifiers = { [key: string]: boolean }

declare type ASTElementHandler = {
value: string;
modifiers: ?{ [key: string]: true };
modifiers: ?ASTModifiers;
}

declare type ASTElementHandlers = {
Expand All @@ -55,7 +57,7 @@ declare type ASTDirective = {
rawName: string;
value: string;
arg: ?string;
modifiers: ?{ [key: string]: true };
modifiers: ?ASTModifiers;
}

declare type ASTNode = ASTElement | ASTText | ASTExpression
Expand Down
2 changes: 1 addition & 1 deletion flow/vnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ declare type VNodeDirective = {
value?: any;
oldValue?: any;
arg?: string;
modifiers?: { [key: string]: boolean };
modifiers?: ASTModifiers;
def?: Object;
}
4 changes: 2 additions & 2 deletions src/compiler/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function addDirective (
rawName: string,
value: string,
arg: ?string,
modifiers: ?{ [key: string]: true }
modifiers: ?ASTModifiers
) {
(el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers })
}
Expand All @@ -36,7 +36,7 @@ export function addHandler (
el: ASTElement,
name: string,
value: string,
modifiers: ?{ [key: string]: true },
modifiers: ?ASTModifiers,
important: ?boolean
) {
// check capture modifier
Expand Down
46 changes: 32 additions & 14 deletions src/platforms/web/compiler/directives/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,23 @@ export default function model (
}
}
if (tag === 'select') {
genSelect(el, value)
genSelect(el, value, modifiers)
} else if (tag === 'input' && type === 'checkbox') {
genCheckboxModel(el, value)
genCheckboxModel(el, value, modifiers)
} else if (tag === 'input' && type === 'radio') {
genRadioModel(el, value)
genRadioModel(el, value, modifiers)
} else {
genDefaultModel(el, value, modifiers)
}
// ensure runtime directive metadata
return true
}

function genCheckboxModel (el: ASTElement, value: string) {
function genCheckboxModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
) {
if (process.env.NODE_ENV !== 'production' &&
el.attrsMap.checked != null) {
warn(
Expand All @@ -47,6 +51,7 @@ function genCheckboxModel (el: ASTElement, value: string) {
'Declare initial values in the component\'s data option instead.'
)
}
const number = modifiers && modifiers.number
const valueBinding = getBindingAttr(el, 'value') || 'null'
const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
Expand All @@ -60,7 +65,7 @@ function genCheckboxModel (el: ASTElement, value: string) {
'$$el=$event.target,' +
`$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
'if(Array.isArray($$a)){' +
`var $$v=${valueBinding},` +
`var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
'$$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)))}` +
Expand All @@ -69,7 +74,11 @@ function genCheckboxModel (el: ASTElement, value: string) {
)
}

function genRadioModel (el: ASTElement, value: string) {
function genRadioModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
) {
if (process.env.NODE_ENV !== 'production' &&
el.attrsMap.checked != null) {
warn(
Expand All @@ -78,15 +87,17 @@ function genRadioModel (el: ASTElement, value: string) {
'Declare initial values in the component\'s data option instead.'
)
}
const valueBinding = getBindingAttr(el, 'value') || 'null'
const number = modifiers && modifiers.number
let valueBinding = getBindingAttr(el, 'value') || 'null'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a ternary could be used here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated :)

valueBinding = number ? `_n(${valueBinding})` : valueBinding
addProp(el, 'checked', `_q(${value},${valueBinding})`)
addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
}

function genDefaultModel (
el: ASTElement,
value: string,
modifiers: ?Object
modifiers: ?ASTModifiers
): ?boolean {
if (process.env.NODE_ENV !== 'production') {
if (el.tag === 'input' && el.attrsMap.value) {
Expand All @@ -111,12 +122,13 @@ function genDefaultModel (
const needCompositionGuard = !lazy && type !== 'range'
const isNative = el.tag === 'input' || el.tag === 'textarea'

const valueExpression = isNative
let valueExpression = isNative
? `$event.target.value${trim ? '.trim()' : ''}`
: `$event`
let code = number || type === 'number'
? genAssignmentCode(value, `_n(${valueExpression})`)
: genAssignmentCode(value, valueExpression)
valueExpression = number || type === 'number'
? `_n(${valueExpression})`
: valueExpression
let code = genAssignmentCode(value, valueExpression)
if (isNative && needCompositionGuard) {
code = `if($event.target.composing)return;${code}`
}
Expand All @@ -133,14 +145,20 @@ function genDefaultModel (
addHandler(el, event, code, null, true)
}

function genSelect (el: ASTElement, value: string) {
function genSelect (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
) {
if (process.env.NODE_ENV !== 'production') {
el.children.some(checkOptionWarning)
}

const number = modifiers && modifiers.number
const assignment = `Array.prototype.filter` +
`.call($event.target.options,function(o){return o.selected})` +
`.map(function(o){return "_value" in o ? o._value : o.value})` +
`.map(function(o){var val = "_value" in o ? o._value : o.value;` +
`return ${number ? '_n(val)' : 'val'}})` +
(el.attrsMap.multiple == null ? '[0]' : '')

const code = genAssignmentCode(value, assignment)
Expand Down
26 changes: 26 additions & 0 deletions test/unit/features/directives/model-checkbox.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,32 @@ describe('Directive v-model checkbox', () => {
}).then(done)
})

it('.number modifier', () => {
const vm = new Vue({
data: {
test: [],
check: true
},
template: `
<div>
<input type="checkbox" v-model.number="test" value="1">
<input type="checkbox" v-model="test" value="2">
<input type="checkbox" v-model.number="check">
</div>
`
}).$mount()
document.body.appendChild(vm.$el)
var checkboxInputs = vm.$el.getElementsByTagName('input')
expect(checkboxInputs[0].checked).toBe(false)
expect(checkboxInputs[1].checked).toBe(false)
expect(checkboxInputs[2].checked).toBe(true)
checkboxInputs[0].click()
checkboxInputs[1].click()
checkboxInputs[2].click()
expect(vm.test).toEqual([1, '2'])
expect(vm.check).toEqual(false)
})

it('warn inline checked', () => {
const vm = new Vue({
template: `<input type="checkbox" v-model="test" checked>`,
Expand Down
21 changes: 21 additions & 0 deletions test/unit/features/directives/model-radio.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,27 @@ describe('Directive v-model radio', () => {
}).then(done)
})

it('.number modifier', () => {
const vm = new Vue({
data: {
test: 1
},
template: `
<div>
<input type="radio" value="1" v-model="test" name="test">
<input type="radio" value="2" v-model.number="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.$el.children[1].click()
expect(vm.$el.children[0].checked).toBe(false)
expect(vm.$el.children[1].checked).toBe(true)
expect(vm.test).toBe(2)
})

it('warn inline checked', () => {
const vm = new Vue({
template: `<input v-model="test" type="radio" value="1" checked>`,
Expand Down
18 changes: 18 additions & 0 deletions test/unit/features/directives/model-select.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,24 @@ describe('Directive v-model select', () => {
}).then(done)
})

it('.number modifier', () => {
const vm = new Vue({
data: {
test: 2
},
template:
'<select v-model.number="test">' +
'<option value="1">a</option>' +
'<option :value="2">b</option>' +
' <option :value="3">c</option>' +
'</select>'
}).$mount()
document.body.appendChild(vm.$el)
updateSelect(vm.$el, '1')
triggerEvent(vm.$el, 'change')
expect(vm.test).toBe(1)
})

it('should warn inline selected', () => {
const vm = new Vue({
data: {
Expand Down