Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to drag selected group #24

Closed
elmarti opened this issue May 29, 2018 · 19 comments · Fixed by #71
Closed

Ability to drag selected group #24

elmarti opened this issue May 29, 2018 · 19 comments · Fixed by #71
Assignees
Milestone

Comments

@elmarti
Copy link

elmarti commented May 29, 2018

Is your feature request related to a problem?
When drag selecting multiple elements in a form builder, I would then like to be able to drag them as a group
Describe the solution you'd like
I would like a callback for holding a continuous click on an individual element which is selected alongside numerous others

@ThibaultJanBeyer
Copy link
Owner

Hmm, since there are already callbacks for continuous clicks, I’m not sure what you mean. Would you be so kind to elaborate your issue further? Thank you

@elmarti
Copy link
Author

elmarti commented May 30, 2018

Hi, thanks for the response, sorry for not being clear:

As a user, I want to be able to select multiple blocks, so that I can drag them as a group.

I'm currently working on a form builder where I'm looking to add the ability to (drag) select multiple form elements and then drag the selected group of elements as a group elements as a group.

The idea I have is kind of like this - the concept, not the implementation:
https://konvajs.github.io/docs/drag_and_drop/Drag_a_Group.html
only the user would be able to drag select the group

@ThibaultJanBeyer
Copy link
Owner

ThibaultJanBeyer commented May 30, 2018

Hey @elmarti, thanks for clarifying. Yes. That is indeed a feature I intended to add. However, it is a lot of work and also a lot of code to add. Ideally functionality would be separated in separete plugins. I have a plugin for drag and drop functionality dragNdrop. dragNdrop unfortunately is only capable to handle a single element at a time for now. In my opinion, this would be solved the best by implementing a functionality inside dragNdrop to be able to handle multiple elements first. Then you could use DragSelect to create the selection, which would pass the selected elements to dragNdrop which would make it possible to drag and drop the group of items.

As I’m working on this alone it will take a while. If you feel confident enough, I would love your help to achieve this.

Thanks a lot for sharing. Now I know that there is a valid usecase for this functionality.

@ThibaultJanBeyer
Copy link
Owner

I created a corresponding ticket inside dragNdrop

@elmarti
Copy link
Author

elmarti commented May 30, 2018

Great, I'll look into it and see if I can be of any help 😄

@n1crack
Copy link

n1crack commented Sep 27, 2018

hey..

how can I prevent reset selection on mousedown event ? I want to keep my selection and after that make a condition between dragging and starting selection.

I made it this far. but couldn't find a way to keep my selection.. when pressing mousedown .5sec it starts dragging, and stops DragSelect. (and also resets selection :( ) I want to keep my selection in that .5sec. of course it can reset on a mousemove event. Any ideas ?
pic

@ThibaultJanBeyer
Copy link
Owner

Yes, that’s quite difficult because how can you know if someone wants to move the files or to set a new selection?

I can think of some hacky way like on a selection callback setting a break for new selection or stopping the functionality. Something like this:

var ds = new DragSelect({
  callback: function(elements) {
    this.stop();  // but how do you know that the selection is completed?
  }
});

// then restart it somehow after the drag of the file?
ds.start();  // reset the functionality after a teardown
// but how do you know that the drag is done?

In your code, you would have to somehow detect that the user wants to move the selection instead of reselecting then using ds.stop() to remove the event listeners for DragSelect and afterward start it again with ds.start().

Maybe the multiSelectMode could also be helpful for you?

As I see this use-case more and more often, I really want to build that Drag after selection functionality. I have a clear idea on how to write such a plugin on top of this one. However, it’s not trivial and I’m in lack of time currently… So bear with me on that one please :/

@elmarti
Copy link
Author

elmarti commented Oct 1, 2018

In this instance I would expect a new selection to not be set when the event target is one of the selectables nodes. In that instances it should allow the dragging of the selectable elements.

@n1crack
Copy link

n1crack commented Oct 1, 2018

what about selection on mouseup and mousemove event instead of mousedown? mousedown clears the selection directly which makes impossible to get current selection.

