Search code examples
angulardrag-and-dropdraggable

using ng2-dragula in 2018


Can't seem to implement ng2-dragula nested drag and drops even though people seem to be finding solutions to the "nested" hurdle I can't personally pass that barrier.

I have what I think is a simple layout with a source dragula and multiple destination dragulas, the trick comes at trying to drag and drop those destination groups as well ("Client" instances are draggable here).

enter image description here

obviously by default the click and drag action triggers both the child and parent dragulas.

but when trying to implement any of the moves, invalid, accepts dragula options, they trigger a wide array of exceptions.

accepts gets an undefined for target but the most reccurent error is

TypeError: sourceModel.splice is not a function

which I get for both moves and invalid.

github isn't much help as the devs have better things to do (and in the case of the vanilla dragula are long gone).

the problem here is everyone online swears by N variants of the following solutions :

  options = {
    direction: 'horizontal',
    isContainer: function (el) {
      return el.classList.contains('bag-two');
    },
    moves: function(el, container, target) {
      return target.classList.contains('handle');
    }
  };

options = {
    direction: 'horizontal',
    moves: function (el, container, handle) {
      return handle.classList.contains('handle');
    },
}
  options = {
    direction: 'horizontal',
    invalid: (element, source, handle, sibling, event) => {element.className.includes('bag-one')},
  };

the fact of the matter is in 2018 and in angular/ng2-dragula none of them work.

almost all of them throw TypeError: sourceModel.splice is not a function

here's my html :

<div class="root" [ngClass]="' ' + opened">
  <div class="customer-groups-view flex">
    <div class="sidebar">
      <div class="ellipsis">Nouveau Clients : </div>
      <div class="allow-scroll-bottom">
        <div *ngFor="let una of unassigned" [dragula]="'bag-one'" [dragulaModel]="una">
          <app-pane [titleI]="una" [valueI]="una"></app-pane>
        </div>
      </div>
    </div>


    <div class="customer-groups flex-1">
      <div class="flex allow-scroll-right">
        <div *ngFor="let group of groups" [dragula]="'bag-two'" [dragulaModel]="group" [dragulaOptions]="options" class="bag-two flex">
          <div class="customer-group ff" >
            <div class="group-title ellipsis">Client</div>
            <div class="elem-list" (click)="$event.stopPropagation()">
              <div *ngFor="let elem of groupList" [dragula]="'bag-one'" [dragulaModel]="elem">
                <app-pane delayDrag  [titleI]="elem" [valueI]="elem"></app-pane>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

and my ts :

import {AfterViewInit, Component, ElementRef, OnInit} from '@angular/core';
import {conf} from '../common/services/variables';
import {InternationalizedNotifService} from '../common/services/i18n-ed.notif.service';
import {SnotifyService} from 'ng-snotify';
import {Apis} from '../common/api/apis';
import {StoreService} from '../common/services/store.service';
import {DragulaService} from 'ng2-dragula';
import dragula from 'dragula';
import autoScroll from 'dom-autoscroller';

@Component({
  selector: 'app-groups',
  templateUrl: './groups.component.html',
  styleUrls: ['./groups.component.scss']
})
export class Groups implements OnInit, AfterViewInit {

  options = {
    direction: 'horizontal',
    invalid: (element, source, handle, sibling, event) => {element.className.includes('bag-one')},
  };
  justCalledGetCustomersAPItimeout;
  resetCalled = false;
  opened = false;
  scrollAmount;
  data;
  scroll: any;

  groupList = [
    'aadsd',
    'zqds',
    'yghibv',
  ];
  unassigned = [
    'aaappdd',
    'a8d',
    'aaappdd',
    'aaappdd',
    'aaappdd',
    'aa1',
  ];
  groups = [
    'aaadzdaze',
    'aaadzdaze',
    'aaadzdaze',
    'adf',
    'aaadzdaze',
  ];


  constructor(
    private api: Apis,
    private store: StoreService,
    private element: ElementRef,
    private notif18nMessages: InternationalizedNotifService,
    private snotifyService: SnotifyService) {
  }

  ngOnInit() {
    this.doCallWithNotif('getCustomers');
    this.store.topBarOpened$.subscribe(bool => {
      if(bool === true || bool === false) this.opened = bool;
    }, (error) => console.log(error), () => {});

  }

  ngAfterViewInit(){
    const th = this;
    this.scrollAmount = this.element.nativeElement.querySelector('.allow-scroll-right');
    this.scrollAmount.addEventListener('wheel', e => {
      th.scrollAmount.scrollLeft -= (e.wheelDelta)/1.5
    }, false);
  }

EDIT ::

In fact the best behavior I can get is when I delete all the [dragulaModel]s at least then I get desired behavior on one of the the draggable sets : the clients.

dragging a child will result in a successful drag of the parent and then a :

TypeError: Cannot read property '0' of undefined

Solution

