ng2-dnd icon indicating copy to clipboard operation
ng2-dnd copied to clipboard

Multi-list sortable with nested lists

Open jimitndiaye opened this issue 8 years ago • 24 comments

I have a situation similar to the multi-list sortable in the demo except that the target list contains nested lists and items may be dragged from the available boxers panel into any of the nested lists in the First Team panel, including the First Team panel itself. I'm not sure how to achieve that. So far everything just gets dropped into the my equivalent of the First Team panel instead of the nested panel.

I was hoping for behavior similar to this

jimitndiaye avatar May 25 '16 13:05 jimitndiaye

Hi Jimit,

Did you try to use dropZones property available across all components? With the help of it, you can drag and drop only in allowed places.

Please have a look at examples coming along with a project and let me know does it suited for you?

akserg avatar May 26 '16 08:05 akserg

Yes I am using the dropzones property. I'm basically I'm trying to build a WYSIWYG editor like in the example I linked above. That library is built for NG1 however. Currently nested lists don't work very well because it always drops in the parent.

jimitndiaye avatar May 26 '16 08:05 jimitndiaye

I will have a look on the weekend.

akserg avatar May 26 '16 10:05 akserg

Hi Jimit,

Several days ago @adrienverge sent PR with new feature uses a custom function to determine where dropping is allowed. Sounds that is what you are looked for. Check example on README.

akserg avatar Jun 05 '16 15:06 akserg

Hi akserg,

It's not just about limiting where items can be dropped. There are other problems that prevent this scenario from being possible. One is the fact that event handlers don't call event.stopPropagation to prevent parent elements from reacting to drag events on child elements. among others. The implementation of the library i linked above will highlight other differences.

jimitndiaye avatar Jun 08 '16 10:06 jimitndiaye

Hi Jimit.

I get it. Let me think about changes I need to incorporate into the library.

On 8 June 2016 at 12:47, Jimit Ndiaye [email protected] wrote:

Hi akserg,

It's not just about limiting where items can be dropped. There are other problems that prevent this scenario from being possible. One is the fact that event handlers don't call event.stopPropagation to prevent parent elements from reacting to drag events on child elements. among others. The implementation of the library i linked above will highlight other differences.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/akserg/ng2-dnd/issues/20#issuecomment-224554182, or mute the thread https://github.com/notifications/unsubscribe/ABVeWbF_heHnCsV48sHLJR6uUgq9zf3Fks5qJp3BgaJpZM4ImkYT .

akserg avatar Jun 09 '16 06:06 akserg

@akserg Have you come to any conclusions?

jimitndiaye avatar Jun 22 '16 09:06 jimitndiaye

HI Jimmit,

Sorry man, not yet. I remember about you concern and do my best to have a look soon.

akserg avatar Jun 23 '16 04:06 akserg

I have a similar problem, I want to be able to drag&drop panels but I also have lists inside the panels I want to d&d and I have input in the lists that I want to be able to drag to mark everything without moving the panels.

guzmo avatar Jul 29 '16 18:07 guzmo

Did ng2-dragula solve your problem without any workarounds?

guzmo avatar Jul 29 '16 19:07 guzmo

ng2-angular does support nested list. However, if you do multiple nested list with copy, will definitely has a hard time.

kat3su avatar Aug 11 '16 02:08 kat3su

I ended up implementing my own NG2 dnd directives inspired by angular 1's "angular-drag-and-drop-lists" linked in the OP above that work great for handling nested lists, supporting both copy and move with custom placeholders.

jimitndiaye avatar Aug 11 '16 02:08 jimitndiaye

@jimitndiaye That's awesome. Do you mind to share it?

kat3su avatar Aug 11 '16 04:08 kat3su

For anyone interested: here are the directives I wrote. Here you go: https://gist.github.com/jimitndiaye/63909447f3c08b2e5e9bfd6e1c675545

Still contains some console.debug messages that you may or may not want to remove. Some of the code could use some cleanup and refactoring but it works for me.

jimitndiaye avatar Dec 14 '16 10:12 jimitndiaye

