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

cdk-drop: allow drag-and-drop copy behavior #13100

Closed
arlowhite opened this issue Sep 13, 2018 · 49 comments · Fixed by #13743
Closed

cdk-drop: allow drag-and-drop copy behavior #13100

arlowhite opened this issue Sep 13, 2018 · 49 comments · Fixed by #13743
Assignees
Labels
needs: discussion Further discussion with the team is needed before proceeding

Comments

@arlowhite
Copy link

Bug, feature request, or proposal:

In some designs, you want to drag the same item repeatedly to multiple drop zones. In other words, the drag-and-drop system should support copy behavior as well as move behavior.

What is the expected behavior?

Developers should be able to choose whether a drag and drop acts as a move or a copy. This should be determined by the developer's implementation of the (dropped) handler and how they manipulate container data.

What is the current behavior?

<cdk-drop> seems to assume move behavior and has hidden state that prevents the developer from achieving copy behavior. In the (dropped) handler, if the developer leaves a container.data item in the previousContainer, it can be dragged again as expected. However, on the next drag-and-drop event.previousIndex = -1 and event.previousContainer is the wrong cdk-drop

To clarify:

  1. Drag item from cdk-drop source S to A
  2. In (dropped) handler, insert the dragged data item into A's data without removing it from S's data
  3. Drag same item from S to cdk-drop B
  4. Problem: (dropped) event has event.previousIndex = -1 and event.previousContainer is cdk-drop A instead of cdk-drop S.

What are the steps to reproduce?

Providing a StackBlitz reproduction is the best way to share your issue.

StackBlitz starter: https://goo.gl/wwnhMV

I can create a demo if needed.

What is the use-case or motivation for changing an existing behavior?

The current <cdk-drop> behavior is restrictive and doesn't allow for copy-drag designs.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular CLI: 6.2.1
Node: 8.11.4
OS: linux x64
Angular: 6.1.7
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                            Version
------------------------------------------------------------
@angular-devkit/architect          0.7.5
@angular-devkit/build-angular      0.7.5
@angular-devkit/build-optimizer    0.7.5
@angular-devkit/build-webpack      0.7.5
@angular-devkit/core               0.7.5
@angular-devkit/schematics         0.8.1
@angular/cdk                       6.4.7
@angular/cdk-experimental          6.4.7
@angular/cli                       6.2.1
@angular/flex-layout               6.0.0-beta.18
@angular/material                  6.4.7
@angular/material-moment-adapter   6.4.7
@ngtools/webpack                   6.1.5
@schematics/angular                0.8.1
@schematics/update                 0.8.1
rxjs                               6.2.2
typescript                         2.9.2
webpack                            4.9.2

Is there anything else we should know?

I worked-around this issue in my App by forcibly re-creating the <cdk-drop> within my template (toggle via *ngIf).

Also, in my app, I wanted to be able to drag items from the drop zones back to the source zone as a way of removing them. In this case, I remove the data item from the previousContainer.data but nothing in the destination container is changed. Again, I had issues with<cdk-drop> state and needed to apply my hack of re-generating the component.

@josephperrott josephperrott added the feature This issue represents a new feature or feature request rather than a bug or bug fix label Sep 13, 2018
@kanidjar
Copy link

kanidjar commented Sep 13, 2018

Could my issue be related to yours? All i am trying is to "copy" new items to my dropzone.

#13077

@arlowhite
Copy link
Author

@kanidjar seems like the same issue

Look at the (dropped) event properties container, containerIndex, previousContainer, previousIndex and compare them with what you expect. In my case some of these would be wrong after dragging an item a second time.

@kanidjar
Copy link

Confirmed. Using your workaround fixes my issue :)

@SAkhil95
Copy link

SAkhil95 commented Sep 24, 2018

I am also facing the same issue,
I have 3 lists A,B and C where B and C will be my drop zones.
If items are moved from A then I cannot reuse the same item get pushed to other List. So, I guess drag and copy feature is required for to satisfy my requirement.
Any Suggestions / comments will be extremely helpful , Thank You.

@eden6
Copy link

eden6 commented Sep 24, 2018

When dropped inside B or C, you could temporarily call a method inside A that "resets" the array of items being displayed inside a timeout of 0 ms.
Something like:
this.items= []; setTimeout(() => { this.items= this.sourceItems.slice(); }, 0);

@crisbeto
Copy link
Member

