<!DOCTYPE html>
<html>
<head>
<link data-require="bootstrap@*" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" data-semver="3.3.1" data-require="bootstrap-css@*" />
<script src="https://code.angularjs.org/1.4.3/angular.js" data-semver="1.4.3" data-require="angular.js@1.4.3"></script>
<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.13.0.min.js" data-semver="0.13.0" data-require="ui-bootstrap@*"></script>
<script data-require="angular-animate@*" data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular-animate.js"></script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="nested.css" />
<script src="dragdrop.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="demo">
<h1>Angular drag & drop with HTML5</h1>
<p><a href="https://github.com/marceljuenemann/angular-drag-and-drop-lists" class="btn btn-success btn-lg" role="button">Code on github »</a></p>
<br />
<div ng-controller="NestedListsDemoController">
<div class="nestedDemo">
<!-- Markup for lists inside the dropzone. It's inside a seperate template
because it will be used recursively. The dnd-list directive enables
to drop elements into the referenced array. The dnd-draggable directive
makes an element draggable and will transfer the object that was
assigned to it. If an element was dragged away, you have to remove
it from the original list yourself using the dnd-moved attribute -->
<script type="text/ng-template" id="list.html">
<ul dnd-list="list" dnd-dragover="onOver(event, index, type)">
<li ng-repeat="item in list" dnd-draggable="item" dnd-effect-allowed="move" dnd-moved="list.splice($index, 1)" dnd-selected="models.selected = item" ng-class="{selected: models.selected === item}" ng-include="item.type + '.html'">
</li>
</ul>
</script>
<!-- This template is responsible for rendering a container element. It uses
the above list template to render each container column -->
<script type="text/ng-template" id="container.html">
<div class="container-element box box-blue">
<h3>Container {{item.id}}</h3>
<div class="column" ng-repeat="list in item.columns" ng-include="'list.html'"></div>
<div class="clearfix"></div>
</div>
</script>
<!-- Template for a normal list item -->
<script type="text/ng-template" id="item.html">
<div class="item">Item {{item.id}}</div>
</script>
<!-- Main area with dropzones and source code -->
<div class="col-md-10">
<div class="row">
<div ng-repeat="(zone, list) in models.dropzones" class="col-md-6">
<div class="dropzone box box-yellow">
<!-- The dropzone also uses the list template -->
<h3>Dropzone {{zone}}</h3>
<div ng-include="'list.html'"></div>
</div>
</div>
</div>
<div view-source="nested" highlight-lines="{markup: '1-18, 20-28, 40-42, 57-68, 78-82'}"></div>
</div>
<!-- Sidebar -->
<div class="col-md-2">
<div class="toolbox box box-grey box-padding">
<h3>New Elements</h3>
<ul>
<!-- The toolbox only allows to copy objects, not move it. After a new
element was created, dnd-copied is invoked and we generate the next id -->
<li ng-repeat="item in models.templates" dnd-draggable="item" dnd-effect-allowed="copy" dnd-copied="item.id = item.id + 1">
<button type="button" class="btn btn-default btn-lg" disabled="disabled">{{item.type}}</button>
</li>
</ul>
</div>
<div ng-if="models.selected" class="box box-grey box-padding">
<h3>Selected</h3>
<strong>Type: </strong> {{models.selected.type}}
<br>
<input type="text" ng-model="models.selected.id" class="form-control" style="margin-top: 5px" />
</div>
<div class="trashcan box box-grey box-padding">
<!-- If you use [] as referenced list, the dropped elements will be lost -->
<h3>Trashcan</h3>
<ul dnd-list="[]">
<li><img src="http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/nested/trashcan.jpg"></li>
</ul>
</div>
</div>
</div>
</div>
</body>
</html>
var app = angular.module('demo', ['ngAnimate', 'ui.bootstrap', 'dndLists']);
app.controller("NestedListsDemoController", function($scope) {
$scope.onOver = onOver;
function onOver(event, index, type) {
//console.log(angular.element(event.target).context);
if (angular.element(event.target)[0].localName === 'li') {
return true;
}
return false;
}
$scope.models = {
selected: null,
templates: [{
type: "item",
id: 2
}, {
type: "container",
id: 1,
columns: [
[],
[],
[],
[]
]
}],
dropzones: {
"A": [{
"type": "container",
"id": 1,
"columns": [
[{
"type": "item",
"id": "1"
}, {
"type": "item",
"id": "2"
}],
[{
"type": "item",
"id": "3"
}]
]
}, {
"type": "item",
"id": "4"
}],
"B": [{
"type": "item",
"id": 7
}, {
"type": "container",
"id": "2",
"columns": [
[{
"type": "item",
"id": "9"
}],
[{
"type": "item",
"id": "12"
}, {
"type": "container",
"id": "3",
"columns": [
[{
"type": "item",
"id": "13"
}],
[{
"type": "item",
"id": "14"
}]
]
}, {
"type": "item",
"id": "15"
}]
]
}, {
"type": "item",
"id": 16
}]
}
};
$scope.$watch('models.dropzones', function(model) {
$scope.modelAsJson = angular.toJson(model, true);
}, true);
});
angular.module("dndLists",[]).directive("dndDraggable",["$parse","$timeout","dndDropEffectWorkaround","dndDragTypeWorkaround",function(e,n,r,t){return function(a,o,d){o.attr("draggable","true"),d.dndDisableIf&&a.$watch(d.dndDisableIf,function(e){o.attr("draggable",!e)}),o.on("dragstart",function(i){i=i.originalEvent||i,i.dataTransfer.setData("Text",angular.toJson(a.$eval(d.dndDraggable))),i.dataTransfer.effectAllowed=d.dndEffectAllowed||"move",o.addClass("dndDragging"),n(function(){o.addClass("dndDraggingSource")},0),r.dropEffect="none",t.isDragging=!0,t.dragType=d.dndType?a.$eval(d.dndType):void 0,e(d.dndDragstart)(a,{event:i}),i.stopPropagation()}),o.on("dragend",function(i){i=i.originalEvent||i;var f=r.dropEffect;a.$apply(function(){switch(f){case"move":e(d.dndMoved)(a,{event:i});break;case"copy":e(d.dndCopied)(a,{event:i});break;case"none":e(d.dndCanceled)(a,{event:i})}e(d.dndDragend)(a,{event:i,dropEffect:f})}),o.removeClass("dndDragging"),n(function(){o.removeClass("dndDraggingSource")},0),t.isDragging=!1,i.stopPropagation()}),o.on("click",function(n){d.dndSelected&&(n=n.originalEvent||n,a.$apply(function(){e(d.dndSelected)(a,{event:n})}),n.stopPropagation())}),o.on("selectstart",function(){this.dragDrop&&this.dragDrop()})}}]).directive("dndList",["$parse","$timeout","dndDropEffectWorkaround","dndDragTypeWorkaround",function(e,n,r,t){return function(a,o,d){function i(e,n,r){var t=y?e.offsetX||e.layerX:e.offsetY||e.layerY,a=y?n.offsetWidth:n.offsetHeight,o=y?n.offsetLeft:n.offsetTop;return o=r?o:0,o+a/2>t}function f(){var e;return angular.forEach(o.children(),function(n){var r=angular.element(n);r.hasClass("dndPlaceholder")&&(e=r)}),e||angular.element("<li class='dndPlaceholder'></li>")}function l(){return Array.prototype.indexOf.call(D.children,v)}function g(e){if(!t.isDragging&&!E)return!1;if(!c(e.dataTransfer.types))return!1;if(d.dndAllowedTypes&&t.isDragging){var n=a.$eval(d.dndAllowedTypes);if(angular.isArray(n)&&-1===n.indexOf(t.dragType))return!1}return d.dndDisableIf&&a.$eval(d.dndDisableIf)?!1:!0}function s(){return p.remove(),o.removeClass("dndDragover"),!0}function u(n,r,o,d){return e(n)(a,{event:r,index:o,item:d||void 0,external:!t.isDragging,type:t.isDragging?t.dragType:void 0})}function c(e){if(!e)return!0;for(var n=0;n<e.length;n++)if("Text"===e[n]||"text/plain"===e[n])return!0;return!1}var p=f(),v=p[0],D=o[0];p.remove();var y=d.dndHorizontalList&&a.$eval(d.dndHorizontalList),E=d.dndExternalSources&&a.$eval(d.dndExternalSources);o.on("dragover",function(e){if(e=e.originalEvent||e,!g(e))return!0;if(v.parentNode!=D&&o.append(p),e.target!==D){for(var n=e.target;n.parentNode!==D&&n.parentNode;)n=n.parentNode;n.parentNode===D&&n!==v&&(i(e,n)?D.insertBefore(v,n):D.insertBefore(v,n.nextSibling))}else if(i(e,v,!0))for(;v.previousElementSibling&&(i(e,v.previousElementSibling,!0)||0===v.previousElementSibling.offsetHeight);)D.insertBefore(v,v.previousElementSibling);else for(;v.nextElementSibling&&!i(e,v.nextElementSibling,!0);)D.insertBefore(v,v.nextElementSibling.nextElementSibling);return d.dndDragover&&!u(d.dndDragover,e,l())?s():(o.addClass("dndDragover"),e.preventDefault(),e.stopPropagation(),!1)}),o.on("drop",function(e){if(e=e.originalEvent||e,!g(e))return!0;e.preventDefault();var n,t=e.dataTransfer.getData("Text")||e.dataTransfer.getData("text/plain");try{n=JSON.parse(t)}catch(o){return s()}var i=l();if(d.dndDrop&&(n=u(d.dndDrop,e,i,n),!n))return s();var f=a.$eval(d.dndList);return a.$apply(function(){f.splice(i,0,n)}),u(d.dndInserted,e,i,n),r.dropEffect="none"===e.dataTransfer.dropEffect?"copy"===e.dataTransfer.effectAllowed||"move"===e.dataTransfer.effectAllowed?e.dataTransfer.effectAllowed:e.ctrlKey?"copy":"move":e.dataTransfer.dropEffect,s(),e.stopPropagation(),!1}),o.on("dragleave",function(e){e=e.originalEvent||e,o.removeClass("dndDragover"),n(function(){o.hasClass("dndDragover")||p.remove()},100)})}}]).directive("dndNodrag",function(){return function(e,n){n.attr("draggable","true"),n.on("dragstart",function(e){e=e.originalEvent||e,e.dataTransfer.types&&e.dataTransfer.types.length||e.preventDefault(),e.stopPropagation()}),n.on("dragend",function(e){e=e.originalEvent||e,e.stopPropagation()})}}).factory("dndDragTypeWorkaround",function(){return{}}).factory("dndDropEffectWorkaround",function(){return{}});
/***************************** Required styles *****************************/
/**
* For the correct positioning of the placeholder element, the dnd-list and
* it's children must have position: relative
*/
.nestedDemo ul[dnd-list],
.nestedDemo ul[dnd-list] > li {
position: relative;
}
/***************************** Dropzone Styling *****************************/
/**
* The dnd-list should always have a min-height,
* otherwise you can't drop to it once it's empty
*/
.nestedDemo .dropzone ul[dnd-list] {
min-height: 42px;
margin: 0px;
padding-left: 0px;
}
/**
* The dnd-lists's child elements currently MUST have
* position: relative. Otherwise we can not determine
* whether the mouse pointer is in the upper or lower
* half of the element we are dragging over. In other
* browsers we can use event.offsetY for this.
*/
.nestedDemo .dropzone li {
background-color: #fff;
border: 1px solid #ddd;
display: block;
padding: 0px;
}
/**
* Reduce opacity of elements during the drag operation. This allows the user
* to see where he is dropping his element, even if the element is huge. The
* .dndDragging class is automatically set during the drag operation.
*/
.nestedDemo .dropzone .dndDragging {
opacity: 0.7;
}
/**
* The dndDraggingSource class will be applied to the source element of a drag
* operation. It makes sense to hide it to give the user the feeling that he's
* actually moving it. Note that the source element has also .dndDragging class.
*/
.nestedDemo .dropzone .dndDraggingSource {
display: none;
}
/**
* An element with .dndPlaceholder class will be added as child of the dnd-list
* while the user is dragging over it.
*/
.nestedDemo .dropzone .dndPlaceholder {
background-color: #ddd;
min-height: 42px;
display: block;
position: relative;
}
/***************************** Element Selection *****************************/
.nestedDemo .dropzone .selected .item {
color: #3c763d;
background-color: #dff0d8;
}
.nestedDemo .dropzone .selected .box {
border-color: #d6e9c6;
}
.nestedDemo .dropzone .selected .box > h3 {
color: #3c763d;
background-color: #dff0d8;
background-image: linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);
border-color: #d6e9c6;
}
/***************************** Element type specific styles *****************************/
.nestedDemo .dropzone .item {
padding: 10px 15px;
}
.nestedDemo .dropzone .container-element {
margin: 10px;
}
.nestedDemo .dropzone .container-element .column {
float: left;
width: 50%;
}
/***************************** Toolbox *****************************/
.nestedDemo .toolbox ul {
list-style: none;
padding-left: 0px;
cursor: move;
}
.nestedDemo .toolbox button {
margin: 5px;
width: 123px;
opacity: 1.0;
}
.nestedDemo .toolbox .dndDragging {
opacity: 0.5;
}
.nestedDemo .toolbox .dndDraggingSource {
opacity: 1.0;
}
/***************************** Trashcan *****************************/
.nestedDemo .trashcan ul {
list-style: none;
padding-left: 0px;
}
.nestedDemo .trashcan img {
width: 100%;
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
filter: grayscale(100%);
}
.nestedDemo .trashcan .dndDragover img {
width: 100%;
-webkit-filter: none;
-moz-filter: none;
filter: none;
}
.nestedDemo .trashcan .dndPlaceholder {
display: none;
}
body {
padding-top: 70px;
padding-bottom: 30px;
}
.box {
margin-bottom: 20px;
background-color: #fff;
border: 1px solid transparent;
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
box-shadow: 0 1px 2px rgba(0,0,0,.05);
}
.box > h3 {
color: #333;
border-color: #ddd;
border-bottom: 1px solid transparent;
border-top-right-radius: 3px;
border-top-left-radius: 3px;
background-repeat: repeat-x;
display: block;
font-size: 16px;
padding: 10px 15px;
margin-top: 0;
margin-bottom: 0;
}
.box-padding {
padding: 15px;
}
.box-padding > h3 {
margin: -15px;
margin-bottom: 15px;
}
.box-grey {
border-color: #ddd;
}
.box-grey > h3 {
background-color: #f5f5f5;
background-image: -webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);
background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);
}
.box-blue {
border-color: #bce8f1;
}
.box-blue > h3 {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
background-image: -webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);
background-image: linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);
}
.box-yellow {
border-color: #faebcc;
}
.box-yellow > h3 {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
background-image: -webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);
background-image: linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);
}