Skip to content

Commit

Permalink
fix(ssr): properly render <select v-model> initial state
Browse files Browse the repository at this point in the history
fix #6986
  • Loading branch information
yyx990803 committed Nov 3, 2017
1 parent e80104e commit e1657fd
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 2 deletions.
4 changes: 3 additions & 1 deletion src/platforms/web/server/directives/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import show from './show'
import model from './model'

export default {
show
show,
model
}
44 changes: 44 additions & 0 deletions src/platforms/web/server/directives/model.js
Original file line number Diff line number Diff line change
@@ -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 = ''
}
14 changes: 13 additions & 1 deletion src/server/optimizing-compiler/optimizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}

Expand All @@ -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')
)
}
78 changes: 78 additions & 0 deletions test/ssr/ssr-string.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit e1657fd

Please sign in to comment.