We used to have an issue where an item that was dragged into another container wasn't being reverted, however that has been resolved in 7.0.0-beta.1. What other issues are you running into?

@SAkhil95
Copy link

Hey @crisbeto and @eden6 , Thanks for your response.
I will take an example so that the issue I am facing will be conveyed in simple terms.
I have all my employees list in List A, I want to drag drop employees who will be awarded bonuses in Quarter 1 to List B and employees who will be awarded bonuses in Quarter 2 to List C, but there are some employees who will receive in both Q1 & Q2. If I use drag and move feature, I can use the element in only 1 List among B or C . So, I require drag copy behavior instead of drag move so that I can clone Employee X in List B as well as List C. I don't want Employee X to be moved only to either of List B or List C making it not possible to use in both the Lists.

Once again thanks a lot for your responses. 👍

@eden6
Copy link

eden6 commented Sep 24, 2018

@SAkhil95 That's what I had understood. I don't know if a copy will be implemented, but in the meantime if you catch the dropped event on containers B and C, you can reset the employees list displayed in A in order to get fresh items to drag again.
resetEmployeesList(){ this.employees= []; setTimeout(() => { this.employees= this.originalEmployeesList.slice(); }, 0); }
Hope this helps

@crisbeto
Copy link
Member

@SAkhil95 are you also copying the object in your data model after the element has been dropped?

@SAkhil95
Copy link

I have created a simple Stack blitz demo , I just want the items in List A to be copied to List B rather than move.
Please find below:
https://stackblitz.com/edit/angular-rxypv6
Thank You.

@eden6
Copy link

eden6 commented Sep 24, 2018

Here's what I was talking about:
https://stackblitz.com/edit/angular-w5ftfu

@SAkhil95
Copy link

Yes @eden6 , this will certainly help me, Thanks a ton for the alternate solution mate.
Thanks @crisbeto, issue is now resolved with alternate solution provided by @eden6 .
Thank you both once again...

@crisbeto
Copy link
Member

@eden6 you don't need to hack around it with timeouts and resetting the list. You just have to push the item to the new list without removing it from the old one: https://stackblitz.com/edit/angular-ymlmql. Closing the issue since this seems to work now.

@SAkhil95
Copy link

SAkhil95 commented Sep 27, 2018

Hello Guys, is there any easy way to delete items from div if drag dropped out of the div ??
@eden6 @crisbeto

@arlowhite
Copy link
Author

@SAkhil95 I'm not sure if there is an event for dropping outside of a <cdk-drop>. I think the cdkDrag has a number of its own events. In my app, the user "removes" an item by dragging it back to the source <cdk-drop> and in that case I just remove it from the data array of the previousContainer being dragged from.

You'll need to determine what kind of <cdk-drop> it was dropped into, but that's up to you. I just look at the dropId.

@SAkhil95
Copy link

Yes @arlowhite , I was thinking the same to push items back to main List , that will suffice the delete functionality. Thanks you very much.

@johannesheucher
Copy link

@crisbeto @arlowhite
In each of your examples, the left list, although immutable, permutes its items while dragging an item from it and moving it over itself.
Is there a way to keep the items of the left list in place (which means (1) no permutation and (2) no removal of the dragged item when exiting its cdkDrop)?
Or should I open a new issue for that?

@crisbeto
Copy link
Member

I’m not sure I completely get the use case. Are you asking whether it’s possible to disable sorting for a particular list, but still allow items from another one to be dropped into it?

@johannesheucher
Copy link

Yes, I mean (1) disable sorting for a particular list.

But it would also be great, if I could tell a list, that its items don't disappear once they are entering another drop-area (2). In your example above, dragging an item from the left list removes it from this list as soon as it enters the right list. When it is dropped into the right list, it is also recovered in the left list.
I have such a list, which is only a provider of items and its items shall never leave the provider-area but only copies of those items. And the 'plop', when the item is recovered after drop doesn't feel good :)

@crisbeto
Copy link
Member

That can definitely be set up, but the behavior around what happens when the item enters into the disabled list isn't very clear. I'm not sure whether the item would be added at the place where the user's cursor entered the new container or whether it would be appended to the list. Either way it might be frustrating for the user if they were trying to put it into a different position.

@johannesheucher
Copy link

Okay, I understand, thanks for your response. Maybe that disable-sorting-feature should require an immutable list, where you can only drag items out of but not into it.
I just saw that there's already an issue for that: #13340

