bevacqua / angularjs-dragula

:ok_hand: Drag and drop so simple it hurts
https://bevacqua.github.io/angularjs-dragula
MIT License
509 stars 110 forks source link

Unexpected behavior on third level nested tables #91

Open dahamstor opened 7 years ago

dahamstor commented 7 years ago

I am using angular JS 1.5, if that matters (however this particular page is all written in a single component). Everything seems to be working correctly until a third level nested bag is required. Essentially there are three bags and three level objects (the actual names are different): + first-bag item in vm.items second-bag subItem in item.subItems third-bag subSubItem in subItem.subSubItems

IF, very specifically, you drag a second level item (subItem) to another second-bag and then drag a third level item (subSubItem), that is inside this very same second level item, to another third-bag, it seems like the drake.containers array does not update correctly (excess containers are not removed or they are unnecessarily created). When dragging and dropping this third level-item, an error at service.js 50th line (also contained in angular-dragula.js 1072nd line) is triggered -

Cannot read property '0' of undefined at applyDrop (service.js:50)

This happens as a result of the sourceModel being undefined at line 44, which, in turn, happens, because there is one more container than there are models. For an example, if there are two subItems and two subSubItems throughout the whole model, at line 44 (or 1066 in angular-dragula.js) the source variable will match the third container (the number of containers increases the more you drag the second level item between bags), produces the index of 2 (or whatever is the last container), which is not a part of the models array. This results in an undefined SourceModel and in the drag and drop not working. The situation can be viewed in the following images.

image image image

Unfortunately I can't quite upload my source code, but, if there are any ideas as to how this could be fixed or circumvented, please tell me. Thank you for reading and any responses.

dahamstor commented 7 years ago

OK, so I found a bit of a nightmarish way around this. The problem is that, when you move to a second level item to a different first level item, a new container is created, but the old ones weren't deleted. There was a really a subset of three things to account for, when moving second level items - is the second level item being moved "up" in the first level array (needed to compare jQuery attribute's stored item with the newValue), moved "down" in the first level array (needed to check if the jquery attribute was undefined) and make sure the order works (by splicing the element into the correct part of the array). In the current solution, you have to specify the third level object yourself (in the line right below where this is noted in a comment. My link method code ended up like this:

function register (angular) { return ['dragulaService', function angularDragula (dragulaService) { return { restrict: 'A', scope: { dragulaScope: '=', dragulaModel: '=' }, link: link };

function link (scope, elem, attrs) {
  var dragulaScope = scope.dragulaScope || scope.$parent;
  var container = elem[0];
  var name = scope.$eval(attrs.dragula);
  var drake;

  var bag = dragulaService.find(dragulaScope, name);
  if (bag) {

      var spliced = false;
      for (var i = 0; i < bag.drake.containers.length; i++) {
          for (var key in bag.drake.containers[i]) {
              if (key.indexOf("jQuery") == 0) {
                  if (bag.drake.containers[i][key] == undefined) {
                      bag.drake.containers.splice(i, 1, container);
                  }
              }
          }
      }
      drake = bag.drake;
      if (!spliced){
          drake.containers.push(container);
      }

  } else {
    drake = dragula({
      containers: [container]
    });
    dragulaService.add(dragulaScope, name, drake);
  }

  scope.$watch('dragulaModel', function (newValue, oldValue) {
    if (!newValue) {
      return;
    }

    if (drake.models) {
      var modelIndex = oldValue ? drake.models.indexOf(oldValue) : -1;
      if (modelIndex >= 0) {
          for (var i = 0; i < drake.containers.length; i++) {
              for (var key in drake.containers[i]) {
                  if (key.indexOf("jQuery") == 0) {

                       //IMPORTANT! The way I currently designed it (since I needed drag and drop for a single page), you have to specify the third level item here instead of subSubItem

                      if (!spliced && drake.containers[i][key].$scope.$$childTail.subSubItem != undefined && drake.containers[i][key].$scope.$$childTail.subSubItem === newValue[newValue.length - 1]) {
                          drake.containers.splice(i, 1, bag.drake.containers[bag.drake.containers.length - 1]);
                          drake.containers.splice(bag.drake.containers.length - 1, 1);
                          spliced = true;
                      }
                  }
              }
          }
        drake.models.splice(modelIndex, 1, newValue);
      } else {
        drake.models.push(newValue);
      }
    } else {
      drake.models = [newValue];
    }
    dragulaService.handleModels(dragulaScope, drake);
  });
}

}]; }

muhleder commented 7 years ago

We're seeing the same issue getting dragula angular to work with nested items, what's working for us is to amend drake.containers and drake.models when the DOM element is destroyed (which happens when it is dragged out of it's original position).

Adding this to the link function seems to do the trick.

      elem.on('$destroy', function() {
        for (let i = 0; i < drake.containers.length; i ++) {
          if (drake.containers[i] === elem[0]) {
            drake.containers.splice(i, 1);
            drake.models.splice(i, 1);
            break;
          }
        }
      });
jithureddy commented 7 years ago

The above solutions are interesting dimension to think about. I am facing similar issue but the folder bag name is same for nested containers also. When I drop nested container to top container, I am getting the error same as above. It is because the index of the containers and models are not tied to similar index.