Сложность сортировки вложенного дерева слоев JSON с помощью SortableJS в Alpine.js

У меня возникли проблемы с сортировкой вложенного дерева слоев JSON с помощью SortableJS в приложении Alpine.js. У меня есть сложная структура 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">
          <span>
            <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>
          </span>
          <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 
          class = "mt-1 mb-1 ml-4"
          data-id = "${layer.id}">
            <template x-for='layer in layer.children'>
              <li class = "list-none select-none" x-html = "renderLayer(layer)" data-id = "${layer.id}"></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 dragged item's ID
            const draggableId = evt.item.dataset.id;
            const draggableLayer = this.findLayerById(draggableId, this.data.layerStructure);
            const dropId = evt.to.dataset.id;
            console.info(draggableLayer);
            
            // 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(draggableId, newOrder);
          }
        })
      };

      const containerElement = document.getElementById('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));
      }

      // Update your output element with the updated layer structure
      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 -->
      <div id = "sortableContainer">
        <template x-for = "layer in data.layerStructure">
          <ul class = "mt-1 mb-1 ml-4" :data-id = "layer.id">
            <li class = "list-none select-none" x-html = "renderLayer(layer)" :data-id = "layer.id"></li>
          </ul>
        </template>
      </div>
      
      <!-- 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. Реализация SortableJS с Alpine.js, позволяющая перетаскивать вложенные слои.
  2. Использование метода updateLayerStructure для обновления структуры JSON на основе нового порядка после сортировки.
  3. Реализация функции findLayerById для рекурсивного поиска слоя во вложенной структуре по его идентификатору.

Кроме того, я наткнулся на соответствующую публикацию о переполнении стека, в которой пользователь предоставил решение с использованием SortableJS для сериализации вложенных списков в JSON. Вот ссылка на их подход: SortableJS Получить заказ из вложенного списка.

Несмотря на эти попытки, мне не удалось добиться желаемой функциональности прямого обновления определенных узлов с HTML на JSON. Не могли бы вы предложить какие-либо альтернативные подходы или решения, позволяющие правильно работать сортировке вложенных слоев JSON с SortableJS в Alpine.js?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
104
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

где вы устанавливаете идентификатор данных?

// 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);

Кроме того, похоже, что это возвращает тег шаблона, а не li,

evt.from.children

-- РЕДАКТИРОВАТЬ --

ок, думаю, у меня это работает.

Я уменьшил набор данных, чтобы было легче увидеть изменения.

Я удалил шаблон alpine из функции renderLayer и изменил отображение Layer.name, так как иногда он показывал неверные данные. Для тестирования я изменил Layer.name на Layer.id.

Я изменил инициализациюSortable несколькими способами: теперь она сохраняет сортируемый объект в массиве, чтобы получить порядок сортировки каждого сортируемого объекта для построения выходных данных, а onEnd был изменен для использования новой функции обработки.

findLayerById был изменен, поскольку он постоянно обновлял data.layerStructure при использовании, что приводило к сбою страницы, поскольку он использовался в шаблоне alpine x-for.

updateLayerStructure был удален, так как не удалось обновить data.layerStructure, поскольку он обновил шаблон alpine и испортил отображение.

ProcessSortable3 был создан для генерации вывода (да, третья попытка)

init был обновлен для отображения первого сгенерированного вывода.

У меня есть рабочий пример https://codepen.io/terreporter/pen/WNqrWga

просто нужно иметь способ добавлять новые элементы в список, и я думаю, что это был бы хороший функциональный вложенный редактор перетаскивания.

По Ли и ул. Я заметил, что сегодня при перетаскивании он даже не нацелен на правильный JSON, и я пока не знаю, почему. Я буду дома через 3,5 часа, чтобы все посмотреть поподробнее.

Michael Schwartz 18.07.2024 22:12

@MichaelSchwartz внес некоторые изменения :)

Terre Porter 19.07.2024 00:32

Object.entries — интересный подход к работе с массивом. Я не думал об этом. Хорошая работа! Мне нужно, чтобы функция onclick работала, чтобы теперь нацеливаться на слой для управления состоянием, но сортировка - это то, что меня застало. Это экспериментальное перо, над которым я работаю над мелочами в бесплатном конструкторе веб-сайтов с открытым исходным кодом, который я создаю для мобильных устройств. - codepen.io/michaelsboost/pen/mdZeZBV

Michael Schwartz 19.07.2024 05:18

Выглядит потрясающе, я попробую превратить его в конструктор меню для проекта, над которым я работаю, который также будет с открытым исходным кодом.

Terre Porter 19.07.2024 14:08

Это репозиторий GitHub. Новую версию еще не выпустил. Он еще не готов, но у вас есть ссылка, когда он будет готов github.com/michaelsboost/Polyrise

Michael Schwartz 19.07.2024 17:48

хорошо, спасибо. Я изменил функцию findByLayer с Object.assign на JSON.parse(JSON.stringify(layer)). поскольку он все еще не создавал реальную копию объекта, а некоторые дочерние свойства копировались по ссылке.

Terre Porter 20.07.2024 14:50

@MichaelSchwartz Мне пришлось внести некоторые изменения в некоторые функции, чтобы обновить данные меню. codepen.io/terreporter/pen/NWZNMYY

Terre Porter 20.07.2024 21:49

Другие вопросы по теме