According to (2): Do you understand what I mean with that? Or shall I make it more clear? For our project, this is a crucial behavior.

@crisbeto
Copy link
Member

If I understand it correctly, you want the item to both stay in the original list and show up in the new one?

@johannesheucher
Copy link

Yes :)

@nayfin
Copy link
Contributor

nayfin commented Oct 18, 2018

@crisbeto think of it as an 'inventory' container that you only pull items from, for a vizio or lucidchart type app. You may want to sort the inventory but you would probably want to do that from a drag handle, and the drag item that we wish to clone may be inside your sortable container. There should probably be an option for limited or infinite cloning of the drag item as well.

I've been thinking of starting a PR for this, but I need some direction. Would you prefer to further configure the cdkDropList directive to be able to fit the requested use case or create a new directive with this functionality in mind?

If a new directive is preferred, I would also like to submit a sister directive, 'canvas', which will: fix dropped items where they are dropped in the container, account for scroll position, offer snap-to-grid, and scroll-on-hover-edge functionality.

If some of the shared functionality was abstracted into a base class, we could start building a library of directives to cover other use cases.

I'll submit a new issue for the canvas directive, I just wanted to get your thoughts first since this is your baby.

@arlowhite
Copy link
Author

@nayfin The canvas idea is interesting.

IMO your first paragraph has ideas that the developer should just implement in their component. Tracking how often something has been dragged and sorting should be up to the developer. I'm not sure what you mean by sorting "from a drag handle".

Ideally, I think features like scroll-on-hover-edge would be some kind of generic Cdk thing. So you could use it on your canvas when dragging something. But other people could use it for other designs that need scroll-on-edge behavior. This would be more work, but add a lot more utility for developers.

@nayfin
Copy link
Contributor

nayfin commented Oct 18, 2018

@arlowhite I agree, unfortunately I don't currently see a way to toggle the sorting feature on the current cdkDropList. So, now when an item is dragged out the UI indicates that it was removed from the list even when there is no developer logic to do so. It then gets added back visually after dropped in the other list. @crisbeto's demo highlights this.

By sorting from a drag handle I mean that it's likely that you have a list of containers with drag items inside, the items you want to drag clone infinitely but you want to allow the user to sort the list of containers to suit their needs.

Heres a quick mock I made using lucidcharts. The up-down arrows represent the sort handles for the container, while the circles are the drag items. This would allow user to sort often used items to the top or wherever they find useful.
screen shot 2018-10-18 at 12 46 56 pm

I know much of this could be executed currently, I don't know how separate drag items will operate inside of a single cdkDropList though. I will get as close as I can with the library as is, and then update with a stackblitz. I'm riding the edge of getting off topic here and I don't want to pollute the thread. I'll start a fresh issue after I get some feedback.

EDIT: I got most of it working here. It was easier than I expected, kudos to the team. I just need a way to toggle off the sorting/transferring or toggle on a clone mode when I don't want to remove the drag item from the list ondragstart. I still have a lot of work to do making a canvas directive, I'll post progress to a new issue as soon as I have a POC.

@nayfin
Copy link
Contributor

nayfin commented Oct 22, 2018

@crisbeto can we please reopen. I don't think there has been a working solution proposed and I am in the middle of two PRs to resolve.

@TKul6
Copy link

TKul6 commented Oct 24, 2018

It really be cool to have this copy behavior, I think it actually solves part of my issue #13796

@nayfin
Copy link
Contributor

nayfin commented Oct 24, 2018

I have a PR ready to merge, that adds a helper function for making copies, similar to the transferArrayItem and moveItemInArray functions. I thought I knew how to implement drag clone functionality, but it's less straightforward than I initially thought. So, it's going to take me a little longer. If anyone else has dug into the source and has ideas please let me know.

