Сохранение состояния вложенных сеток

Gridstack.js предоставляет возможность создавать динамические, перетаскиваемые и масштабируемые сетки элементов в веб-приложениях. Когда используется вложенная сетка (nested grid), каждый элемент родительской сетки может содержать собственную дочернюю сетку. Для правильного восстановления интерфейса после перезагрузки страницы или изменения состояния необходимо сохранять позицию, размеры и порядок элементов как родительской, так и дочерней сеток.


Структура данных для сохранения

Gridstack.js использует объекты для описания состояния виджетов. Каждый элемент имеет стандартные свойства:

  • x – позиция по горизонтали (номер колонки)
  • y – позиция по вертикали (номер ряда)
  • w – ширина элемента в колонках
  • h – высота элемента в рядах
  • id – уникальный идентификатор элемента (рекомендуется для восстановления)
  • minW, minH, maxW, maxH – ограничения размеров
  • content или другие кастомные свойства для хранения специфической информации

Для вложенных сеток важно также сохранять состояние дочерних сеток, что превращает состояние в рекурсивную структуру. Пример объекта состояния:

[
  {
    "id": "parent1",
    "x": 0,
    "y": 0,
    "w": 6,
    "h": 4,
    "nestedGrid": [
      {
        "id": "child1",
        "x": 0,
        "y": 0,
        "w": 3,
        "h": 2
      },
      {
        "id": "child2",
        "x": 3,
        "y": 0,
        "w": 3,
        "h": 2
      }
    ]
  },
  {
    "id": "parent2",
    "x": 6,
    "y": 0,
    "w": 6,
    "h": 4
  }
]

Получение состояния сетки

Для получения текущего состояния Gridstack используется метод save() или save(false) (в зависимости от версии):

let grid = GridStack.init();
let state = grid.save(); // Возвращает массив объектов

При использовании вложенных сеток необходимо рекурсивно обрабатывать элементы, которые содержат дочерние сетки. Например:

function saveNestedGrid(grid) {
    return grid.save().map(widget => {
        let nestedGridEl = widget.el.querySelector('.grid-stack');
        if (nestedGridEl) {
            let nestedGrid = GridStack.getGrid(nestedGridEl);
            widget.nestedGrid = saveNestedGrid(nestedGrid);
        }
        return widget;
    });
}

let fullState = saveNestedGrid(GridStack.getGrids()[0]);

Восстановление состояния сетки

Для восстановления состояния используется метод load():

grid.load(savedState);

Для вложенных сеток необходимо загружать состояние рекурсивно:

function loadNestedGrid(grid, state) {
    grid.removeAll();
    grid.load(state.map(widget => {
        let w = { ...widget };
        delete w.nestedGrid; // удаляем вложенную сетку для загрузки
        return w;
    }));

    state.forEach(widget => {
        if (widget.nestedGrid) {
            let el = grid.getCellFromNode(document.getElementById(widget.id));
            let nestedGridEl = el.querySelector('.grid-stack');
            let nestedGrid = GridStack.init({ staticGrid: false }, nestedGridEl);
            loadNestedGrid(nestedGrid, widget.nestedGrid);
        }
    });
}

loadNestedGrid(GridStack.getGrids()[0], fullState);

Хранение состояния в браузере

Состояние удобно сохранять в localStorage или IndexedDB для восстановления после перезагрузки:

localStorage.setItem('gridState', JSON.stringify(fullState));
let savedState = JSON.parse(localStorage.getItem('gridState'));

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


События для автоматического сохранения

Gridstack.js поддерживает события change и added, которые позволяют автоматически отслеживать изменения:

grid.on('change', function(event, items) {
    let state = saveNestedGrid(grid);
    localStorage.setItem('gridState', JSON.stringify(state));
});

Для вложенных сеток рекомендуется также подписываться на события дочерних сеток:

function attachNestedSave(grid) {
    grid.on('change', () => {
        let state = saveNestedGrid(GridStack.getGrids()[0]);
        localStorage.setItem('gridState', JSON.stringify(state));
    });

    grid.engine.nodes.forEach(node => {
        let nestedEl = node.el.querySelector('.grid-stack');
        if (nestedEl) {
            let nestedGrid = GridStack.getGrid(nestedEl);
            attachNestedSave(nestedGrid);
        }
    });
}

attachNestedSave(GridStack.getGrids()[0]);

Практические рекомендации

  • Использовать уникальные ID для всех элементов, особенно если элементы содержат вложенные сетки.
  • Ограничивать глубину вложенности, чтобы избежать чрезмерно сложной рекурсии.
  • При динамическом добавлении элементов не забывать инициализировать вложенные сетки перед сохранением состояния.
  • Проверять совместимость версий Gridstack.js: методы save() и load() могут изменяться между версиями.
  • При больших сетках использовать дебаунсинг событий change, чтобы уменьшить частоту записи состояния в localStorage.

Пример полной структуры HTML для вложенной сетки

<div class="grid-stack" id="parentGrid">
    <div class="grid-stack-item" id="parent1">
        <div class="grid-stack-item-content">
            <div class="grid-stack nested-grid"></div>
        </div>
    </div>
    <div class="grid-stack-item" id="parent2">
        <div class="grid-stack-item-content"></div>
    </div>
</div>

Инициализация:

let parentGrid = GridStack.init({ cellHeight: 80 }, '#parentGrid');
parentGrid.engine.nodes.forEach(node => {
    let nestedEl = node.el.querySelector('.nested-grid');
    if (nestedEl) {
        GridStack.init({ cellHeight: 40 }, nestedEl);
    }
});

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