14

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.

7
  • You should definitely make a plunker of fiddle, this is a lot of code to sort through. Commented Sep 16, 2013 at 20:19
  • @ZackArgyle I just clarified my question more. That code wasn't working I was hoping it might help though. Commented Sep 16, 2013 at 21:22
  • I might be wrong, but doesn't Angular UI have a draggable directive? Commented Sep 20, 2013 at 11:56
  • @ErikHonn They have ui-sortable github.com/angular-ui/ui-sortable which is one half of the problem. Commented Sep 20, 2013 at 14:45
  • Is there an example of your code somewhere?? I'm not sure I understand. How, visually, does the user know where they are dropping the item? Do you have placeholder elements in the DOM for the dropped objects? Commented Sep 24, 2013 at 1:30

2 Answers 2

18
+250

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.

Sign up to request clarification or add additional context in comments.

7 Comments

Awesome! I'll turn this into a Github repo once I replace my existing setup with this and update my question with the link.
@MichaelCalkins are you still planning to post this on github?
@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
@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
@turbo2oh you can find an example here clouddueling.github.io/angular-common
|
1

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);
        }
    }
})

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.