diff --git a/flow/compiler.js b/flow/compiler.js
index 3cf1714c66b..977df14856c 100644
--- a/flow/compiler.js
+++ b/flow/compiler.js
@@ -41,6 +41,7 @@ declare type ModuleOptions = {
}
declare type ASTModifiers = { [key: string]: boolean }
+declare type ASTIfConditions = Array<{ exp: ?string; block: ASTElement }>
declare type ASTElementHandler = {
value: string;
@@ -95,8 +96,9 @@ declare type ASTElement = {
if?: string;
ifProcessed?: boolean;
+ elseif?: string;
else?: true;
- elseBlock?: ASTElement;
+ conditions?: ASTIfConditions;
for?: string;
forProcessed?: boolean;
diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js
index fe57ee5f80d..572634fa801 100644
--- a/src/compiler/codegen/index.js
+++ b/src/compiler/codegen/index.js
@@ -110,17 +110,27 @@ function genOnce (el: ASTElement): string {
}
}
-// v-if with v-once shuold generate code like (a)?_m(0):_m(1)
function genIf (el: any): string {
- const exp = el.if
el.ifProcessed = true // avoid recursion
- return `(${exp})?${el.once ? genOnce(el) : genElement(el)}:${genElse(el)}`
+ return genIfConditions(el.conditions)
}
-function genElse (el: ASTElement): string {
- return el.elseBlock
- ? genElement(el.elseBlock)
- : '_e()'
+function genIfConditions (conditions: ASTIfConditions): string {
+ if (!conditions.length) {
+ return '_e()'
+ }
+
+ var condition = conditions.shift()
+ if (condition.exp) {
+ return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}`
+ } else {
+ return `${genTernaryExp(condition.block)}`
+ }
+
+ // v-if with v-once shuold generate code like (a)?_m(0):_m(1)
+ function genTernaryExp (el) {
+ return el.once ? genOnce(el) : genElement(el)
+ }
}
function genFor (el: any): string {
diff --git a/src/compiler/optimizer.js b/src/compiler/optimizer.js
index 60c61d9096e..34029ee6ec9 100644
--- a/src/compiler/optimizer.js
+++ b/src/compiler/optimizer.js
@@ -80,12 +80,18 @@ function markStaticRoots (node: ASTNode, isInFor: boolean) {
markStaticRoots(node.children[i], isInFor || !!node.for)
}
}
- if (node.elseBlock) {
- markStaticRoots(node.elseBlock, isInFor)
+ if (node.conditions) {
+ walkThroughConditionsBlocks(node.conditions, isInFor)
}
}
}
+function walkThroughConditionsBlocks (conditionBlocks: ASTIfConditions, isInFor: boolean): void {
+ for (let i = 1, len = conditionBlocks.length; i < len; i++) {
+ markStaticRoots(conditionBlocks[i].block, isInFor)
+ }
+}
+
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { // expression
return false
diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js
index 3b60e99d2e2..6e8a4b57266 100644
--- a/src/compiler/parser/index.js
+++ b/src/compiler/parser/index.js
@@ -154,10 +154,13 @@ export function parse (
root = element
checkRootConstraints(root)
} else if (!stack.length) {
- // allow 2 root elements with v-if and v-else
- if (root.if && element.else) {
+ // allow root elements with v-if, v-elseif and v-else
+ if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element)
- root.elseBlock = element
+ addIfCondition(root, {
+ exp: element.elseif,
+ block: element
+ })
} else if (process.env.NODE_ENV !== 'production' && !warned) {
warned = true
warn(
@@ -166,8 +169,8 @@ export function parse (
}
}
if (currentParent && !element.forbidden) {
- if (element.else) { // else block
- processElse(element, currentParent)
+ if (element.elseif || element.else) {
+ processIfConditions(element, currentParent)
} else if (element.slotScope) { // scoped slot
currentParent.plain = false
const name = element.slotTarget || 'default'
@@ -316,23 +319,43 @@ function processIf (el) {
const exp = getAndRemoveAttr(el, 'v-if')
if (exp) {
el.if = exp
- }
- if (getAndRemoveAttr(el, 'v-else') != null) {
- el.else = true
+ addIfCondition(el, {
+ exp: exp,
+ block: el
+ })
+ } else {
+ if (getAndRemoveAttr(el, 'v-else') != null) {
+ el.else = true
+ }
+ const elseif = getAndRemoveAttr(el, 'v-elseif')
+ if (elseif) {
+ el.elseif = elseif
+ }
}
}
-function processElse (el, parent) {
+function processIfConditions (el, parent) {
const prev = findPrevElement(parent.children)
if (prev && prev.if) {
- prev.elseBlock = el
+ addIfCondition(prev, {
+ exp: el.elseif,
+ block: el
+ })
} else if (process.env.NODE_ENV !== 'production') {
warn(
- `v-else used on element <${el.tag}> without corresponding v-if.`
+ `v-${el.elseif ? ('elseif="' + el.elseif + '"') : 'else'} ` +
+ `used on element <${el.tag}> without corresponding v-if.`
)
}
}
+function addIfCondition (el, condition) {
+ if (!el.conditions) {
+ el.conditions = []
+ }
+ el.conditions.push(condition)
+}
+
function processOnce (el) {
const once = getAndRemoveAttr(el, 'v-once')
if (once != null) {
diff --git a/test/unit/features/directives/if.spec.js b/test/unit/features/directives/if.spec.js
index 893c83d35a3..7146ec0e048 100644
--- a/test/unit/features/directives/if.spec.js
+++ b/test/unit/features/directives/if.spec.js
@@ -88,6 +88,55 @@ describe('Directive v-if', () => {
}).then(done)
})
+ it('should work well with v-elseif', done => {
+ const vm = new Vue({
+ template: `
+
+ hello
+ elseif
+ bye
+
+ `,
+ data: { foo: true, bar: false }
+ }).$mount()
+ expect(vm.$el.innerHTML.trim()).toBe('hello')
+ vm.foo = false
+ waitForUpdate(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('bye')
+ vm.bar = true
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('elseif')
+ vm.bar = false
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('bye')
+ vm.foo = true
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('hello')
+ vm.foo = false
+ vm.bar = {}
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('elseif')
+ vm.bar = 0
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('bye')
+ vm.bar = []
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('elseif')
+ vm.bar = null
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('bye')
+ vm.bar = '0'
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('elseif')
+ vm.bar = undefined
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('bye')
+ vm.bar = 1
+ }).then(() => {
+ expect(vm.$el.innerHTML.trim()).toBe('elseif')
+ }).then(done)
+ })
+
it('should work well with v-for', done => {
const vm = new Vue({
template: `
diff --git a/test/unit/features/directives/once.spec.js b/test/unit/features/directives/once.spec.js
index 21da307980b..01bb83ca9a4 100644
--- a/test/unit/features/directives/once.spec.js
+++ b/test/unit/features/directives/once.spec.js
@@ -242,6 +242,56 @@ describe('Directive v-once', () => {
}).then(done)
})
+ it('should work inside v-for with nested v-elseif and v-else', done => {
+ const vm = new Vue({
+ data: {
+ tester: false,
+ list: [{ id: 0, text: 'a', tester: true, truthy: 'y' }]
+ },
+ template: `
+
+
+
+ {{ i.truthy }}
+ {{ i.text }}elseif
+ {{ i.text }}
+
+
+
+
+ {{ i.truthy }}
+ {{ i.text }}elseif
+ {{ i.text }}
+
+
+ `
+ }).$mount()
+
+ expectTextContent(vm, 'y')
+ vm.list[0].truthy = 'yy'
+ waitForUpdate(() => {
+ expectTextContent(vm, 'y')
+ vm.list[0].tester = false
+ }).then(() => {
+ expectTextContent(vm, 'a')
+ vm.list[0].text = 'nn'
+ }).then(() => {
+ expectTextContent(vm, 'a')
+ vm.tester = true
+ }).then(() => {
+ expectTextContent(vm, 'nnelseif')
+ vm.list[0].text = 'xx'
+ }).then(() => {
+ expectTextContent(vm, 'nnelseif')
+ vm.list[0].tester = true
+ }).then(() => {
+ expectTextContent(vm, 'yy')
+ vm.list[0].truthy = 'nn'
+ }).then(() => {
+ expectTextContent(vm, 'yy')
+ }).then(done)
+ })
+
it('should warn inside non-keyed v-for', () => {
const vm = new Vue({
data: {
diff --git a/test/unit/features/directives/style.spec.js b/test/unit/features/directives/style.spec.js
index 8a480602a76..beca442de76 100644
--- a/test/unit/features/directives/style.spec.js
+++ b/test/unit/features/directives/style.spec.js
@@ -304,4 +304,39 @@ describe('Directive v-bind:style', () => {
expect(style.marginTop).toBe('12px')
}).then(done)
})
+
+ it('should not merge for v-if, v-elseif and v-else elements', (done) => {
+ const vm = new Vue({
+ template:
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
',
+ data: {
+ foo: true,
+ bar: false,
+ style: {
+ fontSize: '12px'
+ }
+ }
+ }).$mount()
+ const style = vm.$el.children[0].style
+ expect(style.fontSize).toBe('12px')
+ expect(style.color).toBe('blue')
+ waitForUpdate(() => {
+ vm.foo = false
+ }).then(() => {
+ expect(style.color).toBe('')
+ expect(style.fontSize).toBe('')
+ expect(style.marginBottom).toBe('24px')
+ vm.bar = true
+ }).then(() => {
+ expect(style.color).toBe('')
+ expect(style.fontSize).toBe('')
+ expect(style.marginBottom).toBe('')
+ expect(style.marginTop).toBe('12px')
+ }).then(done)
+ })
})
diff --git a/test/unit/modules/compiler/codegen.spec.js b/test/unit/modules/compiler/codegen.spec.js
index 989a71f22bd..4a8be0b0b47 100644
--- a/test/unit/modules/compiler/codegen.spec.js
+++ b/test/unit/modules/compiler/codegen.spec.js
@@ -74,6 +74,27 @@ describe('codegen', () => {
)
})
+ it('generate v-elseif directive', () => {
+ assertCodegen(
+ '',
+ `with(this){return _h('div',[(show)?_h('p',["hello"]):(hide)?_h('p',["world"]):_e()])}`
+ )
+ })
+
+ it('generate v-elseif with v-else directive', () => {
+ assertCodegen(
+ '',
+ `with(this){return _h('div',[(show)?_h('p',["hello"]):(hide)?_h('p',["world"]):_h('p',["bye"])])}`
+ )
+ })
+
+ it('generate mutli v-elseif with v-else directive', () => {
+ assertCodegen(
+ '',
+ `with(this){return _h('div',[(show)?_h('p',["hello"]):(hide)?_h('p',["world"]):(3)?_h('p',["elseif"]):_h('p',["bye"])])}`
+ )
+ })
+
it('generate ref', () => {
assertCodegen(
'',
diff --git a/test/unit/modules/compiler/optimizer.spec.js b/test/unit/modules/compiler/optimizer.spec.js
index f7b3e08c21b..5fc38251430 100644
--- a/test/unit/modules/compiler/optimizer.spec.js
+++ b/test/unit/modules/compiler/optimizer.spec.js
@@ -66,7 +66,7 @@ describe('optimizer', () => {
optimize(ast, baseOptions)
expect(ast.static).toBe(false)
expect(ast.children[0].static).toBe(false)
- expect(ast.children[0].elseBlock.static).toBeUndefined()
+ expect(ast.children[0].conditions[1].block.static).toBeUndefined()
})
it('v-pre directive', () => {
@@ -213,14 +213,28 @@ describe('optimizer', () => {
it('mark static trees inside v-for with nested v-else and v-once', () => {
const ast = parse(`
+
+
+
{{ i }}
+
{{ i }}
+
{{ i }}
+
+
-
`, baseOptions)
+
+ `, baseOptions)
optimize(ast, baseOptions)
- expect(ast.elseBlock.children[0].children[0].elseBlock.staticRoot).toBe(false)
- expect(ast.elseBlock.children[0].children[0].elseBlock.staticInFor).toBe(true)
+ expect(ast.conditions[1].block.children[0].children[0].conditions[1].block.staticRoot).toBe(false)
+ expect(ast.conditions[1].block.children[0].children[0].conditions[1].block.staticInFor).toBe(true)
+
+ expect(ast.conditions[1].block.children[0].children[0].conditions[2].block.staticRoot).toBe(false)
+ expect(ast.conditions[1].block.children[0].children[0].conditions[2].block.staticInFor).toBe(true)
+
+ expect(ast.conditions[2].block.children[0].children[0].conditions[1].block.staticRoot).toBe(false)
+ expect(ast.conditions[2].block.children[0].children[0].conditions[1].block.staticInFor).toBe(true)
})
})
diff --git a/test/unit/modules/compiler/parser.spec.js b/test/unit/modules/compiler/parser.spec.js
index 21a7f26ad46..127e8528ea8 100644
--- a/test/unit/modules/compiler/parser.spec.js
+++ b/test/unit/modules/compiler/parser.spec.js
@@ -88,6 +88,12 @@ describe('parser', () => {
.not.toHaveBeenWarned()
})
+ it('not warn 3 root elements with v-if, v-elseif and v-else', () => {
+ parse('
', baseOptions)
+ expect('Component template should contain exactly one root element')
+ .not.toHaveBeenWarned()
+ })
+
it('not warn 2 root elements with v-if and v-else on separate lines', () => {
parse(`
@@ -97,13 +103,59 @@ describe('parser', () => {
.not.toHaveBeenWarned()
})
+ it('not warn 3 or more root elements with v-if, v-elseif and v-else on separate lines', () => {
+ parse(`
+
+
+
+ `, baseOptions)
+ expect('Component template should contain exactly one root element')
+ .not.toHaveBeenWarned()
+
+ parse(`
+
+
+
+
+
+ `, baseOptions)
+ expect('Component template should contain exactly one root element')
+ .not.toHaveBeenWarned()
+ })
+
it('generate correct ast for 2 root elements with v-if and v-else on separate lines', () => {
const ast = parse(`
`, baseOptions)
expect(ast.tag).toBe('div')
- expect(ast.elseBlock.tag).toBe('p')
+ expect(ast.conditions[1].block.tag).toBe('p')
+ })
+
+ it('generate correct ast for 3 or more root elements with v-if and v-else on separate lines', () => {
+ const ast = parse(`
+
+
+
+ `, baseOptions)
+ expect(ast.tag).toBe('div')
+ expect(ast.conditions[0].block.tag).toBe('div')
+ expect(ast.conditions[1].block.tag).toBe('span')
+ expect(ast.conditions[2].block.tag).toBe('p')
+
+ const astMore = parse(`
+
+
+
+
+
+ `, baseOptions)
+ expect(astMore.tag).toBe('div')
+ expect(astMore.conditions[0].block.tag).toBe('div')
+ expect(astMore.conditions[1].block.tag).toBe('span')
+ expect(astMore.conditions[2].block.tag).toBe('div')
+ expect(astMore.conditions[3].block.tag).toBe('span')
+ expect(astMore.conditions[4].block.tag).toBe('p')
})
it('warn 2 root elements with v-if', () => {
@@ -118,12 +170,34 @@ describe('parser', () => {
.toHaveBeenWarned()
})
+ it('warn 3 root elements with v-if and v-elseif on first 2', () => {
+ parse('
', baseOptions)
+ expect('Component template should contain exactly one root element:\n\n' +
+ '
')
+ .toHaveBeenWarned()
+ })
+
+ it('warn 4 root elements with v-if, v-elseif and v-else on first 2', () => {
+ parse('
', baseOptions)
+ expect('Component template should contain exactly one root element:\n\n' +
+ '
')
+ .toHaveBeenWarned()
+ })
+
it('warn 2 root elements with v-if and v-else with v-for on 2nd', () => {
parse('
', baseOptions)
- expect('Cannot use v-for on stateful component root element because it renders multiple elements:\n
')
+ expect('Cannot use v-for on stateful component root element because it renders multiple elements:\n' +
+ '
')
.toHaveBeenWarned()
})
+ it('warn 2 root elements with v-if and v-elseif with v-for on 2nd', () => {
+ parse('
', baseOptions)
+ expect('Cannot use v-for on stateful component root element because it renders multiple elements:\n' +
+ '
')
+ .toHaveBeenWarned()
+ })
+
it('warn
as root element', () => {
parse('', baseOptions)
expect('Cannot use as component root element').toHaveBeenWarned()
@@ -193,15 +267,32 @@ describe('parser', () => {
it('v-if directive syntax', () => {
const ast = parse('hello world
', baseOptions)
expect(ast.if).toBe('show')
+ expect(ast.conditions[0].exp).toBe('show')
+ })
+
+ it('v-elseif directive syntax', () => {
+ const ast = parse('', baseOptions)
+ const ifAst = ast.children[0]
+ const conditionsAst = ifAst.conditions
+ expect(conditionsAst.length).toBe(3)
+ expect(conditionsAst[1].block.children[0].text).toBe('elseif')
+ expect(conditionsAst[1].block.parent).toBe(ast)
+ expect(conditionsAst[2].block.children[0].text).toBe('world')
+ expect(conditionsAst[2].block.parent).toBe(ast)
})
it('v-else directive syntax', () => {
const ast = parse('', baseOptions)
const ifAst = ast.children[0]
- const elseAst = ifAst.elseBlock
- expect(elseAst.else).toBe(true)
- expect(elseAst.children[0].text).toBe('world')
- expect(elseAst.parent).toBe(ast)
+ const conditionsAst = ifAst.conditions
+ expect(conditionsAst.length).toBe(2)
+ expect(conditionsAst[1].block.children[0].text).toBe('world')
+ expect(conditionsAst[1].block.parent).toBe(ast)
+ })
+
+ it('v-elseif directive invalid syntax', () => {
+ parse('', baseOptions)
+ expect('v-elseif="1" used on element').toHaveBeenWarned()
})
it('v-else directive invalid syntax', () => {