@jimitndiaye any help on how to use it? can't manage to get it working.

redondogabriel avatar Mar 10 '17 11:03 redondogabriel

@jimitndiaye please make a simple tutorial on how to use your directives. Thx.

victorpigmeo avatar Apr 20 '17 14:04 victorpigmeo

@redondogabriel @victorblq I wrote those directives some time ago so bear with me. Here's a snippet of production code using the drag/drop directives:

<ul tkDropTarget [allowedTypes]="container.allowedTypes" 
                           [allowExternalSources]="false" 
                           [horizontal]="container.horizontal"
                           [dragOverClass]="'dndDragOver'" 
                           (allowDrop)="validateChildComponent($event, container.id)" 
                           (onDrop)="addComponent($event, container.id)"
                           [ngClass]="{horizontal: container.horizontal}" 
                           class="container">
            <li *ngFor="let component of container.components"
                class="container-item"
                [tkDraggable]="component"
                [draggedItemType]="component.type"
                [dragEffect]="'move'"
                [dragClass]="'dndDragging'"
                [dragSourceClass]="'dndDraggingSource'">
                      <ion-toolbar>
                          <ion-title>{{getComponentName(component.type)}}</ion-title>
                          <ion-buttons right>
                              <button primary
                                      title="Settings "
                                      (click)="configureComponent(component)">
                                  <ion-icon name="options"></ion-icon>
                              </button>
                              <button danger
                                      title="Delete "
                                      (click)="removeComponent(component.id)">
                                  <ion-icon name="close-circle"></ion-icon>
                              </button>
                          </ion-buttons>
                      </ion-toolbar>
                      <template [tkComponentOutlet]="component"
                                (componentChanged)="logComponentChanged($event)"></template>
                  </li>
                  <li class="dndPlaceholder container-item "
                      text-center
                      text-capitalize
                      *tkDropTargetPlaceholder>Drop here</li>
              </ul>

This was written using Angular 2.1.0. I'm not quite up to date any changes in syntax in more recent versions of Angular, if any. It is for a WYSIWYG designer allowing drag and drop of nested components. We needed to support arbitrarily deep nesting while allowing drag and drop to and from containers at any level. The key things to note are:

  • The tkDropTarget directive is used on the enclosing ul to mark it as a drop zone.
  • The horizontal property on that directive is a boolean used to indicate whether the children are arranged horizontally or vertically - this is used to calculate placeholder placement. In this instance it is bound to a property. If not specified, horizontal is assumed to be false, i.e items are arranged vertically.
  • The allowedTypes property is used to filter which items are allowed to be dropped to in this zone.
  • The dragOverClass is an optional property that allows you to toggle a CSS class when an item is being dragged over the drop zone e.g toggle a border color etc.
  • I use the allowDrop and onDrop events validate drop items and process dropped items respectively.
  • Within the enclosing ul I use an ngFor to list a bunch of draggable li using the tkDraggable directive. These represent the direct children of the container. Marking them as draggable allows you to use drag and drop to reposition items within the container or even move them from one container to another.
  • dragClass is used to toggle a CSS class on the dragged item
  • dragSourceClass is used to toggle a CSS class on the dragged item's original position
  • The each li contains a template (tkComponentOutlet is very similar to ngComponentOutlet) for embedding a component which may itself be a container, thus resulting in nested lists. In my case, any nested containers reuse this same template above resulting in a form of recursion.
  • Finally the ul has an li marked with tkDropTargetPlaceholder which basically defines the template for the placeholder element shown during drag and drop

If you have any further questions on individual directive properties there are comments in the original gist for every input property.

The relevant CSS classes for the snippet above are as follows:

/***************************** Dropzone Styling *****************************/

/**
 * The drop target should always have a min-height,
 * otherwise you can't drop to it when it's empty
 */
ul.container[tkDropTarget] {
    min-height: 42px;
    width: 100%;
    height: 100%;
    margin: 0px;
    padding-left: 0px;
    // background-color: grey;
   // margin-top: 10px;
    // margin-bottom: 10px;
}