I want to make a timeout on mousedown event

    let delay = setTimeout(() => {
        // I need to get the current selection here. which is already wiped.
        selectable.stop();
    }, 500);

and clear it on mousemove event

    clearTimeout(delay);
    // we can wipe selection here.. Then recalculate? 

@ThibaultJanBeyer
Copy link
Owner

Ok guys, based on your request I’ve added onDragStartBegin callback so you can do the following:

onDragStartBegin: function(event) {
    if(this.getSelection().indexOf(event.target) > -1) {
      this.break();
    }
}

Which solves your specific use-case. I know that it’s not ideal, but it will unblock you for now.

@n1crack
Copy link

n1crack commented Oct 3, 2018

Can we remove
this.checkIfInsideSelection(true);
from _startUp(mousedown) function. And add it to mouseup event? That’s what I need I think.

Currently I’m dragging selected items with alt+click and selecting with only left click. But I think It is better to make it activate dragging by holding left mouse in certain time

@bxchang04
Copy link

bxchang04 commented Apr 6, 2020

I'm currently also in need of this feature. Will see if I can get it working for my app. If there's any way I can contribute to this project I'd be glad to do so as well. However if anyone has already begun work on this feature, I'm wondering if you could share any insights or updates you may have so we don't retread the same ground :)

@ThibaultJanBeyer
Copy link
Owner

Yes I'm working on it. It's not an easy one tho' it's a big task and between work and home there is not much time, I fear that you'll have to be patient :/
Maybe you can combine it with DragNDrop library for now. That should work and is also kinda how I would solve it

@elmarti
Copy link
Author

elmarti commented Apr 7, 2020

As the original creator of this issue, I thought i'd share 2 solutions I've come up with in the past 2 years. Although this is a great library, I ended up making something custom for the very specific use cases.

Overlaying
Creating an overlay which sits on top of the selected elements, containing an anchor to drag and other options relating to the group selection.
Benefits

  • The selected elements are obscured, so less risk of the drag affecting them
  • The overlay makes the grouping very clear

Drawbacks

  • This can hide the content of the selected

Translating each selected element
Adding/removing a class to the element on selection/deselection. When the user drag within one of these classes, the absolute position of the classes is translated relative to the cursor.
Benefits

  • Selected content remains active and visible

Drawbacks

  • The above benefit can be cumbersome
  • I didn't implement this in a grid setting, i.e only freehand drag and drop

I'm happy to help with any of these features if you want to unload some work onto me. When I opened this issue I was new to DnD, but now i've been around the block with it.

@alextate4
Copy link

alextate4 commented Apr 30, 2020

I am also trying to move SVG elements in groups. How do you place multiple SVG items within a <g></g> tag once the DragSelect box touches them? The console is telling me that each item is being put inside of an array, but how do I wrap that array inside of a group?

