index.html
                    
<html ng-app="dragAndDrop">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<style type="text/css">
* {
font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
}
section {
border: 1px solid gray;
border-radius: 1px;
margin-bottom: 10px;
}
ul {
padding-left: 10px;
}
li {
display: inline-block;
width: 160px;
vertical-align: top;
margin: 5px;
cursor: pointer;
box-shadow: 5px 5px 5px 0px #656565;
}
li div {
display: table-cell;
vertical-align: top;
}
.dragdrop-item-controller {
position: absolute;
visibility: hidden
}
</style>
<script type="text/ng-template" id="dragItemTemplate">
<div style="box-shadow: 5px 5px 10px 0px #656565; ">
<img src="{{value.data.url}}"/>
<div style="position: absolute;top:0px;left: 0px;background: rgba(255,255,255,0.8);width: 100%">
{{value.data.title}}
</div>
</div>
</script>
<title></title>
</head>
<body>
<div class="row">
<section class="col-md-4" ng-controller="DraggableController">
<h3>Drag section</h3>
<ul>
<li ng-repeat="item in itemsDrag" drag drop-callback="endDrag($index)" drag-include="'dragItemTemplate'"
drag-model="item">
<div>
<img src="{{item.url}}"/>
</div>
<div>
<strong>{{item.title}}</strong>:<span>{{item.description}}</span>
</div>
</li>
</ul>
</section>
<section ng-controller="Drop1Controller" drop drop-callback="endDrop" drop-mouse-enter="dropClass='bg-info'"
drop-mouse-leave="dropClass=''" class="col-md-4 {{dropClass}}">
<h3>Drop section with highlight</h3>
<ul>
<li ng-repeat="item in itemsDrop" data-count="{{$index}}">
<div>
<img src="{{item.url}}"/>
</div>
<div>
<strong>{{item.title}}</strong>:<span>{{item.description}}</span>
</div>
</li>
</ul>
</section>
<section ng-controller="Drop2Controller" drop drop-callback="endDrop" drop-mouse-move="mouseMove"
drop-mouse-leave="mouseLeave" class="col-md-4">
<h3>Drop section with live insertion</h3>
<ul id="dropcontainer">
<li ng-repeat="item in insertDraggedItem(itemsDrop)" data-count="{{$index}}">
<div>
<img src="{{item.url}}"/>
</div>
<div>
<strong>{{item.title}}</strong>:<span>{{item.description}}</span>
</div>
</li>
</ul>
</section>
</div>
<div ng-controller="DragAndDropController">
<div ng-repeat='(key, value) in items' ng-controller="DragAndDropItemController" ng-init="init(value)"
class="dragdrop-item-controller">
<div style="width: 100%" ng-include src="value.include">
</div>
</div>
</div>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="https://code.angularjs.org/1.2.16/angular.js"></script>
<script src="script.js"></script>
<script type="application/javascript">
function DraggableController($scope) {
$scope.itemsDrag = [];
for (var i = 0; i < 8; i++) {
$scope.itemsDrag.push({
title: "Title " + i,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
url: "http://lorempixel.com/80/80/sports/" + ((i % 8) + 1)
});
}
$scope.endDrag = function (data) {
$scope.itemsDrag.splice(data, 1);
};
}
function Drop1Controller($scope) {
$scope.itemsDrop = [];
$scope.endDrop = function (data) {
$scope.itemsDrop.push(angular.copy(data));
$scope.dropClass='';
}
}
function Drop2Controller($scope, $http) {
$scope.itemsDrop = [];
$scope.insertedIndex = -1;
$scope.inserted = null;
$scope.endDrop = function (data) {
if ($scope.insertedIndex >= 0) {
$scope.itemsDrop.splice($scope.insertedIndex, 0, $scope.inserted);
} else {
$scope.itemsDrop.push(angular.copy(data));
}
$scope.insertedIndex = -1;
};
$scope.insertDraggedItem = function (data) {
var cloned = data.slice(0);
if ($scope.insertedIndex >= 0) {
cloned.splice($scope.insertedIndex, 0, $scope.inserted);
}
return cloned;
};
$scope.mouseLeave = function (evt, data) {
$scope.insertedIndex = -1;
};
$scope.mouseMove = function (evt, data) {
var $container = angular.element(document.getElementById("dropcontainer"));
var $ite = angular.element(evt.target);
while ($ite.length > 0 && $ite.attr("data-count") == null) {
$ite = $ite.parent()
}
if ($ite.length > 0 && $scope.insertedIndex == $ite.attr("data-count")) {
return;
}
if ($scope.itemsDrop.length == 0) {
$scope.inserted = angular.copy(data.data);
$scope.insertedIndex = 0;
} else if ($ite.length > 0) {
console.log($ite.attr("data-count"));
$scope.inserted = angular.copy(data.data);
$scope.insertedIndex = $ite.attr("data-count");
}
};
}
</script>
</body>
</html>
                    script.js
                    
'use strict';
(function (app) {
var $DESTROY = "$destroy"
var MOUSEDOWN = "mousedown";
var MOUSEUP = "mouseup";
var MOUSEENTER = "mouseenter";
var MOUSELEAVE = "mouseleave";
var MOUSEMOVE = "mousemove";
var TOUCHSTART = "touchstart";
var TOUCHEND = "touchend";
var TOUCHMOVE = "touchmove";
var MOUSE_ID = "mouse";
var TOUCHEND_ANGULAR_EVENT = "touchend";
/**
* Controller for dragged item
*/
app.controller("DragAndDropItemController", ['$scope', '$element', '$document' , function ($scope, $element) {
$scope.init = function (value) {
$scope.value = value;
$scope.value.$element = $element;
}
}]);
/**
* Controller for all dragged items
*/
app.controller("DragAndDropController", ['$rootScope' , '$scope', 'dragAndDrop', '$document' , function ($rootScope, $scope, dragAndDrop, $document) {
//Mouse up : d&d ended, remove the associated data
//Need to remove a mouseUp not handled which bubbled to document
var mouseUp = function (evt) {
if (dragAndDrop.hasData(MOUSE_ID)) {
$scope.$apply(function () {
dragAndDrop.removeData(MOUSE_ID);
});
}
};
//Dragged item follow the mouse
var mouseMove = function (evt) {
if (dragAndDrop.hasData(MOUSE_ID)) {
dragAndDrop.getData(MOUSE_ID).$element.css({
top: evt.pageY + 5,
left: evt.pageX + 5,
visibility: "visible"
});
}
};
//Touched items follow the fingers
var touchMove = function (evt) {
for (var ite = 0; ite < evt.targetTouches.length; ite++) {
var currentTouch = evt.targetTouches[ite];
if (dragAndDrop.hasData(currentTouch.identifier)) {
dragAndDrop.getData(currentTouch.identifier).$element.css({
top: currentTouch.pageY + 5,
left: currentTouch.pageX + 5,
visibility: "visible"
});
evt.preventDefault();
}
}
};
//Centralized touchend event, dispatch Touch to all drop directives
//Must implement centralized touch end because the event only occurs on the element that initiate the touch start (not the final element)
var touchEnd = function (evt) {
$scope.$apply(function () {
for (var ite = 0; ite < evt.changedTouches.length; ite++) {
var currentTouch = evt.changedTouches[ite];
if (dragAndDrop.hasData(currentTouch.identifier)) {
$rootScope.$emit(TOUCHEND_ANGULAR_EVENT, currentTouch);
//once emited, remove data
dragAndDrop.removeData(currentTouch.identifier)
}
}
});
};
$document.bind(MOUSEMOVE, mouseMove);
$document.bind(TOUCHMOVE, touchMove);
$document.bind(MOUSEUP, mouseUp);
$document.bind(TOUCHEND, touchEnd);
$scope.$on($DESTROY, function () {
$document.unbind(MOUSEMOVE, mouseMove);
$document.unbind(TOUCHMOVE, touchMove);
$document.unbind(MOUSEUP, mouseUp);
$document.unbind(TOUCHEND, touchEnd);
});
$scope.hasItems = function() {
return $scope.items.length > 0;
};
$scope.items = dragAndDrop.datas;
}]);
/**
* Drag&Drop service, holds dragged item data
*/
app.factory('dragAndDrop', function () {
var Service = function () {
this.counter = 0;
this.datas = {};
};
Service.prototype.setData = function (id, data, success, include, width, height) {
this.datas[id] = {data: data, include: include, width: width, height: height, callback: success};
};
Service.prototype.getData = function (id) {
return this.datas[id];
};
Service.prototype.removeData = function (id) {
if (this.datas[id]) {
delete this.datas[id];
}
};
Service.prototype.hasData = function (id) {
return this.datas[id] != null;
};
return new Service();
});
app.directive("drag", ["$rootScope", "dragAndDrop", "$controller", function ($rootScope, dragAndDrop) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var dragInclude = scope.$eval(attrs.dragInclude);
var success = function () {
if (attrs.dropCallback) {
var dropCallback = scope.$eval(attrs.dropCallback);
if (angular.isFunction(dropCallback)) {
dropCallback(dragAndDrop.getData(id));
}
}
};
var mouseDown = function (evt) {
evt.preventDefault();
scope.$apply(function () {
if (attrs.dragModel) {
dragAndDrop.setData(MOUSE_ID, scope.$eval(attrs.dragModel), success, dragInclude, element[0].offsetWidth, element[0].offsetHeight);
}
});
};
var touchStart = function (evt) {
evt.preventDefault();
scope.$apply(function () {
for (var ite = 0; ite < evt.targetTouches.length; ite++) {
var currentTouch = evt.targetTouches[ite];
if (attrs.dragModel) {
dragAndDrop.setData(currentTouch.identifier, scope.$eval(attrs.dragModel), success, dragInclude, element[0].offsetWidth, element[0].offsetHeight);
}
}
});
};
element.bind(MOUSEDOWN, mouseDown);
element.bind(TOUCHSTART, touchStart);
scope.$on($DESTROY, function () {
element.unbind(MOUSEDOWN, mouseDown);
element.unbind(TOUCHSTART, touchStart);
});
}
}
}]);
app.directive("drop", ['$rootScope', "dragAndDrop", "$document", function ($rootScope, dragAndDrop, $document) {
return {
restrict: 'A',
link: function (scope, $element, attrs) {
function doDrop(identifier, target) {
if (attrs.dropCallback) {
var result = scope.$eval(attrs.dropCallback);
if (angular.isFunction(result)) {
result(dragAndDrop.getData(identifier).data);
}
}
dragAndDrop.getData(identifier).callback();
dragAndDrop.removeData(identifier);
}
var mouseUp = function (evt) {
if (!dragAndDrop.hasData(MOUSE_ID)) {
return;
}
evt.preventDefault();
scope.$apply(function () {
doDrop(MOUSE_ID);
});
};
var touchEnd = function (s, currentTouch) {
var touchEndElement = $document[0].elementFromPoint(currentTouch.pageX, currentTouch.pageY);
if ($element[0].compareDocumentPosition(touchEndElement) & Node.DOCUMENT_POSITION_CONTAINED_BY) {
doDrop(currentTouch.identifier);
}
};
var doCallBack = function (cb) {
return function (evt) {
if (dragAndDrop.hasData(MOUSE_ID)) {
scope.$apply(function () {
var result = scope.$eval(cb);
if (angular.isFunction(result)) {
result(evt, dragAndDrop.getData(MOUSE_ID));
}
});
}
}
};
$rootScope.$on(TOUCHEND_ANGULAR_EVENT, touchEnd);
//A mouseup occurs on the target element
$element.bind(MOUSEUP, mouseUp);
scope.$on($DESTROY, function () {
$element.unbind(MOUSEUP, mouseUp);
});
//Mouse enter/leave/move an element
if (attrs.dropMouseEnter) {
var mouseEnter = doCallBack(attrs.dropMouseEnter);
$element.bind(MOUSEENTER, mouseEnter);
scope.$on($DESTROY, function () {
$element.unbind(MOUSEENTER, mouseEnter);
});
}
if (attrs.dropMouseLeave) {
var mouseLeave = doCallBack(attrs.dropMouseLeave);
$element.bind(MOUSELEAVE, mouseLeave);
scope.$on($DESTROY, function () {
$element.unbind(MOUSELEAVE, mouseLeave);
});
}
if (attrs.dropMouseMove) {
var mouseMove = doCallBack(attrs.dropMouseMove);
$element.bind(MOUSEMOVE, mouseMove);
scope.$on($DESTROY, function () {
$element.unbind(MOUSEMOVE, mouseMove);
});
}
}
}
}]);
}(angular.module('dragAndDrop', [])));
                    README.md
                    
See https://github.com/nithril/angular-draganddrop