Drag object into sortable list - AngularJS

12,857

Solution 1

There is no angular-magic that can help you find the position of a new or moved element, but it's easy to do with jQuery. I've created an example of the jQueryUI-demo wrapping sortable and draggable in directives:

http://plnkr.co/edit/aSOlqR0UwBOXgpQSFKOH?p=preview

<ul>
  <li my-draggable="#sortable" class="ui-state-highlight">Drag me down</li>
</ul>

<ul my-sortable id="sortable">
  <li class="ui-state-default" ng-repeat="item in items">{{item.name}}</li>
</ul>

Value of my my-draggable is the id to the corresponding my-sortable-element. my-draggable is otherwise pretty straight forward:

app.directive('myDraggable',function(){

  return {
    link:function(scope,el,attrs){
      el.draggable({
        connectToSortable: attrs.myDraggable,
        helper: "clone",
        revert: "invalid"
    });
    el.disableSelection();
    }
  }
})

In my-sortable I listen to the deactivate event which indicates that an element has been dropped. from is the position of the element in the array that is the source of ng-repeat. ng-repeat creates a child scope for each element with an $index variable indicating the position of the current element in the array. If $index is undefined I know that it's a new element (might be a better way to determine this, but it works for this example). to is the new position of the item. I $emit a 'my-sorted' event if an existing element was moved or a 'my-created' event if a new item was added.

app.directive('mySortable',function(){
  return {
    link:function(scope,el,attrs){
      el.sortable({
        revert: true
      });
      el.disableSelection();

      el.on( "sortdeactivate", function( event, ui ) { 
        var from = angular.element(ui.item).scope().$index;
        var to = el.children().index(ui.item);
        if(to>=0){
          scope.$apply(function(){
            if(from>=0){
              scope.$emit('my-sorted', {from:from,to:to});
            }else{
              scope.$emit('my-created', {to:to, name:ui.item.text()});
              ui.item.remove();
            }
          })
        }
      } );
    }
  }
})

In the controller I create the items-array and listen to the events:

$scope.items = [
  {name:'Item 1'},
  {name:'Item 2'},
  {name:'Item 3'},
  {name:'Item 4'},
];

$scope.$on('my-sorted',function(ev,val){
  // rearrange $scope.items
  $scope.items.splice(val.to, 0, $scope.items.splice(val.from, 1)[0]);
})

$scope.$on('my-created',function(ev,val){
  // create new item at position 
  $scope.items.splice(val.to, 0,
    {name:'#'+($scope.items.length+1)+': '+val.name});
})

As you can see, when you add or move an element the model in the scope gets updated.

These directives are not very general - you might have to do some adjustment to get them to work with your application.

Solution 2

You should be able to do every thing you need in your link function.

myapp.directive("menuDrag", function () {

    return{
        restrict: "A",
        link:     function (scope, element, attrs) {
            var item = $(".draggable").draggable(
                {
                    snap:   true,
                    revert: false,
                    // scope:".dropable"
                    //scope: "tasks"
                }
            )
            var target = $(".dropable").droppable({

                greedy:     true,
                hoverClass: "warning",
                accept:     ".draggable"
            })


            item.on("drag", function (evt) {
                item.css = ('background-color', 'red');
                //evt.stopPropagation();
                //evt.preventDefault();
            })


            target.on("over", function (evt) {

                target.css('background-color', 'blue')

                return false;
            });
            target.on("out", function (evt) {
                dropBox.css('background_color', 'red');


                return false;
            });
            target.on("drop", function (evt) {
                alert("Droped");
                return false;
            });
            //dragEnterLeave(evt);
        }
    }
})
Share:
12,857

Related videos on Youtube

Michael J. Calkins
Author by

Michael J. Calkins

My dream is to pursue programming that improves the world allowing me to make a positive impact on people's lives. I want to work with talented people whom I can learn from and become a better software engineer.

Updated on June 05, 2022

Comments

  • Michael J. Calkins
    Michael J. Calkins almost 2 years

    Problem:

    I'm trying to recreate the Draggable + Sortable functionality from jQuery and can't get the dropped element to go into my array of objects.

    I want to drag a $.draggable() button into a $.sortable() list. I want the button to represent an object with properties (could be an associative array, or an object itself) and when I drop it in my list I want it to put itself into the array at the position it was dropped at.


    Just to be clear, I have an array of potential objects in a menu to the left. On the right, I use $http to call my API to retrieve a form that has fields all held in $scope. I want that potential object (like a textarea) to be dropped into that form's fields at the position dropped.

    The jquery bit is straightforward but the non-existent object to the position in $scope array is the problem.


    What I've tried:

    I was close with mixing combing ui-sortable and $.draggable directive wrapper but my code isn't working very well.


    Examples:


    Update 1:

    I have made progress with a ui-sortable like directive combined with a directive that wraps $.draggable(), kinda ugly but works.

    Update 2:

    I have it working now but I grab the index from jquery and use PHP to slice it into that position and then reload the entire list. Talk about lame there must be a better way.

    Update 3:

    Here is a working example of modularized for anyone's app.

  • Michael J. Calkins
    Michael J. Calkins over 10 years
    How would I add the object into my form object held in $scope? If I'm not mistaken I only see the jquery pieces here.
  • Michael J. Calkins
    Michael J. Calkins over 10 years
    Awesome! I'll turn this into a Github repo once I replace my existing setup with this and update my question with the link.
  • turbo2oh
    turbo2oh over 10 years
    @MichaelCalkins are you still planning to post this on github?
  • turbo2oh
    turbo2oh over 10 years
    @joakimbl im just learning angular but if i swap them to inputs and modify their values, why isn't it updated in the $scope.items? plnkr.co/edit/4lztv66RfnJAnIcMlPNs?p=preview
  • Michael J. Calkins
    Michael J. Calkins over 10 years
    @turbo2oh I completely forgot about this but I have to rebuilt the feature I needed this for I'll be putting something up to github.com/clouddueling/angular-common
  • Michael J. Calkins
    Michael J. Calkins over 10 years
    Funny enough in my repo I was trying everything to make this work turns out there is a bug when looping ng-includes with ng-repeat (my comment is at the bottom): github.com/angular/angular.js/issues/3584
  • Michael J. Calkins
    Michael J. Calkins over 10 years
    @turbo2oh you can find an example here clouddueling.github.io/angular-common
  • Jack Shultz
    Jack Shultz almost 10 years
    The problem is this example does not work on my ipad. Any way to get touch support?