Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add x-else and x-else-if Directives for Conditional Rendering #4353

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/alpinejs/src/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ let directiveOrder = [
'transition',
'show',
'if',
'else-if',
'else',
DEFAULT,
'teleport',
]
Expand Down
2 changes: 2 additions & 0 deletions packages/alpinejs/src/directives/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import './x-show'
import './x-for'
import './x-ref'
import './x-if'
import './x-else-if';
import './x-else';
import './x-id'
import './x-on'

Expand Down
81 changes: 81 additions & 0 deletions packages/alpinejs/src/directives/x-else-if.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { evaluateLater } from '../evaluator'
import { addScopeToNode } from '../scope'
import { directive } from '../directives'
import { initTree, destroyTree } from '../lifecycle'
import { mutateDom } from '../mutation'
import { skipDuringClone } from '../clone'
import { warn } from "../utils/warn"

directive('else-if', (el, { expression }, { effect, cleanup }) => {
if (el.tagName.toLowerCase() !== 'template') {
warn('x-else-if can only be used on a <template> tag', el)
return
}

el._x_else_if = { expression }
let prevElement = el.previousElementSibling

// Traverse backward to find a preceding x-if or x-else-if
while (prevElement && !prevElement._x_if && !prevElement._x_else_if) {
prevElement = prevElement.previousElementSibling
}

if (!prevElement || (!prevElement._x_if && !prevElement._x_else_if)) {
warn('x-else-if requires an x-if or x-else-if before it', el)
return
}

let evaluate = evaluateLater(el, expression)

let show = () => {
if (el._x_currentElseIfEl) return el._x_currentElseIfEl

let clone = el.content.cloneNode(true).firstElementChild

addScopeToNode(clone, {}, el)

mutateDom(() => {
el.after(clone)
skipDuringClone(() => initTree(clone))()
})

el._x_currentElseIfEl = clone

el._x_undoElseIf = () => {
mutateDom(() => {
destroyTree(clone)
clone.remove()
})

delete el._x_currentElseIfEl
}

return clone
}

let hide = () => {
if (!el._x_undoElseIf) return

el._x_undoElseIf()
delete el._x_undoElseIf
}

effect(() => {
// Determine if any preceding condition is true
let prevConditionsSatisfied = prevElement._x_ifSatisfied || prevElement._x_elseIfSatisfied

evaluate(value => {
if (prevConditionsSatisfied) {
hide()
} else if (value) {
show()
el._x_elseIfSatisfied = true
} else {
hide()
el._x_elseIfSatisfied = false
}
})
})

cleanup(() => el._x_undoElseIf && el._x_undoElseIf())
})
92 changes: 92 additions & 0 deletions packages/alpinejs/src/directives/x-else.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { evaluateLater } from '../evaluator'
import { addScopeToNode } from '../scope'
import { directive } from '../directives'
import { initTree, destroyTree } from '../lifecycle'
import { mutateDom } from '../mutation'
import { skipDuringClone } from '../clone'
import { warn } from "../utils/warn"

