gridstack / gridstack.js

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

Draggable handle not working as expected #2372

Closed cca-developer closed 1 year ago

cca-developer commented 1 year ago

Subject of the issue

When specifying the draggable..handle in the options, it does not seem to respect only that class name for dragging. Instead, anything on the item can still be used to drag it.

Your environment

gridstack v ^7.0.0 tested on both Windows and Linux

This is also a problem with v8, but is not a problem when rolling back to v6

Steps to reproduce

In the fiddle below, note that the options are set with

  draggable: {
    handle: ".drag-handle"
  }

Since that class does not exist in the html, I would expect that the grid items could not be dragged anywhere. Instead, they are draggable by clicking and dragging anywhere in the item.

https://jsfiddle.net/ryvbk4j5/1/

Expected behavior

The draggable option should respect the header class if provided.

adumesny commented 1 year ago

that is by design (fallback). set noMove:true if you don't want to drag

cca-developer commented 1 year ago

The point is not that I don't want to drag. The point is that I do want to be able to drag, but only with the class specified in the handle option. I was trying to provide a minimalist example in the fiddle. Right now, we have the draggable handle specified, but clicking anywhere in the item allows dragging. If I understand it correctly, it should only be draggable based on the handle provided. We are using Angular components, so maybe that has something to do with it? I'll try to come up with a fiddle that better illustrates the problem. But we have a configuration that works as expected with v6 (can only drag from the class specified), and that same configuration can be dragged from anywhere if we're using v7 or v8.

adumesny commented 1 year ago

So your saying the class exist but it's dragging entire item instead of that div? Yeah then it's a bug.

sent from phone. excuse any typ-oh!

On Mon, Jun 19, 2023, 4:55 AM cca-developer @.***> wrote:

The point is not that I don't want to drag. The point is that I do want to be able to drag, but only with the class specified in the handle option. I was trying to provide a minimalist example in the fiddle. Right now, we have the draggable handle specified, but clicking anywhere in the item allows dragging. If I understand it correctly, it should only be draggable based on the handle provided. We are using Angular components, so maybe that has something to do with it? I'll try to come up with a fiddle that better illustrates the problem. But we have a configuration that works as expected with v6 (can only drag from the class specified), and that same configuration can be dragged from anywhere if we're using v7 or v8.

— Reply to this email directly, view it on GitHub https://github.com/gridstack/gridstack.js/issues/2372#issuecomment-1597332891, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA2VRZXP6EC6YQ3IOHJSB5LXMBR7XANCNFSM6AAAAAAZJOI3TM . You are receiving this because you modified the open/close state.Message ID: @.***>

cca-developer commented 1 year ago

Correct. The class exists, but clicking anywhere in the item and dragging moves the entire item. For our application, we have a map, and we want to be able to pan the map, but instead the entire item moves.

adumesny commented 1 year ago

the example you gave has no '.drag-handle' in your dynamically created widgets. you need to provide a running case. closing until you do...

vishaalagartha commented 6 months ago

Kind of along these lines... Is there a way to have gridstack update when a new handle is added? I'm having an issue in Ember where the drag handle is being rendered after the grid-stack-item. Here is an example that illustrates the issue: https://jsfiddle.net/vishaalagartha/gvt4390e/9/

In the example, the entire grid-stack-item remains draggable when I would want only the drag handle to be the starting point for future drags.

adumesny commented 6 months ago

@vishaalagartha try disable drag grid.setStatic(true) and re-enable drag to the new class (hack update the grid.opts directly as there isn't an API from I recall) to have it use that instead. or only enable drag after all your classes are set...

vishaalagartha commented 6 months ago

@adumesny - you're a wizard. Thank you so much.

vishaalagartha commented 6 months ago

@adumesny - do you think there is room for a clearer external API that gridstack could expose here? A function like resetDragHandles or something would make things clearer for developers I think. If so, I'd love to make the contribution!

adumesny commented 6 months ago

Maybe too special case. hummm...

hspaay commented 2 months ago

@cca-developer are you using htmx?

I found that when delayed-loading 'grid-stack-item-content' with htmx, that the handle selector (.drag-target) doesn't work. Grid-stack probably locates the drag-handle element on initial load and isn't aware that the content is swapped out.

A workaround that does work for some reason is to use the cancel handle to exclude the body of the content from dragging. Oddly enough the html with the cancel selector is also loaded with htmx but maybe it is located when a drag starts instead of initial load.

Example delayed-loading tile content using htmx:

<div class="grid-stack-item "gs-id="{{$k}}"
                 gs-min-h="4"
                 gs-size-to-content="false">
    <div class="grid-stack-item-content" style="overflow:unset">

        <div  hx-trigger="intersect once"
            hx-swap="outerHTML"
            hx-params="none"
            hx-get="{{.ReRenderTilePath}}"
        >
      <p>reloading tile fragment...</p>
      <h-loading></h-loading>
        </div>
    </div>
</div>

Where after loading this would contain a header with class="drag-target" and a body. In this case drag-target is not re-evaluated but class="no-drag" is. The example below uses sse events to update the tile content using htmx.

<div class="grid-stack-item "gs-id="{{$k}}"
                 gs-min-h="4"
                 gs-size-to-content="false">
    <div class="grid-stack-item-content" style="overflow:unset">

       <section class="tile-panel"
         hx-trigger="sse:{{.TileUpdatedEvent}}"
         hx-swap="outerHTML"
         hx-params="none"
         hx-get="{{.ReRenderTilePath}}"
         >
             <header class="tile-header drag-target">...</header>
             <main class="no-drag">...</main>
    </section>
    </div>
</div>
<script>
   var options = {
      draggable: {
          handle: ".drag-target",     // doesn't work after htmx-get is invoked
          cancel: ".no-drag"              // this works
     }
    ...
   }
   var   grid  = GridStack.init(options);
</script>

Its a bit of a hassle to do the htmx thing with golang backend on jsfiddle but hopefully this helps someone.

@adumesny not sure if htmx is on your radar but in case it is this might be of interest. Thank you for a great library!

adumesny commented 2 months ago

| Grid-stack probably locates the drag-handle element on initial load and isn't aware that the content is swapped out

@hspaay yes of course. how would GS know ? even if possible to track children DOM changes, redoing drag target everytime something changes (cached at best) would be pretty expensive for eveyone to occure because some framework coders do init() the grid before content is there...

the most GS could offer is an API to redo the event binding (simar to disable move/resize and re-enable) to make it bit easier for you to tell GS to redo it after your content is in the dom, or pass the element directly. There is the drag region but also multiple resize regions so all would need to be removed/added...

and would that be for entire grid, or per widget, or... see it gets complicated quickly. The API is there today, but is not clear from documentation view.

hspaay commented 2 months ago

Indeed, that sounds pretty expensive. Just curious, why does the cancel handler not have this problem?

htmx has a nice callback handler 'hx-on::after-request' which could be used to invoke the API to refresh the handlers. In my use-case only a single tile refreshes when its contents changes so shouldn't be too bad.

Initial testing however shows that using the 'cancel' handler on the body works quite well. So-far it looks like a good solution when using htmx. Maybe useful to include this as a suggestion in the docs. Something along the lines of: "When using htmx to refresh tiles and using the drag-handle, consider using the cancel handler instead as the drag-handle doesn't update after a swap".

Thanks again,