gridstack / gridstack.js

Build interactive dashboards in minutes.
https://gridstackjs.com
MIT License
6.3k stars 1.26k forks source link

Updating DOM stack elements within the captured change event are not reflected. #2686

Closed adholland closed 1 week ago

adholland commented 1 month ago

Subject of the issue

Currently when performing a tab of the grid items the focus jumps around, as the tabIndex follows the order in which the items were added to the grid, rather than its current location on the grid. I was hoping to rearrange the DOM elements, within the change event, so that I can arrange the elements differently. I'm attempting to order the elements to enable the tabIndex to work top row, left to right, and then next row and so on.

On receiving the change event I would like to update the stack item element positions within the grid. I am able to adjust when adding a new stack item, just not after dragging an item to a new location.

Your environment

VueJS3 Gridstack.js - 10.1.2

Steps to reproduce

Creating a new widget (stack item) allows the item to be updated as required in the DOM, adding it to the correct location.

function createWidget(newWidget) {
  console.log('create widget', newWidget)
  closeWidgetConfigDialog()

  const marker = widgetCreationMarker++
  newWidget.marker = marker
  newWidget.id = `widget${marker}`
  newWidget.text = `widget ${marker}`
  newWidget.gridPosition = newWidget.y * GRID_MAX_COLUMNS + newWidget.x

  widgets.value.push(newWidget)
  //setNextAvailableSpace(newWidget)

  nextTick(() => {
    grid.addWidget(getWidgetElement(newWidget.id))

    //console.log('updating newWidget...', newWidget.id)
    const currentElement = getWidgetElement(newWidget.id)
    const insertBeforeId = findInsertBeforeElementId(newWidget.id, newWidget.gridPosition)
    if (insertBeforeId) {
      const insertBeforeElement = getWidgetElement(insertBeforeId)
      //console.log('insertBefore newWidget', insertBeforeId, currentElement, insertBeforeElement)
      currentElement.parentElement.insertBefore(currentElement, insertBeforeElement)
    } else {
      //console.log('appendChild newWidget', currentElement)
      currentElement.parentElement.appendChild(currentElement)
    }
  })
}

However doing similar within the change event has no affect. This is required to update any items impacted by an item being dragged into a new location. I need to update all affected items. The relevant elements are located as expected, but the insertBefore and appendChild in these case have no effect.

grid.on('change', (ev, gsItems) => {
    ev.preventDefault()
    gsItems.forEach((item) => {
      const foundWidget = getWidget(item.id)
      foundWidget.h = item.h
      foundWidget.w = item.w
      foundWidget.x = item.x
      foundWidget.y = item.y
      foundWidget.gridPosition = foundWidget.y * GRID_MAX_COLUMNS + foundWidget.x

      console.log('onchange updating...', foundWidget.id)
      const currentElement = getWidgetElement(foundWidget.id)
      const parentElement = currentElement.parentElement
      console.log('parentElement', parentElement)
      const insertBeforeId = findInsertBeforeElementId(foundWidget.id, foundWidget.gridPosition)
      if (insertBeforeId) {
        const insertBeforeElement = getWidgetElement(insertBeforeId)
        console.log('insertBefore', insertBeforeId, currentElement, insertBeforeElement)
        parentElement.insertBefore(currentElement, insertBeforeElement)
      } else {
        console.log('appendChild', currentElement)
        parentElement.appendChild(currentElement)
      }
    })
  })
})

I would really appreciated any assistance/advice on this as I'm pretty stuck.

Thanks.

adumesny commented 1 month ago

personally I would use html tabindex rather than try to re-order the items DOM. could GS do that for you, sure... given the right donation that's something that could work out of the box.

adholland commented 1 month ago

Hi @adumesny.

Thanks for getting back to me so quickly. I love GridStack, but this issue could well be a show stopper for me as I dont see how someone using the keyboard can traverse the grid in an intuitive way.

Just wondering how the tabIndex would work given the DOM elements are out of grid order. I don;t see how that would work, really without re-ordering the DOM, when using tabIndex="0". The only other way I can think is to use positive values which I would imagine would impact the rest of the HTML page.

On top of this I was hoping a user could traverse grid items using the keyboard cursor. However that is secondary to the tabIndex issue.

Thanks for your help and time. Much appreciated.

JeffDerk commented 1 month ago

Hi maybe this will help

this._grid.on('change', (event, items) => { const widgets = this._grid.getGridItems(); const nodes = widgets.map(i => i.gridstackNode); const sortedWidgets = GridStack.Utils.sort([...nodes], 1).map(n => n.el);

let moves = [];
for (let i = 0; i < nodes.length; i++) {
    const actual = widgets[i];
    const expected = sortedWidgets[i];
    if (actual != expected) {
        moves.push({
            from: i,
            to: sortedWidgets.indexOf(actual),
            el: actual
        });
    }
}

clearTimeout(this.moveItemsHandle);
this.moveItemsHandle = setTimeout(() => {
    for (let move of moves) {
        let refocus = false;
        if ($(move.el).is(':focus')) {
            refocus = true;
        }
        this._grid.el.insertBefore(move.el, this._grid.el.childNodes[move.to]);

        if (refocus) {
            move.el.focus();
        }
    }
}, 100);

});

adholland commented 2 weeks ago

Hi @JeffDerk @adumesny I'm attaching my changes that, in all my testing, seems to correctly re-order the DOM when adding and updating grid stack items.

I'd appreciate your views and whether it could be improved in any way. Possibly it could do it in a more performant way?

Possibly it could be used to assist others. I'm sure this is something that would be required by others requiring basic accessibility to provide correct tab ordering.

The html file contained, provides a usable gridstack, allowing grid items to be added to the grid. The items can be added and moved and the DOM order is maintained.

vue3.zip

adumesny commented 1 week ago

closing since you appear to have a workaround. as I said this coudl be added to GS if you want to sponsort that feature...