Framework Integration
How to use Swapy in Vue where items and slots can be added and removed.
When Swapy is used in vanilla JavaScript, swapping happens by directly manipulating the DOM. This will not work properly in frameworks (like Vue) for dynamic use cases, where slots and items can be added or removed by the user.
To make this work, Swapy needs to hand DOM updates over to the framework, by setting manualSwap: true
. Each framework has its own way for that. But Swapy provides a few helpers to make it easy to set up.
First step is enabling manualSwap from the configs object.
<script setup>
onMounted(() => {
swapy.value = createSwapy(container.current, {
manualSwap: true
})
})
onUnmounted(() => {
swapy.value?.destroy()
})
</script>
Instead of displaying your data directly in the template, you need to wrap them with slottedItems
(which we’ll see how to create later in this guide).
So if this is your template (let’s say you’re displaying an array of users):
<template>
<div ref="container">
<div class="users">
<div
v-for="user in users"
class="slot"
:key="user.userId"
:data-swapy-slot="user.userId"
>
<div class="user" :key="user.userId" :data-swapy-item="user.userId">
<span>{{user.name}}</span>
<button @click="() => {
users = users.filter(u => u.userId !== user.userId)
}">Delete</button>
</div>
</div>
</div>
<button @click="() => {
users.push(/*newUser*/)
}">Add User</button>
</div>
</template>
It will become:
<template>
<div ref="container">
<div class="users">
<div
v-for="{ slotId, itemId, item: user } in slottedItems"
class="slot"
:key="slotId"
:data-swapy-slot="slotId"
>
<div class="user" :key="itemId" :data-swapy-item="itemId">
<span>{{user.name}}</span>
<button @click="() => {
users = users.filter(u => u.userId !== user.userId)
}">Delete</button>
</div>
</div>
</div>
<button @click="() => {
users.push(/*newUser*/)
}">Add User</button>
</div>
</template>
Changes are:
slottedItems
instead of users
.user
object by destructuring slottedItems
parameter.slotId
and itemId
.slotId
for the key
and data-swapy-slot
on the slot element.itemId
for the key
and data-swapy-item
on the item element.SlottedItems
will be created based on the current slotItemMap
of the Swapy instance. So let’s create a new ref for slotItemMap
and update it on swap events.
<script setup>
import { createSwapy, utils } from 'swapy'
const slotItemMap = ref(utils.initSlotItemMap(users.value, 'userId'))
// ...
onMounted(() => {
swapy.value = createSwapy(container.value, {
manualSwap: true
})
swapy.value.onSwap(event => {
requestAnimationFrame(() => {
slotItemMap.value = event.newSlotItemMap.asArray
})
})
})
</script>
To initialize slotItemMap
, we used Swapy’s helper function, utils.initSlotItemMap
. It takes two parameters: your data array (e.g. users), and the name of the id field in your data (e.g. userId).
SlottedItems
is a computed array based on the current slotItemMap
. In Vue, we can create computed values using computed
.
<script setup>
const slotItemMap = ref(utils.initSlotItemMap(users.value, 'userId'))
const slottedItems = computed(() => utils.toSlottedItems(users.value, 'userId', slotItemMap.value))
// ...
</script>
We also used a helper function for that, utils.toSlottedItems
.
We can update the Swapy’s instance using swapy.update()
. But we also need to update the current slotItemMap
along with that. To save you all the work, there’s a helper function for that, utils.dynamicSwapy
.
You need to use it in watch
, like this:
<script setup>
const slotItemMap = ref(utils.initSlotItemMap(users.value, 'userId'))
const slottedItems = computed(() => utils.toSlottedItems(users.value, 'userId', slotItemMap.value))
watch(users, () =>
utils.dynamicSwapy(swapy.value, users.value, 'userId', slotItemMap.value, (value) => slotItemMap.value = value),
{ deep: true }
)
// ...
</script>