new DragSelect({ selectables: document.querySelectorAll('.drag-svg'), area: document.getElementById('floor-map'), onElementSelect: function(el) { el = this; this.toggleClass('.item'); //remove current class of item that makes elements draggable this.wrap("<g class='item'></g>"); //add nodes inside of a group and places the class of item on the group }, callback: e => console.log(e) });

Nothing happens to the elements.

@ThibaultJanBeyer
Copy link
Owner

Man how fast time flies :O

@elmarti thanks a lot for your input. In fact, it's exactly the two solutions I also thought of! 👍

Yes, the overlay is also what we used in our company in combination with DragSelect. I think it's the easiest solution. But from the usability, I would prefer the second solution! Because the intention of DragSelect is to mimic the native os drag solution in your OS and the behavior is the following (try it on some files on your desktop i.e.):

  • You select multiple files
  • If you click and hold on one of the selected files it will drag the whole group.
    That is what I was envisioning. But due to life and work I never got the time. If you want to give it a shot, I would be very happy. I would not work with classes, however (we already add classes for selected elements and the user can opt to disable them) I would rather work with checking whether the elements are in the selection when dragging. I would also like to use the DragNDrop library for this. Just to dogfood and not reinvent the wheel.

@alextate4
Copy link

I don't understand how to check for elements in the selection. After I figure that out, how can I wrap them in an SVG <g></g> tag ?

      new DragSelect({
            selectables: document.querySelectorAll('.drag-svg'),
            area: document.getElementById('floor-map'),

            onDragStart: function(group) { 
                group = $('.shadowGroup'); //this is an empty container I am trying to put items in one by one. 
                dragSVG = $('.drag-svg'); 
                dragSVG.appendTo(group); //for each item that is selected I want to append one by one
                dragSVG.removeClass('item'); //this isn't working. for each item when this happens individually....
            },
            callback: e => console.log(e) 
          });
});

I am using the plain-draggable library. I can switch to your library but I want to keep constraints for objects that were worked into his library like this:

JS FIddle Lines 3-18

This next section sets constraints so SVGs cannot touch. The problem with this library is that I can only use getElementbyID and I need to map every single item, instead of just calling a single className. Is there a an easier way to set these constraints with DragNDrop?

 const items = Array.from(document.querySelectorAll('.item'))
 .map(element => new PlainDraggable(element, {onDrag(moveTo) {
   const that = this,
    xDir = moveTo.left > that.left ? 1 : moveTo.left < that.left ? -1 : 0,
    yDir = moveTo.top > that.top ? 1 : moveTo.top < that.top ? -1 : 0;
      while (items.some(item => {
      if (item === that || !(
        moveTo.left + that.rect.width > item.left && moveTo.left < item.rect.right &&
        moveTo.top + that.rect.height > item.top && moveTo.top < item.rect.bottom
        )) { return false; }

        constxBackLen =
          xDir > 0 ? moveTo.left - (item.left - that.rect.width) :
          xDir < 0 ? item.rect.right - moveTo.left :
          that.rect.width + that.rect.height + item.rect.width + item.rect.height, // Max length

      yBackLen =
         yDir > 0 ? moveTo.top - (item.top - that.rect.height) :
         yDir < 0 ? item.rect.bottom - moveTo.top :
         that.rect.width + that.rect.height + item.rect.width + item.rect.height; // Max length
         if (xBackLen <= yBackLen) {
         moveTo.left += xBackLen * -xDir;
        } else {
          moveTo.top += yBackLen * -yDir;
        }
          return true;
        }))  { /* empty */}
        }}));

        const objects = Array.from(document.querySelectorAll('.stationary'))
        .map(element => new PlainDraggable(element, {onDrag(stayThere){
            stayThere.remove();
        }}));

Thank you!

@ThibaultJanBeyer ThibaultJanBeyer self-assigned this Dec 13, 2020
@ThibaultJanBeyer ThibaultJanBeyer added this to the v2 milestone Dec 13, 2020
@ThibaultJanBeyer
Copy link
Owner

ThibaultJanBeyer commented Dec 13, 2020

Just FYI, you probably by now, have your own workaround but I started to work on this.

There is still a lot to do, so since this is my free-time it'll still take a while, but here is a sneak-peek for you to enjoy :D

dragselect_move

I can't say any specific date but I aim to release it as new functionality in version 2 (since it's a breaking change as it'll be enabled by default) and I think I'll make it a native integration/functionality of the tool itself

Cheers and stay tuned 🍻

€dit: would be nice to have some beta testing here. I pushed the changes to the branch version/2.0.0 please give it a try with your set-up :)

_€dit: intial add in commit 8f99e7a41cb18f9e90b0e59bee71d93d396d67c7

@ThibaultJanBeyer
Copy link
Owner

ThibaultJanBeyer commented Dec 20, 2020

Quick Update

  • The mouse interaction seems now to work fine
  • Added keyboard interaction for accessibility:
    dragselect_keyboard

Next up:

  • Add various automatic tests for that new functionality but also conduct some manual testing in various browsers and also mobile
  • Fix bugs that are found while testing

(see todos, for v2 only tests are missing)

@ThibaultJanBeyer ThibaultJanBeyer linked a pull request Dec 26, 2020 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants