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 assoc array, or the object itself) and when I drop it in my list I want it to put itself into the 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 to 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 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:
- KnockoutJS Example: http://bit.ly/15yrf8X
- jQuery demo: http://jqueryui.com/draggable/#sortable
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 modularized for anyone's app.
http://clouddueling.github.io/angular-common
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.
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);
}
}
})