Skip to content

Commit 1552095

Browse files
authored
fix(ssr): handle initial selected state for select with v-model + v-for/v-if option (#13487)
close #13486
1 parent f3479aa commit 1552095

File tree

2 files changed

+140
-15
lines changed

2 files changed

+140
-15
lines changed

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

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,132 @@ describe('ssr: v-model', () => {
166166
_push(\`</optgroup></select></div>\`)
167167
}"
168168
`)
169+
170+
expect(
171+
compileWithWrapper(`
172+
<select multiple v-model="model">
173+
<optgroup>
174+
<option v-for="item in items" :value="item">{{item}}</option>
175+
</optgroup>
176+
</select>`).code,
177+
).toMatchInlineSnapshot(`
178+
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
179+
180+
return function ssrRender(_ctx, _push, _parent, _attrs) {
181+
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
182+
_ssrRenderList(_ctx.items, (item) => {
183+
_push(\`<option\${
184+
_ssrRenderAttr("value", item)
185+
}\${
186+
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
187+
? _ssrLooseContain(_ctx.model, item)
188+
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
189+
}>\${
190+
_ssrInterpolate(item)
191+
}</option>\`)
192+
})
193+
_push(\`<!--]--></optgroup></select></div>\`)
194+
}"
195+
`)
196+
197+
expect(
198+
compileWithWrapper(`
199+
<select multiple v-model="model">
200+
<optgroup>
201+
<option v-if="true" :value="item">{{item}}</option>
202+
</optgroup>
203+
</select>`).code,
204+
).toMatchInlineSnapshot(`
205+
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")
206+
207+
return function ssrRender(_ctx, _push, _parent, _attrs) {
208+
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
209+
if (true) {
210+
_push(\`<option\${
211+
_ssrRenderAttr("value", _ctx.item)
212+
}\${
213+
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
214+
? _ssrLooseContain(_ctx.model, _ctx.item)
215+
: _ssrLooseEqual(_ctx.model, _ctx.item))) ? " selected" : ""
216+
}>\${
217+
_ssrInterpolate(_ctx.item)
218+
}</option>\`)
219+
} else {
220+
_push(\`<!---->\`)
221+
}
222+
_push(\`</optgroup></select></div>\`)
223+
}"
224+
`)
225+
226+
expect(
227+
compileWithWrapper(`
228+
<select multiple v-model="model">
229+
<optgroup>
230+
<template v-if="ok">
231+
<option v-for="item in items" :value="item">{{item}}</option>
232+
</template>
233+
</optgroup>
234+
</select>`).code,
235+
).toMatchInlineSnapshot(`
236+
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
237+
238+
return function ssrRender(_ctx, _push, _parent, _attrs) {
239+
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
240+
if (_ctx.ok) {
241+
_push(\`<!--[-->\`)
242+
_ssrRenderList(_ctx.items, (item) => {
243+
_push(\`<option\${
244+
_ssrRenderAttr("value", item)
245+
}\${
246+
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
247+
? _ssrLooseContain(_ctx.model, item)
248+
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
249+
}>\${
250+
_ssrInterpolate(item)
251+
}</option>\`)
252+
})
253+
_push(\`<!--]-->\`)
254+
} else {
255+
_push(\`<!---->\`)
256+
}
257+
_push(\`</optgroup></select></div>\`)
258+
}"
259+
`)
260+
261+
expect(
262+
compileWithWrapper(`
263+
<select multiple v-model="model">
264+
<optgroup>
265+
<template v-for="item in items" :value="item">
266+
<option v-if="item===1" :value="item">{{item}}</option>
267+
</template>
268+
</optgroup>
269+
</select>`).code,
270+
).toMatchInlineSnapshot(`
271+
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
272+
273+
return function ssrRender(_ctx, _push, _parent, _attrs) {
274+
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
275+
_ssrRenderList(_ctx.items, (item) => {
276+
_push(\`<!--[-->\`)
277+
if (item===1) {
278+
_push(\`<option\${
279+
_ssrRenderAttr("value", item)
280+
}\${
281+
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
282+
? _ssrLooseContain(_ctx.model, item)
283+
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
284+
}>\${
285+
_ssrInterpolate(item)
286+
}</option>\`)
287+
} else {
288+
_push(\`<!---->\`)
289+
}
290+
_push(\`<!--]-->\`)
291+
})
292+
_push(\`<!--]--></optgroup></select></div>\`)
293+
}"
294+
`)
169295
})
170296

171297
test('<input type="radio">', () => {

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

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
3939
}
4040
}
4141

42+
const processSelectChildren = (children: TemplateChildNode[]) => {
43+
children.forEach(child => {
44+
if (child.type === NodeTypes.ELEMENT) {
45+
processOption(child as PlainElementNode)
46+
} else if (child.type === NodeTypes.FOR) {
47+
processSelectChildren(child.children)
48+
} else if (child.type === NodeTypes.IF) {
49+
child.branches.forEach(b => processSelectChildren(b.children))
50+
}
51+
})
52+
}
53+
4254
function processOption(plainNode: PlainElementNode) {
4355
if (plainNode.tag === 'option') {
4456
if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
@@ -65,9 +77,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
6577
)
6678
}
6779
} else if (plainNode.tag === 'optgroup') {
68-
plainNode.children.forEach(option =>
69-
processOption(option as PlainElementNode),
70-
)
80+
processSelectChildren(plainNode.children)
7181
}
7282
}
7383

@@ -163,18 +173,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
163173
checkDuplicatedValue()
164174
node.children = [createInterpolation(model, model.loc)]
165175
} else if (node.tag === 'select') {
166-
const processChildren = (children: TemplateChildNode[]) => {
167-
children.forEach(child => {
168-
if (child.type === NodeTypes.ELEMENT) {
169-
processOption(child as PlainElementNode)
170-
} else if (child.type === NodeTypes.FOR) {
171-
processChildren(child.children)
172-
} else if (child.type === NodeTypes.IF) {
173-
child.branches.forEach(b => processChildren(b.children))
174-
}
175-
})
176-
}
177-
processChildren(node.children)
176+
processSelectChildren(node.children)
178177
} else {
179178
context.onError(
180179
createDOMCompilerError(

0 commit comments

Comments
 (0)