mmalerba pushed a commit that referenced this issue Oct 25, 2018
…ne array to another (#13743)

* feat(drag-utils): add utility function for cloning array items from one array to another

creates a utility helper for cloning an array item into the array of the drop container
compliments moveItemInArray and transferArrayItem

partially closes #13100

* feat(drag-utils): add utility function for cloning array items from one array to another

resolves issues surfaced from code review

* feat(drag-utils): add utility function for cloning array items from one array to another

cuts extra space between parens

* removes link in comments, and clarifies function definition as recommended in review

* resolves nits
@nayfin
Copy link
Contributor

nayfin commented Oct 25, 2018

@mmalerba I think this issue is actually two parts: copying an item to a target array in the logic, and adding functionality to tell UI to make a copy of drag item instead of dragging it out of the list then replacing it on drop. Here is a stackblitz highlighting what I described. The function added in #13743 helps with copying the item to the target array but not in addressing requested UI functionality.
If there is a way to configure this behavior in the current implementation, please advice here, I'll be happy to update docs with examples. Else, I think this issue should be reopened or I can create a new issue if you'd prefer that.

mmalerba pushed a commit that referenced this issue Oct 26, 2018
…ne array to another (#13743)

* feat(drag-utils): add utility function for cloning array items from one array to another

creates a utility helper for cloning an array item into the array of the drop container
compliments moveItemInArray and transferArrayItem

partially closes #13100

* feat(drag-utils): add utility function for cloning array items from one array to another

resolves issues surfaced from code review

* feat(drag-utils): add utility function for cloning array items from one array to another

cuts extra space between parens

* removes link in comments, and clarifies function definition as recommended in review

* resolves nits
@TKul6
Copy link

TKul6 commented Oct 28, 2018

Hi @nayfin I've seen the pull request and the final result, it looks fine.
I have one question though: It seems the element being dragged out from the element is removed from the initial container when the drag start, and when the item is dropped it re appears in the initial container.
Is there a particular reason for that? can we change it, so he item won't leave the original container?

Thanks

@nayfin
Copy link
Contributor

nayfin commented Nov 5, 2018

@TKul6 this discussion probably belongs on the PR, not this issue, but I address those concerns in the comment above yours. #13743 doesn't touch the UI, it's just a helper function to facilitate copying from one array to another. I hope to address the UI in a separate PR as soon as I can find some free time.

atscott pushed a commit to atscott/components that referenced this issue Nov 5, 2018
…ne array to another (angular#13743)

* feat(drag-utils): add utility function for cloning array items from one array to another

creates a utility helper for cloning an array item into the array of the drop container
compliments moveItemInArray and transferArrayItem

partially closes angular#13100

* feat(drag-utils): add utility function for cloning array items from one array to another

resolves issues surfaced from code review

* feat(drag-utils): add utility function for cloning array items from one array to another

cuts extra space between parens

* removes link in comments, and clarifies function definition as recommended in review

* resolves nits
@nayfin
Copy link
Contributor

nayfin commented Nov 16, 2018

I have working solution here. I had to do some strange nesting of drop list containers but it works and supports a limit to the copied items. IMPORTANT: I had to use a list inside a list to pull this off and, styling of the drag items is important. display: ablsolute and left and top are set to stack the drag items, this is what gives us the appearance of copying the drag item. It still needs a lot of refinement but I wanted to get it posted as soon as possible since so many people are wanting something like this.
This solution highlights how purpose driven this feature has been designed to be. I dug through the source to try and implement a change to the CdkDragListContainer that would allow this behavior but it is very tightly coupled to the Trello style use case and would have required a ton random if statements all over the place and would muddied the source code. I would like to see a base CdkDropContainer abstracted from the CdkDragListContainer that the CdkDragListContainer would extend to implement all the automatic ui behavior that we see.

@TKul6
Copy link

TKul6 commented Nov 23, 2018

Seems cool to me.
I'm not sure about the fact that when I drag the item back to the source container it seems like there are 2 items of the same source.

The display: absolute might also raise problems in a large scale application.

But other than that it looks great.

Thank you.

@danielmoncada
Copy link

Thanks, I got copy working based on a few suggestions from here. Is there any way to 'disable' the automatic sorting of drop zone items when a dragged item is hovering over them?

@Tommatheussen
Copy link

So, I needed to create a toolbox with drag and drop functionality and recently started rewriting the old components we had, removing old and obsolete libraries which were not maintained any longer.

I actually found a way to have a (somewhat) proper copy behaviour, it was especially challenging due to the fact that we use placeholders to show the location of the new element. After going through the code, I found that, when using a placeholder, the old element just gets replaced in the HTML nodes, so, my solution basically reverses that. Demo can be found here:
https://stackblitz.com/edit/angular-material-drag-copy

What I've got now:

  • A toolbox from which users can drag any element to a dynamic form (using the custom addField method instead of just copying the element over, as there's some custom stuff going on that I left out)
  • Reordering of previously dragged elements inside the form
  • Toolbox items are not being sorted when dragging them (they all are in their own dropList elements)

Main things to take in account:

  • I store the old HTML node element inside the component, and replace it when the dragged is being moved, this is not ideal as it checks the condition every pixel moved...
  • I had to set a display: block !important CSS property on the element, which would be fine for simple element, but more complex ones might not like this

Only final thing I'm encountering now: sometimes there's a very short blinking effect of the placeholder ghost showing, I might be able to get that hidden with some css selectors.

I hope this helps anybody else needing copy behaviour

@nayfin
Copy link
Contributor

nayfin commented Jan 30, 2019

@Tommatheussen there is work in progress to enable custom drop-zones, these could selectively implement drop list methods and behaviors. See #14261. The issue is still open but looking at the current source, it seems like the required pieces are there. I plan attempting a custom drop-zone in a few weeks after finishing up some other stuff. I'll document if I'm successful and submit a PR to publish so that others can do the same. Then, hopefully we can expand the cdk to facilitate other common drag functionalities.

@Tommatheussen
Copy link

@Tommatheussen there is work in progress to enable custom drop-zones, these could selectively implement drop list methods and behaviors. See #14261. The issue is still open but looking at the current source, it seems like the required pieces are there. I plan attempting a custom drop-zone in a few weeks after finishing up some other stuff. I'll document if I'm successful and submit a PR to publish so that others can do the same. Then, hopefully we can expand the cdk to facilitate other common drag functionalities.

That's awesome news, I'll keep an eye out for more details on this. As we have to ship our software in a week or 3, I'll keep my current implementation as is and adapt when it's easier supported on the cdk, thanks for the heads up already :)

@parliament718
Copy link

parliament718 commented Feb 6, 2019

@Tommatheussen Thanks for the example, it really helped. There is an undesirable jitter that occurs when the element it removed and re-added to the DOM (between dragStart and dragMove). I haven't figured out how to get rid of that, it's ugly but not such a major issue.

However, the only additional functionality I need is to be able to drag an element outside of its container to remove it. Has anybody figured this out? Even if you drag the element way outside any drop area, cdkDropListDropped still fires with the original container. It would be great if event.container was null if an element was not dropped into any container, but that is not the case.

Does anybody have any ideas how to remove the element when it's dragged outside?

@jayanlee
Copy link

My suggestion for copy behavior would be to use "copyArrayItem" which is similar to "moveArrayItem". For more info see https://material.angular.io/cdk/drag-drop/api#functions

@abhijitpd
Copy link

abhijitpd commented Mar 14, 2019

Any updates on drag and drop copy requirement.

@DonsWayo
Copy link

Like @jayanlee say, this is a stackblitz example with copy https://stackblitz.com/edit/angular-xjex4y

@PetrosA
Copy link

PetrosA commented Mar 26, 2019

Yes it copies the items correctly, but the UI is not fitting. In the drag event it pulls the item out of the sourceList and thats not the expected behavior. It will confuse the user.
Some update to leave the copy item to his sourceList would be very helpful

@rajibhasan11
Copy link

JQuery DnD have clone or copy mode, in CDK copy is working but drag preview is creating but the item is not showing in the actual list in UI until drag-drop operations completed.

@nimishar1
Copy link

You are right @DonsWayo . But i have done a small quick fix for this.

dragStart(event:CdkDragStart){
const tempTodo = event.source.dropContainer.data.slice();
const indexValue = tempTodo.indexOf(event.source.element.nativeElement.innerText);
const movableValue = tempTodo.splice(indexValue,1).join();
event.source.dropContainer.data.splice(indexValue, 0, movableValue);
}
drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex);
}
}

Copy these functions to you ts code. But there is another catch here, the reason why copy functionality is like that because the element that is dragged might not know whether its going to be put in another droplist or same droplist. This quick fix here works only if you put it in another droplist. If you try to put it or swap inside same droplist it creates a redundant name of the draggable element.

@rajibhasan11
Copy link

Please provide similar behavior like https://jsfiddle.net/drzaus/mP8kY/ in cdk dnd

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 10, 2019
@mmalerba mmalerba added the needs: discussion Further discussion with the team is needed before proceeding label Mar 3, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
needs: discussion Further discussion with the team is needed before proceeding
Projects
None yet