Dave Cooper
Dave Cooper

Reputation: 10714

Drag and drop lists based on certain conditions

I am using angular-drag-and-drop-lists (https://github.com/marceljuenemann/angular-drag-and-drop-lists) for my AngularJS project to create two lists that allow me to do the following:

  1. Drag items from list A to list B
  2. Drag items from list B to list A
  3. Reorder items in list A
  4. Reorder items in list B

Using a simple example on the library's site (http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/simple) I can see that these four conditions are easily achievable. However, things start to get hairy when I want to introduce slightly more complex behaviour:

  1. When dragging items from list A to list B, I want something to occur (easily achieved with the dnd-drop callback. This currently works for me
  2. When dragging items from list B to list A, I don't want anything to happen other than a regular drop (i.e. the item winds up in list A and nothing fancy happens). This currently works for me
  3. When reordering items in list A, nothing should happen when an item is re-dropped into it's new position in the list (even if it is being dropped back into it's original position) This is where I am having issues - see below for further explanation
  4. When reordering items in list B, nothing should happen when an item is re-dropped into it's new position in the list (even if it is being dropped back into it's original position). This currently works for me

I am largely adapting the sample code used in the example link provided above. The problem I am having is that the action I want to take place when moving items from list A to list B is also occurring when I reorder things in list A.

The current setup/pseudocode I have is the following:

My lists:

$scope.models.lists = {
    "A": [],
    "B": []
}

I populate these lists with information pulled from a database. Each item in the list also has a property that tracks how many children the item has.

My markup looks like the following:

<ul dnd-list="list"
    dnd-allowed-types="['itemType']"
    dnd-drop="myCallback(event, index, item, external, type, 'itemType')">
    <li ng-repeat="item in list | filter:searchText"
        dnd-draggable="item"
        dnd-moved="list.splice($index, 1)"
        dnd-effect-allowed="move"
        dnd-selected="models.selected = item"
        dnd-type="'itemType'"
        ng-class="{'selected': models.selected === item}"
        ng-dblclick="editProperties();">
            {{item.label + ' - ' + item.description}}
    </li>
</ul>

My callback looks like the following:

    $scope.myCallback= function(event, index, item, external, type, allowedType) {
        // If the item in question has no children then we don't need to do anything special other than move the item.
        if (item.children.length == 0) {
            return item;
        }

        // If moving items around list B then just move them.
        for (var i = 0; i < $scope.models.lists.B.length; i++) {
            if (item.label === $scope.models.lists.B.[i].label) { 
                return item;
            }
        }

        // I only want this code to execute if we know we are moving from list A to list B and the item in question has > 0 children.
        if (item.children.length > 0) {
            // Code that I want to execute only when moving from list A to list B goes here
        }

If anyone is able to assist me with this I will be very grateful.

Thanks!

Upvotes: 2

Views: 4116

Answers (3)

Passerby
Passerby

Reputation: 1

hi i have add a dragstartCallback that set variable in event.dataTransfer for the information of the origin/source of my dragged obj:

    $scope.dragstartCallback = function(event){
       var id  = event.target.id;
       var parent = $('#' + event.target.id).closest('ul')[0].id;
       var group = $('#' + event.target.id).closest('div')[0].id;

       event.dataTransfer.setData("id", id);
       event.dataTransfer.setData("parent", parent);
       event.dataTransfer.setData("group", group);
       return true;
    }

And in the dropCallback i just check if is List A or List B

    $scope.dropCallback = function(event, index, item, external, type, allowedType) {
       $scope.logListEvent('dropped at', event, index, external, type);
       var idDivSource = event.dataTransfer.getData("parent");
       var idDivDestination = $(event.target).closest('ul')[0].id;

       var groupSource = event.dataTransfer.getData("group");
       var groupDestination = $('#' + event.target.id).closest('div')[0].id;

       if(groupSource == groupDestination){

          if(idDivSource != idDivDestination){
             if(idDivDestination == 'IDLISTB'){
                return item?
             }
             if(idDivDestination == 'IDLISTA'){
                DO Something else
             }
             DO Something else
          }

       } 
    }

Html code:

    <div class="associated-drag-and-drop DnD" id="GROUP-MyD&D1" ng-repeat="(listName, list) in models.lists" flex="">
       <ul dnd-list="list" id="{{listName}}" dnd-drop="dropCallback(event, index, item, external, type, 'containerType')" flex="">
          <md-list-item class="associated-list__item" ng-repeat="item in list | filter:searchFilter" id="{{item.id}}"
             dnd-dragover="dragoverCallback(event, index, external, type)"
             dnd-dragstart="dragstartCallback(event)"
             dnd-draggable="item"
             dnd-moved="list.splice($index, 1)"
             dnd-effect-allowed="move"
             dnd-selected="models.selected = item"
             ng-class="{'selected': models.selected === item}"
             class="noright associated-list__item"
             >
             CONTENT
          </md-list-item>
       </ul>
     </div>

Do not use this type of filter "| filter:searchFilter"
But use ng-show="And put here the condition"
Alternative change the reference to $index in dnd-moved="list.splice($index, 1)"

If you dont make this change you will have a problem with the filtered list when drag and drop

Exemple
NOT FILTERED LIST
you will have 2 array in 2 ng-repeat

    ListA                             ListB
    index1 of     value               index2 of     value
    ng-repeat    of list              ng-repeat    of list
       0          aaa                    0          ppp
       1          bbb                    1          qqq
       2          ccc                    2          www
       3         dddaaa                  3         eeerrr
       4         eeeccc                  4         mmmwww
      ecc...                           ecc...

The drag and drop lists work on the index of ng-repeat

FILTERED LIST
Now if we make a filter for 'a' with "| filter:searchFilter" code
angular will make this list

     List A                         List B
      0      aaa
      1      dddaaa

the index when you drag "dddaaa" will be 1 and not 3
so it will not be removed from listA when dropped in listB
becouse the index is not the same as the non filtered list

instead if you use the ng-show="condition"
it will keep the original index of list not filtered

    <md-list-item ng-repeat="item in list"
        ng-show="condition">
    </md-list-item>

My item list:

    $scope.models = {
       lists: {
          "LISTA": [{1},{2},{3}], 
          "LISTB": [{1},{2},{3}]
       }
    };

Upvotes: 0

Chris Lonardo
Chris Lonardo

Reputation: 141

If you're not totally committed to that library (which, btw, doesn't work with a touchscreen) RubaXa's Sortable is the truth: Sortable

The documentation is strong, it's quite performant, and I wish I hadn't wasted my time on other DnD libraries before this one.

Upvotes: 2

bly
bly

Reputation: 1592

Looking at the README, the dnd-drop event fires for any drop - regardless of whether it was within the same list or not.

Going by the script as you have it now, you need to check the event (or pass some additional information into your callback) to determine what list the event is firing on.

Upvotes: 2

Related Questions