ul.container[tkDropTarget] li{
    // background-color: #fff;
    border: 1px solid #ddd;
    display: block;
    padding: 0px;
}

/**
 * Reduce opacity of elements during the drag operation. This allows the user
 * to see where he is dropping his element, even if the element is huge. The
 * .dndDragging class is automatically set during the drag operation.
 */
ul.container[tkDropTarget] .dndDragging {
    opacity: 0.7;
}

/**
 * Put a border around the drop zone to highlight it during the drag operation
 */
ul.container.dndDragOver  {
    border: 1px solid blue;
}

/**
 * The dndDraggingSource class will be applied to the source element of a drag
 * operation. It makes sense to hide it to give the user the feeling that he's
 * actually moving it. Note that the source element has also .dndDragging class.
 */
ul.container[tkDropTarget] .dndDraggingSource {
    display: none;
}

/**
 * An element with .dndPlaceholder class will be added as child of the drop target
 * while the user is dragging over it.
 */
ul.container[tkDropTarget] .dndPlaceholder {
    background-color: #ddd;
    min-height: 42px;
    display: block;
    position: relative;
}

.container .container-item {
    margin: 10px;
}

jimitndiaye avatar Apr 20 '17 15:04 jimitndiaye

any news on this?

lexyfeito avatar Aug 02 '17 18:08 lexyfeito

@lexyfeito we decide to use a different approach creating a popup with the register that we need to drag and drop (Order in our case), one for each level of nesting. Its a workaround of course.

victorpigmeo avatar Aug 02 '17 19:08 victorpigmeo

@victorblq yeah i had thought about this but damn it, however what i am doing for now is have to set [dragEnabled] on the parent, if [dragEnabled]="false" it allows me to order the child

lexyfeito avatar Aug 02 '17 19:08 lexyfeito

@jimitndiaye Are you still using your directives? I'm trying to do the exact same thing and we're moving from the same NG1 library that you were using, and we keep running into dead ends.

Thanks for any help you can give!

mcblum avatar Nov 01 '17 17:11 mcblum

Works as expected for list in list. You can have the parent list element rearranged and only the children rearranged between themselves.

Adding [dragEnabled] and [dropZones] works as expected for that case.

Only buggy thing I found is when using handler. No way that I found around this.

YavorIvanov avatar Jan 07 '18 21:01 YavorIvanov

If this helps this component have solved all my issues with nested drag and drops and drag handles:

https://github.com/swimlane/ngx-dnd

Follow carefully the README and it works like a charm

stockmind avatar Apr 28 '18 16:04 stockmind

Works for me =)

html

<ul dnd-sortable-container [sortableData]="list" [dropZones]="['parent-zone']">
  <li *ngFor="let item of list; let index = index" dnd-sortable [sortableIndex]="index" [dragEnabled]="activeParent" (mouseenter)="onSorting(true)">
    {{ item.name }}
    <ul dnd-sortable-container [sortableData]="item.subList" [dropZones]="['child-zone']">
      <li *ngFor="let subItem of item.subList; let subIndex = index" dnd-sortable [sortableIndex]="subIndex" [dragEnabled]="!activeParent" (mouseleave) ="onSorting(true)" (mouseenter)="$event.stopPropagation(); onSorting(false)">{{ subItem.name }}</li>
    </ul>
  </li>
</ul>

<p>{{ list | json }}</p>

ts

  activeParent:boolean = false;

  list = [
    { name: '1', subList: [ 
                  { name: '1.1' },
                  { name: '1.2' },
                  { name: '1.3' }
                ]
    },
    { name: '2', subList: [ 
                  { name: '2.1' },
                  { name: '2.2' },
                  { name: '2.3' }
                ]
    },
    { name: '3', subList: [ 
                  { name: '3.1' },
                  { name: '3.2' },
                  { name: '3.3' }
                ]
    }
  ]

  onSorting(isParent:boolean) {
    console.log(isParent);
    this.activeParent = isParent;
  }

spodeniuk avatar Jun 27 '18 21:06 spodeniuk