directive('else', (el, { expression }, { effect, cleanup }) => {
if (el.tagName.toLowerCase() !== 'template') {
warn('x-else can only be used on a <template> tag', el)
return
}

let prevElement = el.previousElementSibling
let conditions = []

// Traverse backwards to find all preceding x-if and x-else-if
while (prevElement) {
if (prevElement._x_if || prevElement._x_else_if) {
conditions.push(evaluateLater(prevElement, prevElement._x_if ? prevElement._x_if.expression : prevElement._x_else_if.expression))
}
prevElement = prevElement.previousElementSibling
}

if (conditions.length === 0) {
warn('x-else requires an x-if or x-else-if before it', el)
return
}

let evaluateConditions = (callback) => {
let index = 0

let evaluateNext = (value) => {
if (value) {
callback(true)
} else if (index < conditions.length) {
conditions[index++](evaluateNext)
} else {
callback(false)
}
}

evaluateNext(false)
}


let show = () => {
if (el._x_currentElseEl) return el._x_currentElseEl

let clone = el.content.cloneNode(true).firstElementChild

addScopeToNode(clone, {}, el)

mutateDom(() => {
el.after(clone)
skipDuringClone(() => initTree(clone))()
})

el._x_currentElseEl = clone

el._x_undoElse = () => {
mutateDom(() => {
destroyTree(clone)
clone.remove()
})

delete el._x_currentElseEl
}

return clone
}

let hide = () => {
if (!el._x_undoElse) return

el._x_undoElse()
delete el._x_undoElse
}

effect(() => {
evaluateConditions(value => {
if (value) {
hide()
} else {
show()
}
})
})

cleanup(() => el._x_undoElse && el._x_undoElse())
})
10 changes: 9 additions & 1 deletion packages/alpinejs/src/directives/x-if.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { skipDuringClone } from '../clone'
directive('if', (el, { expression }, { effect, cleanup }) => {
if (el.tagName.toLowerCase() !== 'template') warn('x-if can only be used on a <template> tag', el)

el._x_if = { expression }

let evaluate = evaluateLater(el, expression)

let show = () => {
Expand Down Expand Up @@ -49,7 +51,13 @@ directive('if', (el, { expression }, { effect, cleanup }) => {
}

effect(() => evaluate(value => {
value ? show() : hide()
if (value) {
show()
el._x_ifSatisfied = true
} else {
hide()
el._x_ifSatisfied = false
}
}))

cleanup(() => el._x_undoIf && el._x_undoIf())
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alpinejs/docs",
"version": "3.14.1-revision.2",
"version": "3.14.1-revision.4",
"description": "The documentation for Alpine",
"author": "Caleb Porzio",
"license": "MIT"
Expand Down
29 changes: 29 additions & 0 deletions packages/docs/src/en/directives/else-if.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
order: 17
title: else-if
---

# x-else-if

`x-else-if` is used alongside `x-if` and `x-else` to handle multiple conditional cases in a chain. It allows you to check additional conditions if the previous `x-if` condition evaluates to false.

Like `x-if` and `x-else`, `x-else-if` should be applied to a <template> tag. This ensures proper handling of the element's addition and removal in the DOM.

```alpine
<template x-if="status === 'loading'">
<div>Loading...</div>
</template>
<template x-else-if="status === 'success'">
<div>Data loaded successfully!</div>
</template>
<template x-else-if="status === 'error'">
<div>There was an error loading the data.</div>
</template>
<template x-else>
<div>No status available</div>
</template>
```

> Place <template x-else-if> after an `x-if` block or other `x-else-if` blocks. It will render only if the preceding conditions are false and the current condition is true.
> `x-else-if` adds and removes elements from the DOM, similar to `x-if`, rather than just toggling visibility with CSS.
> As with `x-if`, `x-else-if` does not support transitions using `x-transition`.
26 changes: 26 additions & 0 deletions packages/docs/src/en/directives/else.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
order: 18
title: else
---

# x-else

`x-else` is used in conjunction with `x-if` or `x-else-if` to handle the fallback or default case in conditional rendering. It allows you to specify what should be displayed when all preceding conditions are false.

Just like `x-if`, `x-else` should be applied to a <template> tag. This ensures that Alpine.js can properly manage the addition and removal of the element from the DOM.

```alpine
<template x-if="value === 'yes'">
<div>Yes is selected</div>
</template>
<template x-else-if="value === 'no'">
<div>No is selected</div>
</template>
<template x-else>
<div>Neither yes nor no is selected</div>
</template>
```

> Place <template x-else> after one or more `x-if` or `x-else-if` blocks. It will render only when none of the preceding conditions are true.
> Unlike `x-show`, `x-else` also adds and removes elements from the DOM rather than toggling their visibility with CSS.
> Since `x-else` operates similarly to `x-if`, it does not support transitions with `x-transition`.
2 changes: 1 addition & 1 deletion packages/docs/src/en/directives/id.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
order: 17
order: 20
title: id
---

Expand Down
Loading
Loading