在 Alpine.js 中使用 SortableJS 对嵌套 JSON 层树进行排序很困难

问题描述 投票:0回答:1

我在 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>

我尝试了多种方法来解决这个问题,包括:

  1. 使用 Alpine.js 实现 SortableJS 以允许拖放嵌套层。
  2. 利用 updateLayerStructure 方法根据排序后的新顺序更新 JSON 结构。
  3. 实现 findLayerById 函数,通过 ID 递归查找嵌套结构中的层。

此外,我还发现了一篇相关的 Stack Overflow 帖子,其中一位用户提供了一个使用 SortableJS 将嵌套列表序列化为 JSON 的解决方案。以下是他们的方法的参考:SortableJS Get Order from Nested List

尽管进行了这些尝试,我仍然无法实现将特定节点从 HTML 直接更新为 JSON 的所需功能。您能否建议任何替代方法或解决方案,以使排序嵌套 JSON 层与 Alpine.js 中的 SortableJS 一起正常工作?

javascript json nested alpine.js sortablejs
1个回答
0
投票

你在哪里设置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);
© www.soinside.com 2019 - 2024. All rights reserved.