我在 Alpine.js 应用程序中使用 SortableJS 对嵌套 JSON 层树进行排序时遇到问题。我有一个复杂的 JSON 结构来表示层,它可以嵌套多个级别。我的目标是对这些层进行排序并相应地更新 JSON 结构。
这是我的层结构和相关代码的简化版本:
// Pre-define theme
document.documentElement.setAttribute('data-theme', 'dark');
function App() {
return {
data: {
layerStructure: [
{
"tag": "header",
"type": "box",
"name": "box",
"id": "wnl0mxhml",
"state": {
"collapsed": false,
"visible": true,
"selected": false
},
"props": {
"class": "p4dmvkj5v"
},
"children": [
{
"tag": "hgroup",
"type": "box",
"name": "box",
"id": "xw0wqhizc",
"state": {
"collapsed": false,
"visible": true,
"selected": false
},
"props": {
"class": "w78d2h"
},
"children": [
{
"tag": "h1",
"type": "text",
"name": "text",
"id": "orfik88na",
"state": {
"collapsed": false,
"visible": true,
"selected": false
},
"props": {
"class": "n6zv2tuar"
},
"text": "App name"
},
{
"tag": "h2",
"type": "text",
"name": "text",
"id": "lzsntwjt3",
"state": {
"collapsed": false,
"visible": true,
"selected": false
},
"props": {
"class": "xqkuxhejp"
},
"text": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi accusantium rem sint voluptatum quisquam cum. Nostrum dolorum alias doloribus quod accusantium odit vero dolor excepturi cumque mollitia? Laboriosam, dolore rem!"
}
]
}
]
},
{
"tag": "main",
"type": "box",
"name": "box",
"id": "o03yq4tqx",
"state": {
"collapsed": false,
"visible": true,
"selected": false
},
"props": {
"class": "p4dmvkj5v"
},
"children": [
{
"tag": "figure",
"type": "box",
"name": "box",
"id": "ary2rnlid",
"state": {
"collapsed": false,
"visible": true,
"selected": false
},
"children": [
{
"tag": "img",
"type": "img",
"name": "img",
"id": "o4cwkc0zb",
"state": {
"collapsed": false,
"visible": true,
"selected": false
},
"props": {
"class": "cc7uwye7i",
"src": "imgs/image.webp",
"alt": "Polyrise"
}
},
{
"tag": "figcaption",
"type": "box",
"name": "box",
"id": "t57ciu00f",
"state": {
"collapsed": false,
"visible": true,
"selected": false
},
"children": [
{
"tag": "a",
"type": "text",
"name": "text",
"id": "u50q0cuz9",
"state": {
"collapsed": false,
"visible": true,
"selected": false
},
"props": {
"href": "https://michaelsboost.com/Polyrise/",
"target": "_blank"
},
"text": "michaelsboost.com/Polyrise"
}
],
"text": "Image from"
}
]
}
]
}
]
},
icons: {
move: `<svg class="w-3" viewBox="0 0 512 512" style="color: unset;">
<path
fill="currentColor"
d="M278.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-64 64c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8h32v96H128V192c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6V288h96v96H192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l64 64c12.5 12.5 32.8 12.5 45.3 0l64-64c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8H288V288h96v32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6v32H288V128h32c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-64-64z"/>
</svg>`
},
renderLayer(layer) {
let html = `<code
class="p-0 flex justify-between whitespace-nowrap min-w-min">
<button
data-move
aria-label="sort layer"
name="sort layer"
class="bg-transparent border-0 p-2 text-xs cursor-grab focus:shadow-none" style="color: unset;">
<span x-html="icons.move"></span>
</button>
<button
aria-label="toggle selected layer"
name="toggle selected layer"
class="bg-transparent border-0 p-2 text-xs text-right capitalize"
style="color: unset;">
<span x-html="layer.name"></span>
</button>
</code>`;
if(layer.children) {
html += `<ul
x-ref="${layer.id}"
class="mt-1 mb-1 ml-4">
<template x-for='layer in layer.children'>
<li class="list-none select-none" x-html="renderLayer(layer)"></li>
</template>
</ul>`;
}
return html;
},
init() {
// Initialize SortableJS on the element with ID 'sortableContainer'
const initializeSortable = element => {
new Sortable(element, {
group: 'shared', // Enable moving items between containers
handle: '[data-move]',
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65,
onEnd: evt => {
// Get the updated order
const newOrder = Array.from(evt.from.children).map(item => item.dataset.id);
// Update the layer structure based on new order
this.updateLayerStructure(evt.item.dataset.id, newOrder);
}
})
};
const containerElement = this.$refs.sortableContainer;
this.$nextTick(() => {
initializeSortable(containerElement);
containerElement.querySelectorAll('ul').forEach(ul => initializeSortable(ul));
});
},
findLayerById(id, layers) {
for (const layer of layers) {
if (layer.id === id) return layer;
if (layer.children) {
const found = this.findLayerById(id, layer.children);
if (found) return found;
}
}
return null;
},
updateLayerStructure(id, newOrder) {
// Find the layer by ID in the structure
const layerToUpdate = this.findLayerById(id, this.data.layerStructure);
if (layerToUpdate) {
// Update the children order based on newOrder
layerToUpdate.children = newOrder.map(itemId => this.findLayerById(itemId, this.data.layerStructure));
}
this.$refs.output.value = JSON.stringify(this.data.layerStructure, null, 2);
this.$refs.output.textContent = JSON.stringify(this.data.layerStructure, null, 2);
}
}
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/picocss/2.0.6/pico.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css"/>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
<main x-data="App()" x-init="init()">
<div
class="absolute inset-0 p-2 overflow-auto"
>
<div class="grid grid-cols-2 justify-between h-full gap-4">
<!-- layers -->
<ul x-ref="sortableContainer" class="mt-1 mb-1 ml-4">
<template x-for="layer in data.layerStructure">
<li class="list-none select-none" x-html="renderLayer(layer)"></li>
</template>
</ul>
<!-- json -->
<textarea x-ref="output" class="resize-none" placeholder="json code here" x-text="JSON.stringify(data.layerStructure, null, 2)"></textarea>
</div>
</main>
<script src="https://michaelsboost.com/TailwindCSSMod/tailwind-mod.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.2/Sortable.min.js"></script>
我尝试了多种方法来解决这个问题,包括:
此外,我还发现了一篇相关的 Stack Overflow 帖子,其中一位用户提供了一个使用 SortableJS 将嵌套列表序列化为 JSON 的解决方案。以下是他们的方法的参考:SortableJS Get Order from Nested List。
尽管进行了这些尝试,我仍然无法实现将特定节点从 HTML 直接更新为 JSON 的所需功能。您能否建议任何替代方法或解决方案,以使排序嵌套 JSON 层与 Alpine.js 中的 SortableJS 一起正常工作?
你在哪里设置data-id?
// Get the updated order
const newOrder = Array.from(evt.from.children).map(item => item.dataset.id);
// Update the layer structure based on new order
this.updateLayerStructure(evt.item.dataset.id, newOrder);