AngularJS + jQueryUI Drag & Drop


Checkout Angular Module to implement drag-and-drop more seemlessly

In this post, I’ve created a simple demo of integration of jQueryUI draggable/droppables in AngularJS. It’s pretty straight forward to understand the code as I’ve heavily commented it so I decided not to explain it in detail.

DEMO


<!DOCTYPE html>
<html ng-app="App">
<head>
<meta name="description" content="AngularJS + jQuery UI Drag-n-Drop" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.min.js"></script>
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" />
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
<script>
// Bootstrap the Application
var App = angular.module('App', []);
// Set up a controller and define a model, list1 and list2 (empty)
App.controller('dndCtrl', function($scope) {
$scope.list1 = [
{name: 'AngularJS', reject: true},
{name: 'Is'},
{name: 'teh'},
{name: '@wesome'}
];
$scope.list2 = [];
});
// This makes any element draggable
// Usage: <div draggable>Foobar</div>
App.directive('draggable', function() {
return {
// A = attribute, E = Element, C = Class and M = HTML Comment
restrict:'A',
//The link function is responsible for registering DOM listeners as well as updating the DOM.
link: function(scope, element, attrs) {
element.draggable({
revert:true
});
}
};
});
// This makes any element droppable
// Usage: <div droppable></div>
App.directive('droppable', function($compile) {
return {
restrict: 'A',
link: function(scope,element,attrs){
//This makes an element Droppable
element.droppable({
drop:function(event,ui) {
var dragIndex = angular.element(ui.draggable).data('index'),
reject = angular.element(ui.draggable).data('reject'),
dragEl = angular.element(ui.draggable).parent(),
dropEl = angular.element(this);
if (dragEl.hasClass('list1') && !dropEl.hasClass('list1') && reject !== true) {
scope.list2.push(scope.list1[dragIndex]);
scope.list1.splice(dragIndex, 1);
} else if (dragEl.hasClass('list2') && !dropEl.hasClass('list2') && reject !== true) {
scope.list1.push(scope.list2[dragIndex]);
scope.list2.splice(dragIndex, 1);
}
scope.$apply();
}
});
}
};
});
</script>
</head>
<body ng-controller="dndCtrl" ng-cloak>
<div class='list1' droppable>
<div class='btn btn-info btn-block' ng-repeat="item in list1" data-index="{{$index}}" data-reject="{{item.reject}}" draggable>{{item.name}}</div>
</div>
<div class='list2' droppable>
<div class='btn btn-info btn-block' ng-repeat="item in list2" data-index="{{$index}}" data-reject="{{item.reject}}" draggable>{{item.name}}</div>
</div>
</body>
</html>

Updates: More advanced version by Mita Glefler below:


<!DOCTYPE html>
<html>
<head>
<meta name='description' content='AngularJS + jQuery UI Drag-n-Drop'/>
<title>JS Bin</title>
<link href='//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/base/jquery-ui.css' rel='stylesheet'/>
<link href='//netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap.min.css' rel='stylesheet'>
<style>
#dnd-container {
margin-left: auto;
margin-right: auto;
margin-top: 20px;
width: 568px
}
#dnd-container .ui-droppable {
float: left;
border: 2px solid fuchsia;
width: 250px;
padding: 10px;
height: 208px;
border-radius: 10px;
margin-top: 20px;
}
</style>
</head>
<body>
<div ng-app='App' id='dnd-container' ng-controller='dndCtrl' ng-cloak>
<div ui-drop-listener='dropListener' data-model='someArrays.list0'>
<div ui-draggable ng-repeat='item in someArrays.list0' data-index='{{$index}}' class='btn btn-info btn-block'>
{{item.name}}
</div>
</div>
<div ui-drop-listener='dropListener' data-model='someArrays.list1' style='margin-left: 20px'>
<div ui-draggable ng-repeat='item in someArrays.list1' data-index='{{$index}}' class='btn btn-info btn-block'>
{{item.name}}
</div>
</div>
<div ui-drop-listener='dropListener' data-model='someArrays.list2'>
<div ui-draggable ng-repeat='item in someArrays.list2' data-index='{{$index}}' class='btn btn-info btn-block'>
{{item.name}}
</div>
</div>
<div ui-drop-listener='dropListener' data-model='someArrays.list3' style='margin-left: 20px'>
<div ui-draggable ng-repeat='item in someArrays.list3' data-index='{{$index}}' class='btn btn-info btn-block'>
{{item.name}}
</div>
</div>
</div>
<script src='//ajax.googleapis.com/ajax/libs/mootools/1.4.5/mootools-yui-compressed.js'></script>
<script src='//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js'></script>
<script src='//ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js'></script>
<script src='//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js'></script>
<script>
(function () {
var app = angular.module('App', []);
app.controller('dndCtrl', ['$scope', 'someArrays', function ($scope, someArrays) {
$scope.someArrays = someArrays;
$scope.dropListener = function (eDraggable, eDroppable) {
var isDropForbidden = function (aTarget, item) {
if (aTarget.some(function (i) {
return i.name == item.name;
})) {
return {reason:'target already contains "' + item.name + '"'};
} else {
return false;
}
};
var onDropRejected = function (error) {
alert('Operation not permitted: ' + error.reason);
};
var onDropComplete = function (eSrc, item, index) {
console.log('moved "' + item.name + ' from ' + eSrc.data('model') + '[' + index + ']' + ' to ' + eDroppable.data('model'));
};
var eSrc = eDraggable.parent();
var sSrc = eSrc.data('model');
var sTarget = eDroppable.data('model');
if (sSrc != sTarget) {
$scope.$apply(function () {
var index = eDraggable.data('index');
var aSrc = $scope.$eval(sSrc);
var aTarget = $scope.$eval(sTarget);
var item = aSrc[index];
var error = isDropForbidden(aTarget, item);
if (error) {
onDropRejected(error);
} else {
aTarget.push(item);
aSrc.splice(index, 1);
onDropComplete(eSrc, item, index);
}
});
}
};
}]);
app.factory('someArrays', ['$q', '$timeout', function ($q, $timeout) {
var deferred = $q.defer();
$timeout(function () {
deferred.resolve({
someArrays:{
list0:[
{name:'AngularJS'},
{name:'Is'},
{name:'teh'},
{name:'@wesome'}
],
list1:[
{name:'AngularJS'}
],
list2:[
{name:'Is'},
{name:'rather good'}
],
list3:[
{name:'@wesome'},
{name:'MooTools'}
]}
});
}, 50);
return deferred.promise.then(function (result) {
return result.someArrays;
});
}]);
app.directive('uiDraggable', function () {
return {
restrict:'A',
link:function (scope, element, attrs) {
element.draggable({
revert:true
});
}
};
});
app.directive('uiDropListener', function () {
return {
restrict:'A',
link:function (scope, eDroppable, attrs) {
eDroppable.droppable({
drop:function (event, ui) {
var fnDropListener = scope.$eval(attrs.uiDropListener);
if (fnDropListener && angular.isFunction(fnDropListener)) {
var eDraggable = angular.element(ui.draggable);
fnDropListener(eDraggable, eDroppable, event, ui);
}
}
});
}
};
});
})();
</script>
</body>
</html>

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

7 thoughts on “AngularJS + jQueryUI Drag & Drop

  1. Clean code, nice work. Now it needs some css to limit the width of the items, and to give the two droppzones a border. Otherwise it’s a bit hard to see what’s happening.

    Ugly colors example:

    .list1{float:left;border: 2px solid fuchsia;width:250px;padding:10px;height:200px}
    .list2{float:left;border: 2px solid fuchsia;margin-left:20px;background-color:#87cefa;width:250px;padding:10px;height:200px}

  2. Hi Mita,

    Thanks for the suggestions. Yep, the $watch was not needed and I also fixed the bug you raised myself. Sorry I could not merge your gist with mine but I’d attached your gist in the post for more advanced version instead. Thanks once again.

  3. Hey there! I just would like to give you a huge thumbs up for your great information you
    have right here on this post. I am returning to your web site for more soon.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.