Skip to content

Commit 201c46d

Browse files
authored
fix(ssr): render correct initial selected state for select with v-model (#7432)
close #7392
1 parent 3decc57 commit 201c46d

File tree

3 files changed

+92
-3
lines changed

3 files changed

+92
-3
lines changed

packages/compiler-ssr/__tests__/ssrVModel.spec.ts

+38
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,44 @@ describe('ssr: v-model', () => {
3333
`)
3434
})
3535

36+
test('<select v-model>', () => {
37+
expect(
38+
compileWithWrapper(
39+
`<select v-model="model"><option value="1"></option></select>`
40+
).code
41+
).toMatchInlineSnapshot(`
42+
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
43+
44+
return function ssrRender(_ctx, _push, _parent, _attrs) {
45+
_push(\`<div\${
46+
_ssrRenderAttrs(_attrs)
47+
}><select><option value=\\"1\\"\${
48+
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
49+
? _ssrLooseContain(_ctx.model, \\"1\\")
50+
: _ssrLooseEqual(_ctx.model, \\"1\\"))) ? \\" selected\\" : \\"\\"
51+
}></option></select></div>\`)
52+
}"
53+
`)
54+
55+
expect(
56+
compileWithWrapper(
57+
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`
58+
).code
59+
).toMatchInlineSnapshot(`
60+
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
61+
62+
return function ssrRender(_ctx, _push, _parent, _attrs) {
63+
_push(\`<div\${
64+
_ssrRenderAttrs(_attrs)
65+
}><select multiple><option value=\\"1\\" selected></option><option value=\\"2\\"\${
66+
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
67+
? _ssrLooseContain(_ctx.model, \\"2\\")
68+
: _ssrLooseEqual(_ctx.model, \\"2\\"))) ? \\" selected\\" : \\"\\"
69+
}></option></select></div>\`)
70+
}"
71+
`)
72+
})
73+
3674
test('<input type="radio">', () => {
3775
expect(
3876
compileWithWrapper(`<input type="radio" value="foo" v-model="bar">`).code

packages/compiler-ssr/src/transforms/ssrVModel.ts

+30-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
import {
1919
SSR_LOOSE_EQUAL,
2020
SSR_LOOSE_CONTAIN,
21-
SSR_RENDER_DYNAMIC_MODEL
21+
SSR_RENDER_DYNAMIC_MODEL,
22+
SSR_INCLUDE_BOOLEAN_ATTR
2223
} from '../runtimeHelpers'
2324
import { DirectiveTransformResult } from 'packages/compiler-core/src/transform'
2425

@@ -129,8 +130,34 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
129130
checkDuplicatedValue()
130131
node.children = [createInterpolation(model, model.loc)]
131132
} else if (node.tag === 'select') {
132-
// NOOP
133-
// select relies on client-side directive to set initial selected state.
133+
node.children.forEach(option => {
134+
if (option.type === NodeTypes.ELEMENT) {
135+
const plainNode = option as PlainElementNode
136+
if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
137+
const value = findValueBinding(plainNode)
138+
plainNode.ssrCodegenNode!.elements.push(
139+
createConditionalExpression(
140+
createCallExpression(context.helper(SSR_INCLUDE_BOOLEAN_ATTR), [
141+
createConditionalExpression(
142+
createCallExpression(`Array.isArray`, [model]),
143+
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
144+
model,
145+
value
146+
]),
147+
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
148+
model,
149+
value
150+
])
151+
)
152+
]),
153+
createSimpleExpression(' selected', true),
154+
createSimpleExpression('', true),
155+
false /* no newline */
156+
)
157+
)
158+
}
159+
}
160+
})
134161
} else {
135162
context.onError(
136163
createDOMCompilerError(

packages/server-renderer/__tests__/ssrDirectives.spec.ts

+24
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,30 @@ describe('ssr: directives', () => {
107107
).toBe(`<input type="radio">`)
108108
})
109109

110+
test('select', async () => {
111+
expect(
112+
await renderToString(
113+
createApp({
114+
data: () => ({ model: 1 }),
115+
template: `<select v-model="model"><option value="0"></option><option value="1"></option></select>`
116+
})
117+
)
118+
).toBe(
119+
`<select><option value="0"></option><option value="1" selected></option></select>`
120+
)
121+
122+
expect(
123+
await renderToString(
124+
createApp({
125+
data: () => ({ model: [0, 1] }),
126+
template: `<select multiple v-model="model"><option value="0"></option><option value="1"></option></select>`
127+
})
128+
)
129+
).toBe(
130+
`<select multiple><option value="0" selected></option><option value="1" selected></option></select>`
131+
)
132+
})
133+
110134
test('checkbox', async () => {
111135
expect(
112136
await renderToString(

0 commit comments

Comments
 (0)