fix(ssr): properly render <select v-model> initial state

fix #6986
This commit is contained in:
Evan You 2017-11-03 15:30:47 -04:00
parent e80104eb63
commit e1657fd7ce
4 changed files with 138 additions and 2 deletions

View File

@ -1,5 +1,7 @@
import show from './show'
import model from './model'
export default {
show
show,
model
}

View File

@ -0,0 +1,44 @@
/* @flow */
import { looseEqual, looseIndexOf } from 'shared/util'
// this is only applied for <select v-model> because it is the only edge case
// that must be done at runtime instead of compile time.
export default function model (node: VNodeWithData, dir: VNodeDirective) {
if (!node.children) return
const value = dir.value
const isMultiple = node.data.attrs && node.data.attrs.multiple
for (let i = 0, l = node.children.length; i < l; i++) {
const option = node.children[i]
if (option.tag === 'option') {
if (isMultiple) {
const selected =
Array.isArray(value) &&
(looseIndexOf(value, getValue(option)) > -1)
if (selected) {
setSelected(option)
}
} else {
if (looseEqual(value, getValue(option))) {
setSelected(option)
return
}
}
}
}
}
function getValue (option) {
const data = option.data || {}
return (
(data.attrs && data.attrs.value) ||
(data.domProps && data.domProps.value) ||
(option.children && option.children[0] && option.children[0].text)
)
}
function setSelected (option) {
const data = option.data || (option.data = {})
const attrs = data.attrs || (data.attrs = {})
attrs.selected = ''
}

View File

@ -113,7 +113,8 @@ function isUnOptimizableTree (node: ASTNode): boolean {
return (
isBuiltInTag(node.tag) || // built-in (slot, component)
!isPlatformReservedTag(node.tag) || // custom component
!!node.component // "is" component
!!node.component || // "is" component
isSelectWithModel(node) // <select v-model> requires runtime inspection
)
}
@ -126,3 +127,14 @@ function hasCustomDirective (node: ASTNode): ?boolean {
node.directives.some(d => !isBuiltInDir(d.name))
)
}
// <select v-model> cannot be optimized because it requires a runtime check
// to determine proper selected option
function isSelectWithModel (node: ASTNode): ?boolean {
return (
node.type === 1 &&
node.tag === 'select' &&
node.directives &&
node.directives.some(d => d.name === 'model')
)
}

View File

@ -1034,6 +1034,84 @@ describe('SSR: renderToString', () => {
done()
})
})
it('render v-model with <select> (value binding)', done => {
renderVmWithOptions({
data: {
selected: 2,
options: [
{ id: 1, label: 'one' },
{ id: 2, label: 'two' }
]
},
template: `
<div>
<select v-model="selected">
<option v-for="o in options" :value="o.id">{{ o.label }}</option>
</select>
</div>
`
}, result => {
expect(result).toContain(
'<select>' +
'<option value="1">one</option>' +
'<option selected="selected" value="2">two</option>' +
'</select>'
)
done()
})
})
it('render v-model with <select> (static value)', done => {
renderVmWithOptions({
data: {
selected: 2
},
template: `
<div>
<select v-model="selected">
<option value="1">one</option>
<option value="2">two</option>
</select>
</div>
`
}, result => {
expect(result).toContain(
'<select>' +
'<option value="1">one</option> ' +
'<option value="2" selected="selected">two</option>' +
'</select>'
)
done()
})
})
it('render v-model with <select> (text as value)', done => {
renderVmWithOptions({
data: {
selected: 2,
options: [
{ id: 1, label: 'one' },
{ id: 2, label: 'two' }
]
},
template: `
<div>
<select v-model="selected">
<option v-for="o in options">{{ o.id }}</option>
</select>
</div>
`
}, result => {
expect(result).toContain(
'<select>' +
'<option>1</option>' +
'<option selected="selected">2</option>' +
'</select>'
)
done()
})
})
})
function renderVmWithOptions (options, cb) {