  • update (21-11-2018) and side-note :

    I ended up not using dragula as the angular-dragula maintainer himself advised me against it. I ended up implementing native drag and drop without the use of any libraries, it works much better and gives me more options

    answer :

    ok solved it:

    you have to put the [dragula]="'bag-two'" [dragulaModel]="group" [dragulaOptions]="options" on top of the *ngIf not whithin.

    also make sure to be pointing to arrays or arrays not arrays of strings.

    also options as an argument within the DOM won't work.

    At last the code will look like this :

    <div class="root" [ngClass]="' ' + opened">
      <div class="customer-groups-view flex">
        <div class="sidebar">
          <div class="ellipsis">Nouveau Clients : </div>
          <div class="allow-scroll-bottom" [dragula]="'bag-one'" [dragulaModel]="una">
            <div *ngFor="let una of unassigned">
              <app-pane [titleI]="una[0]" [valueI]="una[0]"></app-pane>
            </div>
          </div>
        </div>
    
    
        <div class="customer-groups flex-1">
          <div class="flex allow-scroll-right" [dragula]="'bag-two'" [dragulaModel]="group">
            <div *ngFor="let group of groupList" class="bag-two">
              <div class="customer-group" >
                <div class="flex"><div class="handle">X</div><div class="group-title ellipsis">Client</div></div>
                <div class="elem-list" [dragula]="'bag-one'" [dragulaModel]="elem">
                  <div *ngFor="let elem of group" class="bag-one">
                    <app-pane [titleI]="elem[0]" [valueI]="elem[0]"></app-pane>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    

    ts:

    import {AfterViewInit, Component, ElementRef, OnInit} from '@angular/core';
    import {conf} from '../common/services/variables';
    import {InternationalizedNotifService} from '../common/services/i18n-ed.notif.service';
    import {SnotifyService} from 'ng-snotify';
    import {Apis} from '../common/api/apis';
    import {StoreService} from '../common/services/store.service';
    import {DragulaService} from 'ng2-dragula';
    
    @Component({
      selector: 'app-groups',
      templateUrl: './groups.component.html',
      styleUrls: ['./groups.component.scss']
    })
    export class Groups implements OnInit, AfterViewInit {
    
      justCalledGetCustomersAPItimeout;
      resetCalled = false;
      opened = false;
      scrollAmount;
      data;
      scroll: any;
    
      groupList = [
        [
          ['aaadzdaze'],
          ['aaadzdaze'],
          ['aaadzdaze'],
        ],
        [
          ['adf'],
          ['aaadzdaze'],
        ],
        [
          ['aaadzdaze'],
          ['aaa'],
        ],
        [
          ['aaadfe'],
          ['aafdfaze'],
        ]
      ];
      unassigned = [
        ['aaappdd'],
        ['aaappdd'],
        ['aaddfsppdd'],
        ['aaappdd'],
        ['aa4'],
        ['aaappdd'],
        ['a8d'],
        ['aaappdd'],
        ['aaap5appdd'],
        ['aaappdd'],
        ['aaappdd'],
        ['aa1'],
        ['aaappdd'],
        ['aaappdd'],
        ['aaddfsppdd'],
        ['aaappdd'],
        ['aa4'],
        ['aaappdd'],
        ['a8d'],
        ['aeappdd'],
        ['aaap5appdd'],
        ['aaappdd'],
        ['aaappdd'],
        ['aa1'],
        ['aaapupdd'],
        ['afadd'],
        ['aaddfsppdd'],
        ['aaappdd'],
        ['aa4'],
        ['aaappdd'],
        ['a8d'],
        ['aaappdd'],
        ['aaap5appdd'],
        ['aaappdd'],
        ['aaappdd'],
        ['aa1'],
      ];
    
    
    
      constructor(
        private api: Apis,
        private store: StoreService,
        private element: ElementRef,
        private notif18nMessages: InternationalizedNotifService,
        private snotifyService: SnotifyService,
        private dragulaService: DragulaService) {
        this.dragulaService.setOptions('bag-two', {
          moves: function (el, container, handle) {
            return handle.className === 'handle';
          }
          // moves: (element, source, handle, sibling) => {
          //   if (handle.className.includes('bag-one')) {
          //     return false;
          //   }
          //   return true;
          // }
        });
      }
    
      ngOnInit() {
        this.doCallWithNotif('getCustomers');
        this.store.topBarOpened$.subscribe(bool => {
          if(bool === true || bool === false) this.opened = bool;
        }, (error) => console.log(error), () => {});
      }