4 | Instructions:
5 | In this demo you can not only drag & drop list items, but also containers, which
6 | can contain list items or other containers themselves. To create new elements, use the toolbar on the right.
7 | If this is more than you need, check out the simple list demo
8 |
21 | Drop any {{list.allowedTypes.join(' or ')}} here
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/demo/multi/multi.css:
--------------------------------------------------------------------------------
1 | /**
2 | * The dnd-list should always have a min-height,
3 | * otherwise you can't drop into it once it's empty
4 | */
5 | .multiDemo ul[dnd-list] {
6 | min-height: 42px;
7 | padding-left: 0px;
8 | }
9 |
10 | /**
11 | * An element with .dndPlaceholder class will be
12 | * added to the dnd-list while the user is dragging
13 | * over it.
14 | */
15 | .multiDemo ul[dnd-list] .dndPlaceholder {
16 | background-color: #ddd;
17 | display: block;
18 | min-height: 42px;
19 | }
20 |
21 | .multiDemo ul[dnd-list] li {
22 | background-color: #fff;
23 | border: 1px solid #ddd;
24 | border-top-right-radius: 4px;
25 | border-top-left-radius: 4px;
26 | display: block;
27 | margin-bottom: -1px;
28 | padding: 10px 15px;
29 | }
30 |
31 | /**
32 | * Show selected elements in green
33 | */
34 | .multiDemo ul[dnd-list] li.selected {
35 | background-color: #dff0d8;
36 | color: #3c763d;
37 | }
38 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Testing angular-drag-and-drop-lists...
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/demo/framework/view-source.js:
--------------------------------------------------------------------------------
1 | angular.module("demo").directive('viewSource', function($http, $timeout) {
2 | return {
3 | scope: {
4 | demoName: "@viewSource",
5 | highlightLines: "="
6 | },
7 | templateUrl: 'framework/view-source.html',
8 | link: function (scope, element, attr) {
9 |
10 | scope.models = {
11 | types: [
12 | {extension: "html", language: "markup", label: "Markup"},
13 | {extension: "css", language: "css", label: "CSS"},
14 | {extension: "js", language: "javascript", label: "Javascript"},
15 | ],
16 | activeTab: "markup"
17 | };
18 |
19 | angular.forEach(scope.models.types, function(type) {
20 | $http.get(scope.demoName + '/' + scope.demoName + '.' + type.extension)
21 | .success(function (data) {
22 | type.source = data;
23 | $timeout(Prism.highlightAll, 0);
24 | });
25 | });
26 |
27 | }
28 | };
29 | });
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Marcel Juenemann
4 | Copyright (c) 2014-2016 Google Inc.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/demo/multi/multi-frame.html:
--------------------------------------------------------------------------------
1 |
Demo: Multiselect Lists
2 |
3 |
4 | Instructions:
5 | Click on items to select/unselect them. When dragging, all selected items will be moved at once.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
List {{list.listName}}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
Generated Model
30 |
31 |
32 |
{{modelAsJson}}
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/demo/simple/simple.css:
--------------------------------------------------------------------------------
1 | /**
2 | * The dnd-list should always have a min-height,
3 | * otherwise you can't drop to it once it's empty
4 | */
5 | .simpleDemo ul[dnd-list] {
6 | min-height: 42px;
7 | padding-left: 0px;
8 | }
9 |
10 | /**
11 | * The dndDraggingSource class will be applied to
12 | * the source element of a drag operation. It makes
13 | * sense to hide it to give the user the feeling
14 | * that he's actually moving it.
15 | */
16 | .simpleDemo ul[dnd-list] .dndDraggingSource {
17 | display: none;
18 | }
19 |
20 | /**
21 | * An element with .dndPlaceholder class will be
22 | * added to the dnd-list while the user is dragging
23 | * over it.
24 | */
25 | .simpleDemo ul[dnd-list] .dndPlaceholder {
26 | background-color: #ddd;
27 | display: block;
28 | min-height: 42px;
29 | }
30 |
31 | .simpleDemo ul[dnd-list] li {
32 | background-color: #fff;
33 | border: 1px solid #ddd;
34 | border-top-right-radius: 4px;
35 | border-top-left-radius: 4px;
36 | display: block;
37 | padding: 10px 15px;
38 | margin-bottom: -1px;
39 | }
40 |
41 | /**
42 | * Show selected elements in green
43 | */
44 | .simpleDemo ul[dnd-list] li.selected {
45 | background-color: #dff0d8;
46 | color: #3c763d;
47 | }
48 |
--------------------------------------------------------------------------------
/demo/simple/simple-frame.html:
--------------------------------------------------------------------------------
1 |
Demo: Simple Lists
2 |
3 |
4 | Instructions:
5 | Drag & drop the list items to move them around, or just click to select them.
6 | If that's too boring, check out the nested container demo
7 |
4 | Instructions:
5 | Drag & drop the names to move them around. Note that the names can not be
6 | dropped in the list for the wrong gender. This is achieved with the dnd-type and
7 | dnd-allowed-types attributes.
8 | This demo also shows the use of the dnd-disable-if attribute, which is used here
9 | to limit the number of names per list, as well as fix Alex' position.
10 | You can combine these functions with nested lists
11 | to build very powerful UIs.
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
List of {{list.label}} (max. {{list.max}})
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
List Models
34 |
35 |
36 |
{{modelAsJson}}
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/test/dndNodragSpec.js:
--------------------------------------------------------------------------------
1 | describe('dndNodrag', function() {
2 | var element;
3 |
4 | beforeEach(function() {
5 | element = compileAndLink('');
6 | });
7 |
8 | it('sets the draggable attribute', function() {
9 | expect(element.attr('draggable')).toBe('true');
10 | });
11 |
12 | it('stops propagation and prevents default for dragstart events', function() {
13 | var dragstart = Dragstart.on(element);
14 | expect(dragstart.propagationStopped).toBe(true);
15 | expect(dragstart.defaultPrevented).toBe(true);
16 | });
17 |
18 | it('does not call preventDefault if dataTransfer is already set', function() {
19 | var dragstart = Dragstart.on(element, {presetTypes: ['text/plain']});
20 | expect(dragstart.propagationStopped).toBe(true);
21 | expect(dragstart.defaultPrevented).toBe(false);
22 | });
23 |
24 | it('does nothing in dragstart if the event was triggered on a dnd-handle', function() {
25 | var dragstart = Dragstart.on(element, {dndHandle: true});
26 | expect(dragstart.propagationStopped).toBe(false);
27 | expect(dragstart.defaultPrevented).toBe(false);
28 | });
29 |
30 | it('stops propagation of dragend events', function() {
31 | var dragend = Dragend.on(element);
32 | expect(dragend.propagationStopped).toBe(true);
33 | expect(dragend.defaultPrevented).toBe(false);
34 | });
35 |
36 | it('does nothing in dragend if the event was triggered on a dnd-handle', function() {
37 | var dragend = Dragend.on(element, {dndHandle: true});
38 | expect(dragend.propagationStopped).toBe(false);
39 | expect(dragend.defaultPrevented).toBe(false);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/demo/advanced/advanced.js:
--------------------------------------------------------------------------------
1 | angular.module("demo").controller("AdvancedDemoController", function($scope) {
2 |
3 | $scope.dragoverCallback = function(index, external, type, callback) {
4 | $scope.logListEvent('dragged over', index, external, type);
5 | // Invoke callback to origin for container types.
6 | if (type == 'container' && !external) {
7 | console.log('Container being dragged contains ' + callback() + ' items');
8 | }
9 | return index < 10; // Disallow dropping in the third row.
10 | };
11 |
12 | $scope.dropCallback = function(index, item, external, type) {
13 | $scope.logListEvent('dropped at', index, external, type);
14 | // Return false here to cancel drop. Return true if you insert the item yourself.
15 | return item;
16 | };
17 |
18 | $scope.logEvent = function(message) {
19 | console.log(message);
20 | };
21 |
22 | $scope.logListEvent = function(action, index, external, type) {
23 | var message = external ? 'External ' : '';
24 | message += type + ' element was ' + action + ' position ' + index;
25 | console.log(message);
26 | };
27 |
28 | // Initialize model
29 | $scope.model = [[], []];
30 | var id = 10;
31 | angular.forEach(['all', 'move', 'copy', 'link', 'copyLink', 'copyMove'], function(effect, i) {
32 | var container = {items: [], effectAllowed: effect};
33 | for (var k = 0; k < 7; ++k) {
34 | container.items.push({label: effect + ' ' + id++, effectAllowed: effect});
35 | }
36 | $scope.model[i % $scope.model.length].push(container);
37 | });
38 |
39 | $scope.$watch('model', function(model) {
40 | $scope.modelAsJson = angular.toJson(model, true);
41 | }, true);
42 |
43 | });
44 |
--------------------------------------------------------------------------------
/demo/framework/demo-framework.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 70px;
3 | padding-bottom: 30px;
4 | }
5 |
6 | .box {
7 | margin-bottom: 20px;
8 | background-color: #fff;
9 | border: 1px solid transparent;
10 | border-radius: 4px;
11 | -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
12 | box-shadow: 0 1px 2px rgba(0,0,0,.05);
13 | }
14 |
15 | .box > h3 {
16 | color: #333;
17 | border-color: #ddd;
18 | border-bottom: 1px solid transparent;
19 | border-top-right-radius: 3px;
20 | border-top-left-radius: 3px;
21 | background-repeat: repeat-x;
22 | display: block;
23 | font-size: 16px;
24 | padding: 10px 15px;
25 | margin-top: 0;
26 | margin-bottom: 0;
27 | }
28 |
29 | .box-padding {
30 | padding: 15px;
31 | }
32 |
33 | .box-padding > h3 {
34 | margin: -15px;
35 | margin-bottom: 15px;
36 | }
37 |
38 | .box-grey {
39 | border-color: #ddd;
40 | }
41 |
42 | .box-grey > h3 {
43 | background-color: #f5f5f5;
44 | background-image: -webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);
45 | background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);
46 | }
47 |
48 | .box-blue {
49 | border-color: #bce8f1;
50 | }
51 |
52 | .box-blue > h3 {
53 | color: #31708f;
54 | background-color: #d9edf7;
55 | border-color: #bce8f1;
56 | background-image: -webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);
57 | background-image: linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);
58 | }
59 |
60 | .box-yellow {
61 | border-color: #faebcc;
62 | }
63 |
64 | .box-yellow > h3 {
65 | color: #8a6d3b;
66 | background-color: #fcf8e3;
67 | border-color: #faebcc;
68 | background-image: -webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);
69 | background-image: linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);
70 | }
--------------------------------------------------------------------------------
/demo/advanced/advanced.css:
--------------------------------------------------------------------------------
1 | /***************************** Dropzone Styling *****************************/
2 |
3 | /**
4 | * The dnd-list should always have a min-height,
5 | * otherwise you can't drop to it once it's empty
6 | */
7 | .advancedDemo .dropzone ul[dnd-list] {
8 | min-height: 42px;
9 | margin: 0px;
10 | padding-left: 0px;
11 | }
12 |
13 | .advancedDemo .dropzone li {
14 | display: block;
15 | }
16 |
17 | /**
18 | * Reduce opacity of elements during the drag operation. This allows the user
19 | * to see where he is dropping his element, even if the element is huge. The
20 | * .dndDragging class is automatically set during the drag operation.
21 | */
22 | .advancedDemo .dropzone .dndDragging {
23 | opacity: 0.7;
24 | }
25 |
26 | /**
27 | * The dndDraggingSource class will be applied to the source element of a drag
28 | * operation.
29 | */
30 | .advancedDemo .dropzone .dndDraggingSource {
31 | opacity: 0.5;
32 | }
33 |
34 | /**
35 | * An element with .dndPlaceholder class will be added as child of the dnd-list
36 | * while the user is dragging over it.
37 | */
38 | .advancedDemo .dropzone .dndPlaceholder {
39 | background-color: #ddd !important;
40 | display: block;
41 | min-height: 42px;
42 | }
43 |
44 | /***************************** Element type specific styles *****************************/
45 |
46 | .advancedDemo .dropzone .itemlist {
47 | min-height: 120px !important;
48 | }
49 |
50 | .advancedDemo .dropzone .itemlist > li {
51 | background-color: #337ab7;
52 | border: none;
53 | border-radius: .25em;
54 | color: #fff;
55 | float: left;
56 | font-weight: 700;
57 | height: 50px;
58 | margin: 5px;
59 | padding: 3px;
60 | text-align: center;
61 | width: 80px;
62 | }
63 |
64 | .advancedDemo .dropzone .container-element {
65 | margin: 10px;
66 | }
67 |
--------------------------------------------------------------------------------
/demo/advanced/advanced.html:
--------------------------------------------------------------------------------
1 |
dnd-effect-allowed: This demo shows how to use dnd-effect-allowed to control which drop effects are allowed.
5 | If the source and target elements have no drop effect that is allowed on both, then a drop is not possible. If there are multiple
6 | possible drop effects, then the user can control the drop effect using modifier keys (Ctrl and Alt).
7 |
dnd-external-sources: Allows to drag and drop elements accross browser windows, which you can test in this
8 | example. The downside to this is that the lists will accept arbitrary text to be dropped. To prevent that, the dnd-drop callback
9 | verifies that the dropped element is of the desired format.
10 |
dnd-allowed-types in nested lists: We are using the dnd-allowed-types attribute to ensure that Containers
11 | only accept items, but not other containers.
12 |
dnd-horizontal-list: This attribute tells the positioning algorithm to drop incoming elements left or right
13 | of the existing elements, instead of above or below.
14 |
Callbacks: The directives offer various callbacks, which in this example will log the events to the console.
15 | Additionally, the callbacks on the dnd-list can prevent an element from being dropped. In this example you can't drop elements
16 | after the 10th position, because we are preventing that in the dnd-dragover callback.
54 | Directives for modifying lists with the HTML5 drag & drop API.
55 | Supports nested lists for building trees and other fancy structures.
56 | No boilerplate code, no jQuery, just a few KB!
57 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/demo/nested/nested.css:
--------------------------------------------------------------------------------
1 | /***************************** Dropzone Styling *****************************/
2 |
3 | /**
4 | * The dnd-list should always have a min-height,
5 | * otherwise you can't drop to it once it's empty
6 | */
7 | .nestedDemo .dropzone ul[dnd-list] {
8 | margin: 0px;
9 | min-height: 42px;
10 | padding-left: 0px;
11 | }
12 |
13 | .nestedDemo .dropzone li {
14 | background-color: #fff;
15 | border: 1px solid #ddd;
16 | display: block;
17 | padding: 0px;
18 | }
19 |
20 | /**
21 | * Reduce opacity of elements during the drag operation. This allows the user
22 | * to see where he is dropping his element, even if the element is huge. The
23 | * .dndDragging class is automatically set during the drag operation.
24 | */
25 | .nestedDemo .dropzone .dndDragging {
26 | opacity: 0.7;
27 | }
28 |
29 | /**
30 | * The dndDraggingSource class will be applied to the source element of a drag
31 | * operation. It makes sense to hide it to give the user the feeling that he's
32 | * actually moving it. Note that the source element has also .dndDragging class.
33 | */
34 | .nestedDemo .dropzone .dndDraggingSource {
35 | display: none;
36 | }
37 |
38 | /**
39 | * An element with .dndPlaceholder class will be added as child of the dnd-list
40 | * while the user is dragging over it.
41 | */
42 | .nestedDemo .dropzone .dndPlaceholder {
43 | background-color: #ddd;
44 | display: block;
45 | min-height: 42px;
46 | }
47 |
48 | /***************************** Element Selection *****************************/
49 |
50 | .nestedDemo .dropzone .selected .item {
51 | color: #3c763d;
52 | background-color: #dff0d8;
53 | }
54 |
55 | .nestedDemo .dropzone .selected .box {
56 | border-color: #d6e9c6;
57 | }
58 |
59 | .nestedDemo .dropzone .selected .box > h3 {
60 | background-color: #dff0d8;
61 | background-image: linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);
62 | border-color: #d6e9c6;
63 | color: #3c763d;
64 | }
65 |
66 | /***************************** Element type specific styles *****************************/
67 |
68 | .nestedDemo .dropzone .item {
69 | padding: 10px 15px;
70 | }
71 |
72 | .nestedDemo .dropzone .container-element {
73 | margin: 10px;
74 | }
75 |
76 | .nestedDemo .dropzone .container-element .column {
77 | float: left;
78 | width: 50%;
79 | }
80 |
81 | /***************************** Toolbox *****************************/
82 |
83 | .nestedDemo .toolbox ul {
84 | cursor: move;
85 | list-style: none;
86 | padding-left: 0px;
87 | }
88 |
89 | .nestedDemo .toolbox button {
90 | margin: 5px;
91 | opacity: 1.0;
92 | width: 123px;
93 | }
94 |
95 | .nestedDemo .toolbox .dndDragging {
96 | opacity: 0.5;
97 | }
98 |
99 | .nestedDemo .toolbox .dndDraggingSource {
100 | opacity: 1.0;
101 | }
102 |
103 | /***************************** Trashcan *****************************/
104 |
105 | .nestedDemo .trashcan ul {
106 | list-style: none;
107 | padding-left: 0px;
108 | }
109 |
110 | .nestedDemo .trashcan img {
111 | width: 100%;
112 | -webkit-filter: grayscale(100%);
113 | -moz-filter: grayscale(100%);
114 | filter: grayscale(100%);
115 | }
116 |
117 | .nestedDemo .trashcan .dndDragover img {
118 | width: 100%;
119 | -webkit-filter: none;
120 | -moz-filter: none;
121 | filter: none;
122 | }
123 |
124 | .nestedDemo .trashcan .dndPlaceholder {
125 | display: none;
126 | }
127 |
--------------------------------------------------------------------------------
/demo/nested/nested.html:
--------------------------------------------------------------------------------
1 |
7 |
19 |
20 |
22 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Dropzone {{zone}}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
Generated Model
50 |
{{modelAsJson}}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
New Elements
58 |
59 |
61 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
Selected
73 | Type: {{models.selected.type}}
74 |
75 |
76 |
77 |
78 |
79 |
Trashcan
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/demo/nested/nested.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The controller doesn't do much more than setting the initial data model
3 | */
4 | angular.module("demo").controller("NestedListsDemoController", function($scope) {
5 |
6 | $scope.models = {
7 | selected: null,
8 | templates: [
9 | {type: "item", id: 2},
10 | {type: "container", id: 1, columns: [[], []]}
11 | ],
12 | dropzones: {
13 | "A": [
14 | {
15 | "type": "container",
16 | "id": 1,
17 | "columns": [
18 | [
19 | {
20 | "type": "item",
21 | "id": "1"
22 | },
23 | {
24 | "type": "item",
25 | "id": "2"
26 | }
27 | ],
28 | [
29 | {
30 | "type": "item",
31 | "id": "3"
32 | }
33 | ]
34 | ]
35 | },
36 | {
37 | "type": "item",
38 | "id": "4"
39 | },
40 | {
41 | "type": "item",
42 | "id": "5"
43 | },
44 | {
45 | "type": "item",
46 | "id": "6"
47 | }
48 | ],
49 | "B": [
50 | {
51 | "type": "item",
52 | "id": 7
53 | },
54 | {
55 | "type": "item",
56 | "id": "8"
57 | },
58 | {
59 | "type": "container",
60 | "id": "2",
61 | "columns": [
62 | [
63 | {
64 | "type": "item",
65 | "id": "9"
66 | },
67 | {
68 | "type": "item",
69 | "id": "10"
70 | },
71 | {
72 | "type": "item",
73 | "id": "11"
74 | }
75 | ],
76 | [
77 | {
78 | "type": "item",
79 | "id": "12"
80 | },
81 | {
82 | "type": "container",
83 | "id": "3",
84 | "columns": [
85 | [
86 | {
87 | "type": "item",
88 | "id": "13"
89 | }
90 | ],
91 | [
92 | {
93 | "type": "item",
94 | "id": "14"
95 | }
96 | ]
97 | ]
98 | },
99 | {
100 | "type": "item",
101 | "id": "15"
102 | },
103 | {
104 | "type": "item",
105 | "id": "16"
106 | }
107 | ]
108 | ]
109 | },
110 | {
111 | "type": "item",
112 | "id": 16
113 | }
114 | ]
115 | }
116 | };
117 |
118 | $scope.$watch('models.dropzones', function(model) {
119 | $scope.modelAsJson = angular.toJson(model, true);
120 | }, true);
121 |
122 | });
123 |
--------------------------------------------------------------------------------
/demo/framework/vendor/prism.css:
--------------------------------------------------------------------------------
1 | /**
2 | * prism.js default theme for JavaScript, CSS and HTML
3 | * Based on dabblet (http://dabblet.com)
4 | * @author Lea Verou
5 | */
6 |
7 | code[class*="language-"],
8 | pre[class*="language-"] {
9 | color: black;
10 | text-shadow: 0 1px white;
11 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
12 | direction: ltr;
13 | text-align: left;
14 | white-space: pre;
15 | word-spacing: normal;
16 | word-break: normal;
17 |
18 |
19 | -moz-tab-size: 4;
20 | -o-tab-size: 4;
21 | tab-size: 4;
22 |
23 | -webkit-hyphens: none;
24 | -moz-hyphens: none;
25 | -ms-hyphens: none;
26 | hyphens: none;
27 | }
28 |
29 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
30 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
31 | text-shadow: none;
32 | background: #b3d4fc;
33 | }
34 |
35 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
36 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
37 | text-shadow: none;
38 | background: #b3d4fc;
39 | }
40 |
41 | @media print {
42 | code[class*="language-"],
43 | pre[class*="language-"] {
44 | text-shadow: none;
45 | }
46 | }
47 |
48 | /* Code blocks */
49 | pre[class*="language-"] {
50 | padding: 1em;
51 | margin: .5em 0;
52 | overflow: auto;
53 | }
54 |
55 | :not(pre) > code[class*="language-"],
56 | pre[class*="language-"] {
57 | background: #f5f2f0;
58 | }
59 |
60 | /* Inline code */
61 | :not(pre) > code[class*="language-"] {
62 | padding: .1em;
63 | border-radius: .3em;
64 | }
65 |
66 | .token.comment,
67 | .token.prolog,
68 | .token.doctype,
69 | .token.cdata {
70 | color: slategray;
71 | }
72 |
73 | .token.punctuation {
74 | color: #999;
75 | }
76 |
77 | .namespace {
78 | opacity: .7;
79 | }
80 |
81 | .token.property,
82 | .token.tag,
83 | .token.boolean,
84 | .token.number,
85 | .token.constant,
86 | .token.symbol {
87 | color: #905;
88 | }
89 |
90 | .token.selector,
91 | .token.attr-name,
92 | .token.string,
93 | .token.builtin {
94 | color: #690;
95 | }
96 |
97 | .token.operator,
98 | .token.entity,
99 | .token.url,
100 | .language-css .token.string,
101 | .style .token.string,
102 | .token.variable {
103 | color: #a67f59;
104 | background: hsla(0,0%,100%,.5);
105 | }
106 |
107 | .token.atrule,
108 | .token.attr-value,
109 | .token.keyword {
110 | color: #07a;
111 | }
112 |
113 |
114 | .token.regex,
115 | .token.important {
116 | color: #e90;
117 | }
118 |
119 | .token.important {
120 | font-weight: bold;
121 | }
122 |
123 | .token.entity {
124 | cursor: help;
125 | }
126 |
127 | pre[data-line] {
128 | position: relative;
129 | padding: 1em 0 1em 3em;
130 | }
131 |
132 | .line-highlight {
133 | position: absolute;
134 | left: 0;
135 | right: 0;
136 | padding: inherit 0;
137 | margin-top: 1em; /* Same as .prism’s padding-top */
138 |
139 | background: hsla(24, 20%, 50%,.08);
140 | background: -moz-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
141 | background: -webkit-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
142 | background: -o-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
143 | background: linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
144 |
145 | pointer-events: none;
146 |
147 | line-height: inherit;
148 | white-space: pre;
149 | }
150 |
151 | .line-highlight:before,
152 | .line-highlight[data-end]:after {
153 | content: attr(data-start);
154 | position: absolute;
155 | top: .4em;
156 | left: .6em;
157 | min-width: 1em;
158 | padding: 0 .5em;
159 | background-color: hsla(24, 20%, 50%,.4);
160 | color: hsl(24, 20%, 95%);
161 | font: bold 65%/1.5 sans-serif;
162 | text-align: center;
163 | vertical-align: .3em;
164 | border-radius: 999px;
165 | text-shadow: none;
166 | box-shadow: 0 1px white;
167 | }
168 |
169 | .line-highlight[data-end]:after {
170 | content: attr(data-end);
171 | top: auto;
172 | bottom: .4em;
173 | }
174 | pre.line-numbers {
175 | position: relative;
176 | padding-left: 3.8em;
177 | counter-reset: linenumber;
178 | }
179 |
180 | pre.line-numbers > code {
181 | position: relative;
182 | }
183 |
184 | .line-numbers .line-numbers-rows {
185 | position: absolute;
186 | pointer-events: none;
187 | top: 0;
188 | font-size: 100%;
189 | left: -3.8em;
190 | width: 3em; /* works for line-numbers below 1000 lines */
191 | letter-spacing: -1px;
192 | border-right: 1px solid #999;
193 |
194 | -webkit-user-select: none;
195 | -moz-user-select: none;
196 | -ms-user-select: none;
197 | user-select: none;
198 |
199 | }
200 |
201 | .line-numbers-rows > span {
202 | pointer-events: none;
203 | display: block;
204 | counter-increment: linenumber;
205 | }
206 |
207 | .line-numbers-rows > span:before {
208 | content: counter(linenumber);
209 | color: #999;
210 | display: block;
211 | padding-right: 0.8em;
212 | text-align: right;
213 | }
214 |
--------------------------------------------------------------------------------
/angular-drag-and-drop-lists.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * angular-drag-and-drop-lists v2.1.0
3 | *
4 | * Copyright (c) 2014 Marcel Juenemann marcel@juenemann.cc
5 | * Copyright (c) 2014-2017 Google Inc.
6 | * https://github.com/marceljuenemann/angular-drag-and-drop-lists
7 | *
8 | * License: MIT
9 | */
10 | !function(e){function n(e,n){return"all"==n?e:e.filter(function(e){return-1!=n.toLowerCase().indexOf(e)})}var a="application/x-dnd",r="application/json",t="Text",d=["move","copy","link"]
11 | e.directive("dndDraggable",["$parse","$timeout",function(e,i){return function(l,f,c){f.attr("draggable","true"),c.dndDisableIf&&l.$watch(c.dndDisableIf,function(e){f.attr("draggable",!e)}),f.on("dragstart",function(s){if(s=s.originalEvent||s,"false"==f.attr("draggable"))return!0
12 | o.isDragging=!0,o.itemType=c.dndType&&l.$eval(c.dndType).toLowerCase(),o.dropEffect="none",o.effectAllowed=c.dndEffectAllowed||d[0],s.dataTransfer.effectAllowed=o.effectAllowed
13 | var g=l.$eval(c.dndDraggable),u=a+(o.itemType?"-"+o.itemType:"")
14 | try{s.dataTransfer.setData(u,angular.toJson(g))}catch(p){var v=angular.toJson({item:g,type:o.itemType})
15 | try{s.dataTransfer.setData(r,v)}catch(p){var D=n(d,o.effectAllowed)
16 | s.dataTransfer.effectAllowed=D[0],s.dataTransfer.setData(t,v)}}if(f.addClass("dndDragging"),i(function(){f.addClass("dndDraggingSource")},0),s._dndHandle&&s.dataTransfer.setDragImage&&s.dataTransfer.setDragImage(f[0],0,0),e(c.dndDragstart)(l,{event:s}),c.dndCallback){var y=e(c.dndCallback)
17 | o.callback=function(e){return y(l,e||{})}}s.stopPropagation()}),f.on("dragend",function(n){n=n.originalEvent||n,l.$apply(function(){var a=o.dropEffect,r={copy:"dndCopied",link:"dndLinked",move:"dndMoved",none:"dndCanceled"}
18 | e(c[r[a]])(l,{event:n}),e(c.dndDragend)(l,{event:n,dropEffect:a})}),o.isDragging=!1,o.callback=void 0,f.removeClass("dndDragging"),f.removeClass("dndDraggingSource"),n.stopPropagation(),i(function(){f.removeClass("dndDraggingSource")},0)}),f.on("click",function(n){c.dndSelected&&(n=n.originalEvent||n,l.$apply(function(){e(c.dndSelected)(l,{event:n})}),n.stopPropagation())}),f.on("selectstart",function(){this.dragDrop&&this.dragDrop()})}}]),e.directive("dndList",["$parse",function(e){return function(i,l,f){function c(e){if(!e)return t
19 | for(var n=0;n")}var T=y()
24 | T.remove()
25 | var h=T[0],m=l[0],E={}
26 | l.on("dragenter",function(e){e=e.originalEvent||e
27 | var n=f.dndAllowedTypes&&i.$eval(f.dndAllowedTypes)
28 | E={allowedTypes:angular.isArray(n)&&n.join("|").toLowerCase().split("|"),disabled:f.dndDisableIf&&i.$eval(f.dndDisableIf),externalSources:f.dndExternalSources&&i.$eval(f.dndExternalSources),horizontal:f.dndHorizontalList&&i.$eval(f.dndHorizontalList)}
29 | var a=c(e.dataTransfer.types)
30 | return a&&g(s(a))?void e.preventDefault():!0}),l.on("dragover",function(e){e=e.originalEvent||e
31 | var n=c(e.dataTransfer.types),a=s(n)
32 | if(!n||!g(a))return!0
33 | if(h.parentNode!=m&&l.append(T),e.target!=m){for(var r=e.target;r.parentNode!=m&&r.parentNode;)r=r.parentNode
34 | if(r.parentNode==m&&r!=h){var d=r.getBoundingClientRect()
35 | if(E.horizontal)var o=e.clientX';
4 |
5 | describe('constructor', function() {
6 | it('sets the draggable attribute', function() {
7 | var element = compileAndLink(SIMPLE_HTML);
8 | expect(element.attr('draggable')).toBe('true');
9 | });
10 |
11 | it('watches and handles the dnd-disabled-if expression', function() {
12 | var element = compileAndLink('');
13 | expect(element.attr('draggable')).toBe('true');
14 |
15 | element.scope().disabled = true;
16 | element.scope().$digest();
17 | expect(element.attr('draggable')).toBe('false');
18 |
19 | element.scope().disabled = false;
20 | element.scope().$digest();
21 | expect(element.attr('draggable')).toBe('true');
22 | });
23 | });
24 |
25 | describe('dragstart handler', function() {
26 | var element;
27 |
28 | beforeEach(function() {
29 | element = compileAndLink(SIMPLE_HTML);
30 | });
31 |
32 | it('calls setData with serialized data', function() {
33 | expect(Dragstart.on(element).data).toEqual({'application/x-dnd': '{"hello":"world"}'});
34 | });
35 |
36 | it('includes the dnd-type in the mime type', function() {
37 | element = compileAndLink('');
38 | expect(Dragstart.on(element).data).toEqual({'application/x-dnd-foo': '{}'});
39 | });
40 |
41 | it('converts the dnd-type to lower case', function() {
42 | element = compileAndLink('');
43 | expect(Dragstart.on(element).data).toEqual({'application/x-dnd-foo': '{}'});
44 | });
45 |
46 | it('uses application/json mime type if custom types are not allowed', function() {
47 | element = compileAndLink('');
48 | var dragstart = Dragstart.on(element, {allowedMimeTypes: ['Text', 'application/json']});
49 | expect(dragstart.data).toEqual({'application/json': '{"item":[1]}'});
50 | });
51 |
52 | it('uses Text mime type in Internet Explorer', function() {
53 | element = compileAndLink('');
54 | var dragstart = Dragstart.on(element, {allowedMimeTypes: ['URL', 'Text']});
55 | expect(dragstart.data).toEqual({
56 | 'Text': '{"item":{},"type":"foo"}'
57 | });
58 | });
59 |
60 | it('stops propagation', function() {
61 | expect(Dragstart.on(element).propagationStopped).toBe(true);
62 | });
63 |
64 | it('sets effectAllowed to move by default', function() {
65 | expect(Dragstart.on(element).effectAllowed).toBe('move');
66 | });
67 |
68 | it('sets effectAllowed from dnd-effect-allowed', function() {
69 | element = compileAndLink('');
70 | expect(Dragstart.on(element).effectAllowed).toBe('copyMove');
71 | });
72 |
73 | it('sets effectAllowed to single effect in IE', function() {
74 | element = compileAndLink('');
75 | expect(Dragstart.on(element, {allowedMimeTypes: ['Text']}).effectAllowed).toBe('copy');
76 | });
77 |
78 | it('adds CSS classes to element', inject(function($timeout) {
79 | Dragstart.on(element);
80 | expect(element.hasClass('dndDragging')).toBe(true);
81 | expect(element.hasClass('dndDraggingSource')).toBe(false);
82 |
83 | $timeout.flush(0);
84 | expect(element.hasClass('dndDraggingSource')).toBe(true);
85 | }));
86 |
87 | it('invokes dnd-dragstart callback', function() {
88 | element = compileAndLink('');
89 | Dragstart.on(element);
90 | expect(element.scope().ev).toEqual(jasmine.any(DragEventMock));
91 | });
92 |
93 | it('does not start dragging if dnd-disable-if is true', function() {
94 | element = compileAndLink('');
95 | var dragstart = Dragstart.on(element);
96 | expect(dragstart.returnValue).toBe(true);
97 | expect(dragstart.defaultPrevented).toBe(false);
98 | expect(dragstart.propagationStopped).toBe(false);
99 | });
100 |
101 | it('sets the dragImage if event was triggered on a dnd-handle', function() {
102 | var dragstart = Dragstart.on(element, {allowSetDragImage: true, dndHandle: true});
103 | expect(dragstart.dragImage).toBe(element[0]);
104 | });
105 | });
106 |
107 | describe('dragend handler', function() {
108 | var element, dragstart;
109 |
110 | beforeEach(function() {
111 | element = compileAndLink(SIMPLE_HTML);
112 | dragstart = Dragstart.on(element);
113 | });
114 |
115 | it('stops propagation', function() {
116 | expect(dragstart.dragend(element).propagationStopped).toBe(true);
117 | });
118 |
119 | it('removes CSS classes from element', inject(function($timeout) {
120 | $timeout.flush(0);
121 | expect(element.hasClass('dndDragging')).toBe(true);
122 | expect(element.hasClass('dndDraggingSource')).toBe(true);
123 |
124 | dragstart.dragend(element);
125 | expect(element.hasClass('dndDragging')).toBe(false);
126 | expect(element.hasClass('dndDraggingSource')).toBe(false);
127 | }));
128 |
129 | it('removes dndDraggingSource after a timeout', inject(function($timeout) {
130 | // IE 9 might not flush the $timeout before invoking the dragend handler.
131 | expect(element.hasClass('dndDragging')).toBe(true);
132 | expect(element.hasClass('dndDraggingSource')).toBe(false);
133 |
134 | dragstart.dragend(element);
135 | expect(element.hasClass('dndDragging')).toBe(false);
136 | expect(element.hasClass('dndDraggingSource')).toBe(false);
137 |
138 | $timeout.flush(0);
139 | expect(element.hasClass('dndDragging')).toBe(false);
140 | expect(element.hasClass('dndDraggingSource')).toBe(false);
141 | }));
142 |
143 | var dropEffects = {move: 'moved', copy: 'copied', link: 'linked', none: 'canceled'};
144 | angular.forEach(dropEffects, function(callback, dropEffect) {
145 | it('calls callbacks for dropEffect ' + dropEffect, function() {
146 | var html = '';
149 | var element = compileAndLink(html);
150 | var target = compileAndLink('');
151 | Dragstart.on(element).dragover(target).drop(target).dragend(element);
152 |
153 | expect(element.scope().returnedEvent).toEqual(jasmine.any(DragEventMock));
154 | expect(element.scope().returnedDropEffect).toBe(dropEffect);
155 | });
156 | });
157 | });
158 |
159 | describe('click handler', function() {
160 | it('does nothing if dnd-selected is not set', function() {
161 | var element = compileAndLink(SIMPLE_HTML);
162 | var click = new DragEventResult(element, 'click', new DataTransferMock(), {});
163 | expect(click.propagationStopped).toBe(false);
164 | });
165 |
166 | it('invokes dnd-selected callback and stops propagation', function() {
167 | var element = compileAndLink('');
168 | var click = new DragEventResult(element, 'click', new DataTransferMock(), {});
169 | expect(click.propagationStopped).toBe(true);
170 | expect(element.scope().selected).toBe(true);
171 | });
172 | });
173 | });
174 |
--------------------------------------------------------------------------------
/test/mocks.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | class DataTransferMock {
4 | constructor() { this.$results = {}; }
5 | get dropEffect() { throw "Unexcepted dropEffect getter invocation"; }
6 | set dropEffect(value) { throw "Unexcepted dropEffect setter invocation"; }
7 | get effectAllowed() { throw "Unexcepted effectAllowed getter invocation"; }
8 | set effectAllowed(value) { throw "Unexcepted effectAllowed setter invocation"; }
9 | get types() { throw "Unexcepted types getter invocation"; }
10 | set types(value) { throw "Unexcepted types setter invocation"; }
11 | getData() { throw "Unexcepted getData invocation"; }
12 | setData() { throw "Unexcepted setData invocation"; }
13 | setDragImage() { throw "Unexcepted setDragImage invocation"; }
14 | getResults() { return this.$results; }
15 | }
16 |
17 | class DragstartDataTransfer extends DataTransferMock {
18 | constructor(options) {
19 | super();
20 | this.$allowSetDragImage = options.allowSetDragImage || false;
21 | this.$allowedMimeTypes = options.allowedMimeTypes || null;
22 | this.$presetTypes = options.presetTypes || [];
23 | this.$results.data = {};
24 | }
25 |
26 | get effectAllowed() { throw "Unexcepted effectAllowed getter invocation"; }
27 | set effectAllowed(value) { this.$results.effectAllowed = value; }
28 | get types() { return this.$presetTypes; }
29 | set types(value) { throw "Unexcepted types setter invocation"; }
30 |
31 | setData(format, data) {
32 | if (this.$allowedMimeTypes && !this.$allowedMimeTypes.includes(format)) {
33 | throw "Invalid mime type " + format;
34 | }
35 | this.$results.data[format] = data;
36 | }
37 |
38 | setDragImage(img) {
39 | if (!this.$allowSetDragImage) throw "Unexcepted setDragImage invocation";
40 | this.$results.dragImage = img;
41 | }
42 | }
43 |
44 | class DropzoneDataTransfer extends DataTransferMock {
45 | constructor(data, options) {
46 | super();
47 | this.$data = data;
48 | this.$dropEffect = options.dropEffect || 'move';
49 | this.$effectAllowed = options.effectAllowed || 'move';
50 | this.$types = options.undefinedTypes ? undefined : Object.keys(data);
51 | }
52 |
53 | get dropEffect() { throw "Unexcepted dropEffect getter invocation"; }
54 | set dropEffect(value) { this.$results.dropEffect = value; }
55 | get effectAllowed() { return this.$effectAllowed; }
56 | set effectAllowed(value) { throw "Unexcepted effectAllowed setter invocation"; }
57 | get types() { return this.$types; }
58 | set types(value) { throw "Unexcepted types setter invocation"; }
59 | }
60 |
61 | class DropDataTransfer extends DropzoneDataTransfer {
62 | getData(format) { return this.$data[format]; }
63 | }
64 |
65 | class DragEventMock {
66 | constructor(type, dataTransfer, options) {
67 | this.$type = type;
68 | this.$dataTransfer = dataTransfer;
69 | this.$options = options;
70 | this.$results = {dataTransfer: dataTransfer.getResults()};
71 | }
72 |
73 | get clientX() { return this.$options.clientX || 0; }
74 | get clientY() { return this.$options.clientY || 0; }
75 | get ctrlKey() { return this.$options.ctrlKey || false; }
76 | get altKey() { return this.$options.altKey || false; }
77 | get dataTransfer() { return this.$dataTransfer; }
78 | get originalEvent() { return this; }
79 | get target() { return this.$options.target || undefined; }
80 | get type() { return this.$type; }
81 | get _dndHandle() { return this.$options.dndHandle || undefined; }
82 | get _dndPhShown() { return this.$options.phShown || undefined; }
83 | set _dndPhShown(value) { this.$results.setDndPhShown = value; }
84 |
85 | preventDefault() { this.$results.invokedPreventDefault = true; }
86 | stopPropagation() { this.$results.invokedStopPropagation = true; }
87 | getResults() { return this.$results; }
88 | }
89 |
90 | class DragEventResult {
91 | constructor(element, type, dataTransfer, opt_eventOptions) {
92 | let handler = $._data($(element).get(0), "events")[type][0].handler;
93 | let event = new DragEventMock(type, dataTransfer, opt_eventOptions || {});
94 | this.$results = event.getResults();
95 | this.$results.returnValue = handler(event);
96 | this.$type = type;
97 | }
98 |
99 | get propagationStopped() { return !!this.$results.invokedStopPropagation; }
100 | get defaultPrevented() { return !!this.$results.invokedPreventDefault; }
101 | get dndPhShownSet() { return this.$results.setDndPhShown || false; }
102 | get returnValue() { return this.$results.returnValue; }
103 | get dropEffect() { return this.$results.dataTransfer.dropEffect; }
104 | get type() { return this.$type; }
105 | }
106 |
107 | class Dragstart extends DragEventResult {
108 | constructor(element, options) {
109 | super(element, 'dragstart', new DragstartDataTransfer(options), options);
110 | }
111 |
112 | get data() { return this.$results.dataTransfer.data; }
113 | get dragImage() { return this.$results.dataTransfer.dragImage; }
114 | get effectAllowed() { return this.$results.dataTransfer.effectAllowed; }
115 |
116 | dragenter(element, opt_options) {
117 | var options = $.extend({effectAllowed: this.effectAllowed}, opt_options);
118 | return new Dragenter(element, this.$results.dataTransfer.data, options);
119 | }
120 |
121 | dragover(element, opt_options) {
122 | return this.dragenter(element, opt_options).dragover(element);
123 | }
124 |
125 | dragend(element) {
126 | return Dragend.on(element);
127 | }
128 |
129 | static on(element, opt_options) {
130 | return new Dragstart(element, opt_options || {});
131 | }
132 | }
133 |
134 | class Dragend extends DragEventResult {
135 | constructor(element, options) {
136 | super(element, 'dragend', new DataTransferMock(), options);
137 | }
138 |
139 | static on(element, opt_options) {
140 | return new Dragend(element, opt_options || {});
141 | }
142 | }
143 |
144 | class DropzoneEventResult extends DragEventResult {
145 | constructor(element, type, data, dataTransfer, options) {
146 | options.target = options.target || element[0];
147 | super(element, type, dataTransfer, options);
148 | this.$originalData = $.extend({}, data);
149 | this.$options = options;
150 | }
151 |
152 | dragover(element, opt_options) {
153 | return new Dragover(element, this.$originalData, $.extend({}, this.$options, opt_options));
154 | }
155 | }
156 |
157 | class Dragenter extends DropzoneEventResult {
158 | constructor(element, data, options) {
159 | super(element, 'dragenter', data, new DropzoneDataTransfer(data, options), options);
160 | }
161 |
162 | static externalOn(element, data, opt_options) {
163 | return new Dragenter(element, data, opt_options || {});
164 | }
165 |
166 | static validExternalOn(element, opt_options) {
167 | return Dragenter.externalOn(element, {'application/x-dnd': '{"hello":"world"}'}, opt_options);
168 | }
169 | }
170 |
171 | class Dragover extends DropzoneEventResult {
172 | constructor(element, data, options) {
173 | super(element, 'dragover', data, new DropzoneDataTransfer(data, options), options);
174 | }
175 |
176 | dragleave(element, opt_options) {
177 | return new Dragleave(element, this.$originalData, $.extend({}, this.$options, opt_options));
178 | }
179 |
180 | drop(element, opt_options) {
181 | return new Drop(element, this.$originalData, $.extend({}, this.$options, opt_options));
182 | }
183 | }
184 |
185 | class Dragleave extends DropzoneEventResult {
186 | constructor(element, data, options) {
187 | super(element, 'dragleave', data, new DataTransferMock(), options);
188 | }
189 | }
190 |
191 | class Drop extends DropzoneEventResult {
192 | constructor(element, data, options) {
193 | super(element, 'drop', data, new DropDataTransfer(data, options), options);
194 | }
195 |
196 | dragend(element) {
197 | return Dragend.on(element);
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.1.0 (2017-01-15)
2 |
3 | ## Changes
4 |
5 | - **Custom callbacks with dnd-callback**: The new `dnd-callback` attribute allows for communication between the source and target scopes. For example, this can be used to access information about the object being dragged during dragover, or to transfer objects without serialization, which allows to use complex objects that contain functions references and prototypes. [Demo](https://jsfiddle.net/Ldxffyod/1/)
6 | - **Drop effects fixed and extended**: Most of the bugs around drop effect have been fixed. You can now use move, copy and link, or a combination of them. Drop effects can be restricted using `dnd-effect-allowed` on both the source and target element, and if there are multiple options, the user can choose one using modifier keys (Ctrl or Alt). See the [design document](https://github.com/marceljuenemann/angular-drag-and-drop-lists/wiki/Drop-Effects-Design) for more details. Drop effects don't work on IE9. They do work accross browser tabs if `dnd-external-sources` is activated, although the source restrictions are lost in Safari and IE.
7 | - **New dragleave handler**: Previously, the dragleave handler used a timeout to determine whether the placeholder needs to be removed from a `dnd-list`. The new implementation utilizes `document.elementFromPoint` to determine whether the mouse cursor is outside the target list.
8 | - **Remove dndDraggingSource without timeout**: Fixes problems with ngAnimate (#121).
9 |
10 | ## Tested browsers
11 |
12 | - Chrome 55 (Mac, Ubuntu & Windows 7)
13 | - Firefox 50 (Ubuntu)
14 | - Safari 10 (MacOS)
15 | - Microsoft Edge 20 (Windows 10 emulator)
16 | - Internet Explorer 11 (Windows 7)
17 | - Internet Explorer 9 (Windows 7 emulator)
18 |
19 |
20 | # 2.0.0 (2016-12-25)
21 |
22 | ## Changes
23 |
24 | There have been some major changes to how the directive works internally, although these changes should not affect users of the library.
25 |
26 | - **Simpler placeholder positioning algorithm**: The logic for placeholder positiong is unchanged, i.e. the placeholder will be placed after an element if the mouse cursor is in the second half of the element it is hovering over, otherwise it is placed before it. However, the implementation of this algorithm was massively simplified by using `getBoundingClientRect`. As a result, developers are no longer required to have `position: relative` on the list and list item elements.
27 | - **New dataTransfer algorithm**: The directive now uses custom mime types in modern browsers, and falls back to using `Text` in non-standard comform browsers. As a result, dragged elements can no longer be dropped into arbitrary input fields. More details on how this works can be found in the [design document](https://github.com/marceljuenemann/angular-drag-and-drop-lists/wiki/Data-Transfer-Design). **Breaking change:** As mime types are used, all dnd-type attributes are automatically converted to lower case.
28 | - **Internal test infrastructure**: The mocks used for drag and drop events in unit tests are now much nicer.
29 |
30 | ## Tested browsers
31 |
32 | - Chrome 55 (Mac, Ubuntu & Windows 10)
33 | - Firefox 50 (Ubuntu)
34 | - Safari 10 (Mac)
35 | - Microsoft Edge 20 (Windows 10)
36 | - Internet Explorer 11 (Windows 10)
37 | - Internet Explorer 9 (Windows Vista)
38 |
39 |
40 | # 1.4.0 (2016-02-06)
41 |
42 | ## Features
43 |
44 | - **dnd-handle directive**: This directive can be used in combination with `dnd-nodrag`, so that a `dnd-draggable` can only be dragged by using certain handle elements. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/types)
45 | - **dnd-drop can handle insertion**: The `dnd-drop` callback can now return true to signalize that it will take care of inserting the dropped element itself. `dnd-list` will then no longer insert any elements into the list, but will still call the `dnd-inserted` callback.
46 |
47 | ## Bug Fixes
48 |
49 | - **Fix dnd-disable-if on dnd-draggable**: When you disabled a `dnd-draggable` with `dnd-disable-if`, the user was still able to trigger a drag of that element by selecting some text inside the element. (issue #159)
50 | - **dnd-list now handles the dragenter event**: According to the HTML5 standard dropzones need to handle the `dragenter` event, although there doesn't seem to be any browser that enforces this. (issue #118)
51 |
52 | ## Tested browsers
53 |
54 | - Chrome 48 (Mac, Ubuntu & Windows 10)
55 | - Firefox 44 (Ubuntu)
56 | - Safari 9 (Mac)
57 | - Microsoft Edge 20 (Windows 10)
58 | - Internet Explorer 11 (Windows 10)
59 | - Internet Explorer 10 & 9 in compatibility mode (Windows 10)
60 |
61 | # 1.3.0 (2015-08-20)
62 |
63 | ## Features
64 |
65 | - **New callbacks**: `dnd-dragend`, `dnd-canceled` and `dnd-inserted`.
66 | - **Custom placeholder elements**: `dnd-list` elements can have custom elements by creating a child element with `dnd-placeholder` class. This is useful for cases where a simple `li` element is not sufficient.
67 | - **dnd-nodrag directive**: This directive can be used inside `dnd-draggable` to prevent dragging certain areas. This is useful for input elements inside the draggable or creating handle elements.
68 |
69 | ## Bug Fixes
70 |
71 | - **Fix user selection inside dnd-draggable**: The `selectstart` event is no longer cancelled.
72 | - **Fix click handler compatibility**: Propagation of click events is now only stopped if the `dnd-selected` attribute is present.
73 | - **Fix IE9 glitch**: Double clicks in IE9 previously would trigger the `dnd-moved` callback, and therefore remove items accidentially. (issue #21)
74 |
75 | ## Tested browsers
76 |
77 | - Chrome 43 (Win7)
78 | - Chrome 44 (Ubuntu)
79 | - Chrome 44 (Mac)
80 | - Firefox 40 (Win7)
81 | - Firefox 39 (Ubuntu)
82 | - Safari 8.0.8 (Mac)
83 | - Internet Explorer 11 (IE9 & 10 in compatibility mode)
84 |
85 | # 1.2.0 (2014-11-30)
86 |
87 | ## Bug Fixes
88 |
89 | - **Fix glitches in Chrome**: When aborting a drag operation or dragging an element on itself, Chrome on Linux sometimes sends `move` as dropEffect instead of `none`. This lead to elements sometimes disappearing. Can be reproduced by dragging an element over itself and aborting with Esc key. (issue #14)
90 | - **Fix dnd-allowed-types in nested lists**: When a drop was not allowed due to the wrong element type, the event was correctly propagated to the parent list. Nevertheless, the drop was still executed, because the drop handler didn't check the type again. (issue #16)
91 |
92 | ## Features
93 |
94 | - **New callbacks**: The `dnd-draggable` directive now has a new `dnd-dragstart` callback besides the existing `dnd-moved` and `dnd-copied`. The `dnd-list` directive got the callbacks `dnd-dragover` and `dnd-drag` added, which are also able to abort a drop. (issue #11)
95 | - **dnd-horizontal-list**: Lists can be marked as horizontal with this new attribute. The positioning algorithm then positions the placeholder left or right of other list items, instead of above or below. (issue #19)
96 | - **dnd-external-sources**: This attribute allows drag and drop accross browser windows. See documentation for details. (issue #9)
97 | - **pointer-events: none no longer required**: The dragover handler now traverses the DOM until it finds the list item node, therefore it's child elements no longer require the pointer-events: none style.
98 |
99 | ## Tested browsers
100 |
101 | - Chrome 38 (Ubuntu)
102 | - Chrome 38 (Win7)
103 | - Chrome 39 (Mac)
104 | - Firefox 31 (Win7)
105 | - Firefox 33 (Ubuntu)
106 | - Safari 7.1 (Mac)
107 | - Internet Explorer 11 (IE9 & 10 in compatibility mode)
108 |
109 | # 1.1.0 (2014-08-31)
110 |
111 | ## Bug Fixes
112 |
113 | - **jQuery compatibility**: jQuery wraps browser events in event.originalEvent
114 |
115 | ## Features
116 |
117 | - **dnd-disable-if attribute**: allows to dynamically disable the drag and drop functionality
118 | - **dnd-type and dnd-allowed-types**: allows to restrict an item to specifc lists depending on it's type
119 |
120 | ## Tested browsers
121 |
122 | - Chrome 34 (Ubuntu)
123 | - Chrome 37 (Mac)
124 | - Chrome 37 (Win7)
125 | - Firefox 28 (Win7)
126 | - Firefox 31 (Ubuntu)
127 | - Safari 7.0.6 (Mac)
128 | - Internet Explorer 11 (IE9 & 10 in compatibility mode)
129 |
130 | # 1.0.0 (2014-04-11)
131 |
132 | Initial release
133 |
134 | # Release checklist
135 |
136 | - Bump versions
137 | - bower.json
138 | - package.json
139 | - JS files
140 | - Minify and test (npm run-script minify, check semicolon at EOF)
141 | - Test different OS & browsers (npm start)
142 | - Update README and CHANGELOG
143 | - Merge to master
144 | - Tag release
145 | - Merge to gh-pages
146 | - Publish to npm
147 |
--------------------------------------------------------------------------------
/demo/framework/vendor/prism.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Prism: Lightweight, robust, elegant syntax highlighting
3 | * MIT license http://www.opensource.org/licenses/mit-license.php/
4 | * @author Lea Verou http://lea.verou.me
5 | */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""+s.tag+">"};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();;
6 | Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});;
7 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//g,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*{))/gi,inside:{punctuation:/[;:]/g}},url:/url\((["']?).*?\1\)/gi,selector:/[^\{\}\s][^\{\};]*(?=\s*\{)/g,property:/(\b|\B)[\w-]+(?=\s*:)/ig,string:/("|')(\\?.)*?\1/g,important:/\B!important\b/gi,ignore:/&(lt|gt|amp);/gi,punctuation:/[\{\};:]/g};Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<|<)style[\w\W]*?(>|>)[\w\W]*?(<|<)\/style(>|>)/ig,inside:{tag:{pattern:/(<|<)style[\w\W]*?(>|>)|(<|<)\/style(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css}}});;
8 | Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};
9 | ;
10 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|get|set|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});;
11 | (function(){if(!window.Prism){return}function $$(a,b){return Array.prototype.slice.call((b||document).querySelectorAll(a))}function hasClass(a,b){b=" "+b+" ";return(" "+a.className+" ").replace(/[\n\t]/g," ").indexOf(b)>-1}var h=crlf=/\r?\n|\r/g;function highlightLines(a,b,c){var d=b.replace(/\s+/g,'').split(','),offset=+a.getAttribute('data-line-offset')||0;var e=parseFloat(getComputedStyle(a).lineHeight);for(var i=0,range;range=d[i++];){range=range.split('-');var f=+range[0],end=+range[1]||f;var g=document.createElement('div');g.textContent=Array(end-f+2).join(' \r\n');g.className=(c||'')+' line-highlight';if(!hasClass(a,'line-numbers')){g.setAttribute('data-start',f);if(end>f){g.setAttribute('data-end',end)}}g.style.top=(f-offset-1)*e+'px';if(hasClass(a,'line-numbers')){a.appendChild(g)}else{(a.querySelector('code')||a).appendChild(g)}}}function applyHash(){var b=location.hash.slice(1);$$('.temporary.line-highlight').forEach(function(a){a.parentNode.removeChild(a)});var c=(b.match(/\.([\d,-]+)$/)||[,''])[1];if(!c||document.getElementById(b)){return}var d=b.slice(0,b.lastIndexOf('.')),pre=document.getElementById(d);if(!pre){return}if(!pre.hasAttribute('data-line')){pre.setAttribute('data-line','')}highlightLines(pre,c,'temporary ');document.querySelector('.temporary.line-highlight').scrollIntoView()}var j=0;Prism.hooks.add('after-highlight',function(b){var c=b.element.parentNode;var d=c&&c.getAttribute('data-line');if(!c||!d||!/pre/i.test(c.nodeName)){return}clearTimeout(j);$$('.line-highlight',c).forEach(function(a){a.parentNode.removeChild(a)});highlightLines(c,d);j=setTimeout(applyHash,1)});addEventListener('hashchange',applyHash)})();
12 | ;
13 | Prism.hooks.add("after-highlight",function(e){var t=e.element.parentNode;if(!t||!/pre/i.test(t.nodeName)||t.className.indexOf("line-numbers")===-1){return}var n=1+e.code.split("\n").length;var r;lines=new Array(n);lines=lines.join("");r=document.createElement("span");r.className="line-numbers-rows";r.innerHTML=lines;if(t.hasAttribute("data-start")){t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)}e.element.appendChild(r)})
14 | ;
15 |
--------------------------------------------------------------------------------
/demo/framework/vendor/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.1.1 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-drag-and-drop-lists
2 | ===========================
3 | Angular directives that allow you to build sortable lists with the native HTML5 drag & drop API. The directives can also be nested to bring drag & drop to your WYSIWYG editor, your tree, or whatever fancy structure you are building.
4 |
5 | ## :warning: Discontinuation Notice
6 |
7 | This library was built for AngularJS 1.x, which is in [maintenance mode](https://docs.angularjs.org/misc/version-support-status#long-term-support). I recommend migrating to [Angular](https://angular.io/) and using one of these alternatives:
8 | * [ngx-drag-drop](https://github.com/reppners/ngx-drag-drop): A fork of this library, re-written for Angular 2 and above.
9 | * [Angular Material Drag & Drop](https://material.angular.io/cdk/drag-drop/): Lots of features and well supported, although it doesn't seem to support nested lists ([bug](https://github.com/angular/components/issues/14093)) and doesn't use the HTML5 Drag & Drop API (which might be a good thing, depending on your use case)
10 | * [ng2-dragula](https://github.com/valor-software/ng2-dragula)
11 |
12 | Let me know if there are other libraries I should add here.
13 |
14 | ## Demo
15 | * [Nested Lists](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/nested)
16 | * [Simple Lists](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/simple)
17 | * [Typed Lists](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/types)
18 | * [Advanced Features](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
19 | * [Multiselection Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/multi)
20 |
21 |
22 | ## Supported browsers
23 |
24 | **Touch devices are not supported**, because they do not implement the HTML5 drag & drop standard. However, you can use a [shim](https://github.com/timruffles/ios-html5-drag-drop-shim) to make it work on touch devices as well.
25 |
26 | Internet Explorer 8 or lower is *not supported*, but all modern browsers are (see changelog for list of tested browsers).
27 |
28 |
29 | ## Download & Installation
30 | * Download `angular-drag-and-drop-lists.js` (or the minified version) and include it in your application. If you use bower or npm, just include the `angular-drag-and-drop-lists` package.
31 | * Add the `dndLists` module as a dependency to your angular app.
32 |
33 | ## dnd-draggable directive
34 | Use the dnd-draggable directive to make your element draggable
35 |
36 | **Attributes**
37 | * `dnd-draggable` Required attribute. The value has to be an object that represents the data of the element. In case of a drag and drop operation the object will be serialized and unserialized on the receiving end.
38 | * `dnd-effect-allowed` Use this attribute to limit the operations that can be performed. Valid options are `move`, `copy` and `link`, as well as `all`, `copyMove`, `copyLink` and `linkMove`, while `move` is the default value. The semantics of these operations are up to you and have to be implemented using the callbacks described below. If you allow multiple options, the user can choose between them by using the modifier keys (OS specific). The cursor will be changed accordingly, expect for IE and Edge, where this is not supported. Note that the implementation of this attribute is very buggy in IE9. This attribute works together with `dnd-external-sources` except on Safari and IE, where the restriction will be lost when dragging accross browser tabs. [Design document](https://github.com/marceljuenemann/angular-drag-and-drop-lists/wiki/Drop-Effects-Design) [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
39 | * `dnd-type` Use this attribute if you have different kinds of items in your application and you want to limit which items can be dropped into which lists. Combine with dnd-allowed-types on the dnd-list(s). This attribute must be a lower case string. Upper case characters can be used, but will be converted to lower case automatically. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/types)
40 | * `dnd-disable-if` You can use this attribute to dynamically disable the draggability of the element. This is useful if you have certain list items that you don't want to be draggable, or if you want to disable drag & drop completely without having two different code branches (e.g. only allow for admins). [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/types)
41 |
42 | **Callbacks**
43 | * `dnd-dragstart` Callback that is invoked when the element was dragged. The original dragstart event will be provided in the local `event` variable. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
44 | * `dnd-moved` Callback that is invoked when the element was moved. Usually you will remove your element from the original list in this callback, since the directive is not doing that for you automatically. The original dragend event will be provided in the local `event` variable. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
45 | * `dnd-copied` Same as dnd-moved, just that it is called when the element was copied instead of moved. The original dragend event will be provided in the local `event` variable. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
46 | * `dnd-linked` Same as dnd-moved, just that it is called when the element was linked instead of moved. The original dragend event will be provided in the local `event` variable. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
47 | * `dnd-canceled` Callback that is invoked if the element was dragged, but the operation was canceled and the element was not dropped. The original dragend event will be provided in the local event variable. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
48 | * `dnd-dragend` Callback that is invoked when the drag operation ended. Available local variables are `event` and `dropEffect`. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
49 | * `dnd-selected` Callback that is invoked when the element was clicked but not dragged. The original click event will be provided in the local `event` variable. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/nested)
50 | * `dnd-callback` Custom callback that is passed to dropzone callbacks and can be used to communicate between source and target scopes. The dropzone can pass user defined variables to this callback. This can be used to transfer objects without serialization, see [Demo](https://jsfiddle.net/Ldxffyod/1/).
51 |
52 | **CSS classes**
53 | * `dndDragging` This class will be added to the element while the element is being dragged. It will affect both the element you see while dragging and the source element that stays at it's position. Do not try to hide the source element with this class, because that will abort the drag operation.
54 | * `dndDraggingSource` This class will be added to the element after the drag operation was started, meaning it only affects the original element that is still at it's source position, and not the "element" that the user is dragging with his mouse pointer
55 |
56 | ## dnd-list directive
57 |
58 | Use the dnd-list attribute to make your list element a dropzone. Usually you will add a single li element as child with the ng-repeat directive. If you don't do that, we will not be able to position the dropped element correctly. If you want your list to be sortable, also add the dnd-draggable directive to your li element(s).
59 |
60 | **Attributes**
61 | * `dnd-list` Required attribute. The value has to be the array in which the data of the dropped element should be inserted. The value can be blank if used with a custom dnd-drop handler that handles the insertion on its own.
62 | * `dnd-allowed-types` Optional array of allowed item types. When used, only items that had a matching dnd-type attribute will be dropable. Upper case characters will automatically be converted to lower case. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/types)
63 | * `dnd-effect-allowed` Optional string expression that limits the drop effects that can be performed on the list. See dnd-effect-allowed on dnd-draggable for more details on allowed options. The default value is `all`.
64 | * `dnd-disable-if` Optional boolean expression. When it evaluates to true, no dropping into the list is possible. Note that this also disables rearranging items inside the list. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/types)
65 | * `dnd-horizontal-list` Optional boolean expression. When it evaluates to true, the positioning algorithm will use the left and right halfs of the list items instead of the upper and lower halfs. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
66 | * `dnd-external-sources` Optional boolean expression. When it evaluates to true, the list accepts drops from sources outside of the current browser tab, which allows to drag and drop accross different browser tabs. The only major browser for which this is currently not working is Microsoft Edge. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
67 |
68 | **Callbacks**
69 | * `dnd-dragover` Optional expression that is invoked when an element is dragged over the list. If the expression is set, but does not return true, the element is not allowed to be dropped. The following variables will be available:
70 | * `event` The original dragover event sent by the browser.
71 | * `index` The position in the list at which the element would be dropped.
72 | * `type` The `dnd-type` set on the dnd-draggable, or undefined if unset. Will be null for drops from external sources in IE and Edge, since we don't know the type in those cases.
73 | * `external` Whether the element was dragged from an external source. See `dnd-external-sources`.
74 | * `dropEffect` The dropEffect that is going to be performed, see dnd-effect-allowed.
75 | * `callback` If dnd-callback was set on the source element, this is a function reference to the callback. The callback can be invoked with custom variables like this: `callback({var1: value1, var2: value2})`. The callback will be executed on the scope of the source element. If dnd-external-sources was set and external is true, this callback will not be available.
76 | * [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
77 | * `dnd-drop` Optional expression that is invoked when an element is dropped on the list. The same variables as for dnd-dragover will be available, with the exception that type is always known and therefore never null. There will also be an `item` variable, which is the transferred object. The return value determines the further handling of the drop:
78 | * `falsy` The drop will be canceled and the element won't be inserted.
79 | * `true` Signalises that the drop is allowed, but the dnd-drop callback will take care of inserting the element.
80 | * Otherwise: All other return values will be treated as the object to insert into the array. In most cases you simply want to return the `item` parameter, but there are no restrictions on what you can return.
81 | * `dnd-inserted` Optional expression that is invoked after a drop if the element was actually inserted into the list. The same local variables as for `dnd-drop` will be available. Note that for reorderings inside the same list the old element will still be in the list due to the fact that `dnd-moved` was not called yet. [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/advanced)
82 |
83 | **CSS classes**
84 | * `dndPlaceholder` When an element is dragged over the list, a new placeholder child element will be added. This element is of type `li` and has the class `dndPlaceholder` set. Alternatively, you can define your own placeholder by creating a child element with `dndPlaceholder` class.
85 | * `dndDragover` This class will be added to the list while an element is being dragged over the list.
86 |
87 | ## dnd-nodrag directive
88 |
89 | Use the `dnd-nodrag` attribute inside of `dnd-draggable` elements to prevent them from starting drag operations. This is especially useful if you want to use input elements inside of `dnd-draggable` elements or create specific handle elements.
90 |
91 | **Note:** This directive does not work in Internet Explorer 9.
92 |
93 | [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/types)
94 |
95 | ## dnd-handle directive
96 |
97 | Use the `dnd-handle` directive within a `dnd-nodrag` element in order to allow dragging of that element after all. Therefore, by combining `dnd-nodrag` and `dnd-handle` you can allow `dnd-draggable` elements to only be dragged via specific *handle* elements.
98 |
99 | **Note:** Internet Explorer will show the handle element as drag image instead of the `dnd-draggable` element. You can work around this by styling the handle element differently when it is being dragged. Use the CSS selector `.dndDragging:not(.dndDraggingSource) [dnd-handle]` for that.
100 |
101 | [Demo](http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/types)
102 |
103 | ## Recommended CSS styles
104 | It is recommended that you apply the following CSS styles:
105 |
106 | * If your application is about moving elements by drag and drop, it is recommended that you hide the source element while dragging, i.e. setting `display: none` on the `.dndDraggingSource` class.
107 | * If your application allows to drop elements into empty lists, you need to ensure that empty lists never have a height or width of zero, e.g. by setting a `min-width`.
108 | * You should style the `.dndPlaceholder` class accordingly.
109 |
110 | **Note:** Previous versions of this directive required `postion: relative` on certain elements, but this is no longer required.
111 |
112 | ## Why another drag & drop library?
113 | There are tons of other drag & drop libraries out there, but none of them met my three requirements:
114 |
115 | * **Angular:** If you use angular.js, you really don't want to throw a bunch of jQuery into your app. Instead you want to use libraries that were built the "angular way" and support **two-way data binding** to update your data model automatically.
116 | * **Nested lists:** If you want to build a **WYSIWYG editor** or have some fancy **tree structure**, the library has to support nested lists.
117 | * **HTML5 drag & drop:** Most drag & drop applications you'll find on the internet use pure JavaScript drag & drop. But with the arrival of HTML5 we can delegate most of the work to the browser. For example: If you want to show the user what they are currently dragging, you'll have to update the position of the element all the time and set it below the mouse pointer. In HTML5 the browser will do that for you! But you can not only save code lines, you can also offer a more **native user experience**: If you click on an element in a pure JavaScript drag & drop implementation, it will usually start the drag operation. But remember what happens when you click an icon on your desktop: The icon will be selected, not dragged! This is the native behaviour you can bring to your web application with HTML5.
118 |
119 | If this doesn't fit your requirements, check out one of the other awesome drag & drop libraries:
120 |
121 | * [angular-ui-tree](https://github.com/JimLiu/angular-ui-tree): Very similar to this library, but does not use the HTML5 API. Therefore you need to write some more markup to see what you are dragging and it will create another DOM node that you have to style. However, if you plan to support touch devices this is probably your best choice.
122 | * [angular-dragdrop](https://github.com/angular-dragdrop/angular-dragdrop): One of many libraries with the same name. This one uses the HTML5 API, but if you want to build (nested) sortable lists, you're on your own, because it does not calculate the correct element position for you.
123 | * [more...](https://www.google.de/search?q=angular+drag+and+drop)
124 |
125 |
126 | ## License
127 |
128 | Copyright (c) 2014 [Marcel Juenemann](mailto:marcel@juenemann.cc)
129 |
130 | Copyright (c) 2014-2017 Google Inc.
131 |
132 | This is not an official Google product (experimental or otherwise), it is just code that happens to be owned by Google.
133 |
134 | [MIT License](https://raw.githubusercontent.com/marceljuenemann/angular-drag-and-drop-lists/master/LICENSE)
135 |
--------------------------------------------------------------------------------
/test/dndListSpec.js:
--------------------------------------------------------------------------------
1 | describe('dndList', function() {
2 |
3 | it('hides the placeholder element', function() {
4 | var element = compileAndLink('');
5 | expect(element.children().length).toBe(0);
6 | });
7 |
8 | it('disallows dropping if dnd-disable-if is true', function() {
9 | var source = compileAndLink('');
10 | var element = compileAndLink('');
11 | element.scope().disabled = true;
12 | forAllHandlers(Dragstart.on(source).dragenter(element), element, verifyDropCancelled);
13 | });
14 |
15 | it('allows drop if dnd-disable-if is false', function() {
16 | var source = compileAndLink('');
17 | var element = compileAndLink('');
18 | forAllHandlers(Dragstart.on(source).dragenter(element), element, verifyDropAccepted);
19 | });
20 |
21 | it('disallows dropping from external sources', function() {
22 | var element = compileAndLink('');
23 | var dragenter = Dragenter.validExternalOn(element);
24 | forAllHandlers(dragenter, element, verifyDropCancelled);
25 | });
26 |
27 | it('allows dropping from external sources if dnd-external-sources is set', function() {
28 | var element = compileAndLink('');
29 | var dragenter = Dragenter.validExternalOn(element);
30 | forAllHandlers(dragenter, element, verifyDropAccepted);
31 | });
32 |
33 | it('disallows drop without valid mime types', function() {
34 | var element = compileAndLink('');
35 | var dragenter = Dragenter.externalOn(element, {'text/plain': '{}'});
36 | forAllHandlers(dragenter, element, verifyDropCancelled);
37 | });
38 |
39 | // Old Internet Explorer versions don't have dataTransfer.types.
40 | it('allows drop if dataTransfer.types is undefined', function() {
41 | var element = compileAndLink('');
42 | var data = angular.toJson({item: {}, type: 'mytype'});
43 | var dragenter = Dragenter.externalOn(element, {'Text': data}, {undefinedTypes: true});
44 | forAllHandlers(dragenter, element, verifyDropAccepted);
45 | });
46 |
47 | it('allows drop if dataTransfer.types contains "Text"', function() {
48 | var element = compileAndLink('');
49 | var data = angular.toJson({item: {}, type: 'mytype'});
50 | var dragenter = Dragenter.externalOn(element, {'Text': data});
51 | forAllHandlers(dragenter, element, verifyDropAccepted);
52 | });
53 |
54 | it('allows drop if dataTransfer.types contains "application/json"', function() {
55 | var element = compileAndLink('');
56 | var data = angular.toJson({item: {}, type: 'mytype'});
57 | var dragenter = Dragenter.externalOn(element, {'x-pdf': '{}', 'application/json': data});
58 | forAllHandlers(dragenter, element, verifyDropAccepted);
59 | });
60 |
61 | it('disallows dropping untyped elements if dnd-allowed-types is set', function() {
62 | var source = compileAndLink('');
63 | var element = compileAndLink('');
64 | forAllHandlers(Dragstart.on(source).dragenter(element), element, verifyDropCancelled);
65 | });
66 |
67 | it('allows dropping typed elements if dnd-allowed-types is not set', function() {
68 | var source = compileAndLink('');
69 | var element = compileAndLink('');
70 | forAllHandlers(Dragstart.on(source).dragenter(element), element, verifyDropAccepted);
71 | });
72 |
73 | it('disallows dropping elements of the wrong type', function() {
74 | var source = compileAndLink('');
75 | var element = compileAndLink('');
76 | forAllHandlers(Dragstart.on(source).dragenter(element), element, verifyDropCancelled);
77 | });
78 |
79 | it('allows dropping elements of the correct type', function() {
80 | var source = compileAndLink('');
81 | var element = compileAndLink('');
82 | forAllHandlers(Dragstart.on(source).dragenter(element), element, verifyDropAccepted);
83 | });
84 |
85 | it('disallows dropping elements of the wrong type (test for Edge)', function() {
86 | var source = compileAndLink('');
87 | var element = compileAndLink('');
88 | var dragstart = Dragstart.on(source, {allowedMimeTypes: ['text/plain', 'application/json']});
89 | forAllHandlers(dragstart.dragenter(element), element, verifyDropCancelled);
90 | });
91 |
92 | it('allows dropping elements of the correct type (test for Edge)', function() {
93 | var source = compileAndLink('');
94 | var element = compileAndLink('');
95 | var dragstart = Dragstart.on(source, {allowedMimeTypes: ['text/plain', 'application/json']});
96 | forAllHandlers(dragstart.dragenter(element), element, verifyDropAccepted);
97 | });
98 |
99 | it('allows dropping external elements if correct type is encoded inside', function() {
100 | var element = compileAndLink('');
102 | var data = angular.toJson({item: {}, type: 'mytype'});
103 | var dragenter = Dragenter.externalOn(element, {'application/json': data});
104 | forAllHandlers(dragenter, element, verifyDropAccepted);
105 | });
106 |
107 | describe('dragover handler', function() {
108 | var source, element;
109 |
110 | beforeEach(function() {
111 | source = compileAndLink('');
112 | element = compileAndLink('');
113 | element.scope().list = [];
114 | });
115 |
116 | it('adds dndDragover CSS class', function() {
117 | Dragstart.on(source).dragover(element);
118 | expect(element.hasClass('dndDragover')).toBe(true);
119 | });
120 |
121 | it('adds placeholder element', function() {
122 | Dragstart.on(source).dragover(element);
123 | expect(element.children().length).toBe(1);
124 | expect(element.children()[0].tagName).toBe('LI');
125 | });
126 |
127 | it('reuses custom placeholder element if it exists', function() {
128 | element = compileAndLink('');
129 | Dragstart.on(source).dragover(element);
130 | expect(element.children().length).toBe(1);
131 | expect(element.children()[0].tagName).toBe('IMG');
132 | });
133 |
134 | it('invokes dnd-dragover callback', function() {
135 | element = createListWithItemsAndCallbacks();
136 | Dragstart.on(source).dragover(element);
137 | expect(element.scope().dragover.event).toEqual(jasmine.any(DragEventMock));
138 | expect(element.scope().dragover.index).toBe(3);
139 | expect(element.scope().dragover.external).toBe(false);
140 | expect(element.scope().dragover.type).toBeUndefined();
141 | expect(element.scope().dragover.item).toBeUndefined();
142 | });
143 |
144 | it('invokes dnd-dragover with correct type', function() {
145 | source = compileAndLink('');
146 | element = createListWithItemsAndCallbacks();
147 | Dragstart.on(source).dragover(element);
148 | expect(element.scope().dragover.type).toBe('mytype');
149 | expect(element.scope().dragover.external).toBe(false);
150 | });
151 |
152 | it('invokes dnd-dragover with correct type (test for IE)', function() {
153 | source = compileAndLink('');
154 | element = createListWithItemsAndCallbacks();
155 | Dragstart.on(source, {allowedMimeTypes: ['Text']}).dragover(element);
156 | expect(element.scope().dragover.type).toBe('mytype');
157 | expect(element.scope().dragover.external).toBe(false);
158 | });
159 |
160 | it('invokes dnd-dragover with correct type for external drops', function() {
161 | element = createListWithItemsAndCallbacks();
162 | Dragenter.externalOn(element, {'application/x-dnd-mytype': {}}).dragover(element);
163 | expect(element.scope().dragover.type).toBe('mytype');
164 | expect(element.scope().dragover.external).toBe(true);
165 | });
166 |
167 | it('invokes dnd-dragover with null type for external drops from IE', function() {
168 | element = createListWithItemsAndCallbacks();
169 | Dragenter.externalOn(element, {'Text': 'unaccessible'}).dragover(element);
170 | expect(element.scope().dragover.type).toBeNull();
171 | expect(element.scope().dragover.external).toBe(true);
172 | });
173 |
174 | it('invokes dnd-dragover with undefined callback', function() {
175 | element = createListWithItemsAndCallbacks();
176 | Dragstart.on(source).dragover(element);
177 | expect(element.scope().dragover.callback).toBeUndefined();
178 | });
179 |
180 | it('invokes dnd-dragover with callback set on dragstart', function() {
181 | source = compileAndLink('');
182 | source.scope().a = 2;
183 | element = compileAndLink('
');
569 | element.scope().dropHandler = function(params) {
570 | element.scope().drop = params;
571 | return params.item;
572 | };
573 | element.scope().list = [1, 2, 3];
574 | return element;
575 | }
576 | });
577 |
--------------------------------------------------------------------------------
/angular-drag-and-drop-lists.js:
--------------------------------------------------------------------------------
1 | /**
2 | * angular-drag-and-drop-lists v2.1.0
3 | *
4 | * Copyright (c) 2014 Marcel Juenemann marcel@juenemann.cc
5 | * Copyright (c) 2014-2017 Google Inc.
6 | * https://github.com/marceljuenemann/angular-drag-and-drop-lists
7 | *
8 | * License: MIT
9 | */
10 | (function(dndLists) {
11 |
12 | // In standard-compliant browsers we use a custom mime type and also encode the dnd-type in it.
13 | // However, IE and Edge only support a limited number of mime types. The workarounds are described
14 | // in https://github.com/marceljuenemann/angular-drag-and-drop-lists/wiki/Data-Transfer-Design
15 | var MIME_TYPE = 'application/x-dnd';
16 | var EDGE_MIME_TYPE = 'application/json';
17 | var MSIE_MIME_TYPE = 'Text';
18 |
19 | // All valid HTML5 drop effects, in the order in which we prefer to use them.
20 | var ALL_EFFECTS = ['move', 'copy', 'link'];
21 |
22 | /**
23 | * Use the dnd-draggable attribute to make your element draggable
24 | *
25 | * Attributes:
26 | * - dnd-draggable Required attribute. The value has to be an object that represents the data
27 | * of the element. In case of a drag and drop operation the object will be
28 | * serialized and unserialized on the receiving end.
29 | * - dnd-effect-allowed Use this attribute to limit the operations that can be performed. Valid
30 | * options are "move", "copy" and "link", as well as "all", "copyMove",
31 | * "copyLink" and "linkMove". The semantics of these operations are up to you
32 | * and have to be implemented using the callbacks described below. If you
33 | * allow multiple options, the user can choose between them by using the
34 | * modifier keys (OS specific). The cursor will be changed accordingly,
35 | * expect for IE and Edge, where this is not supported.
36 | * - dnd-type Use this attribute if you have different kinds of items in your
37 | * application and you want to limit which items can be dropped into which
38 | * lists. Combine with dnd-allowed-types on the dnd-list(s). This attribute
39 | * must be a lower case string. Upper case characters can be used, but will
40 | * be converted to lower case automatically.
41 | * - dnd-disable-if You can use this attribute to dynamically disable the draggability of the
42 | * element. This is useful if you have certain list items that you don't want
43 | * to be draggable, or if you want to disable drag & drop completely without
44 | * having two different code branches (e.g. only allow for admins).
45 | *
46 | * Callbacks:
47 | * - dnd-dragstart Callback that is invoked when the element was dragged. The original
48 | * dragstart event will be provided in the local event variable.
49 | * - dnd-moved Callback that is invoked when the element was moved. Usually you will
50 | * remove your element from the original list in this callback, since the
51 | * directive is not doing that for you automatically. The original dragend
52 | * event will be provided in the local event variable.
53 | * - dnd-copied Same as dnd-moved, just that it is called when the element was copied
54 | * instead of moved, so you probably want to implement a different logic.
55 | * - dnd-linked Same as dnd-moved, just that it is called when the element was linked
56 | * instead of moved, so you probably want to implement a different logic.
57 | * - dnd-canceled Callback that is invoked if the element was dragged, but the operation was
58 | * canceled and the element was not dropped. The original dragend event will
59 | * be provided in the local event variable.
60 | * - dnd-dragend Callback that is invoked when the drag operation ended. Available local
61 | * variables are event and dropEffect.
62 | * - dnd-selected Callback that is invoked when the element was clicked but not dragged.
63 | * The original click event will be provided in the local event variable.
64 | * - dnd-callback Custom callback that is passed to dropzone callbacks and can be used to
65 | * communicate between source and target scopes. The dropzone can pass user
66 | * defined variables to this callback.
67 | *
68 | * CSS classes:
69 | * - dndDragging This class will be added to the element while the element is being
70 | * dragged. It will affect both the element you see while dragging and the
71 | * source element that stays at it's position. Do not try to hide the source
72 | * element with this class, because that will abort the drag operation.
73 | * - dndDraggingSource This class will be added to the element after the drag operation was
74 | * started, meaning it only affects the original element that is still at
75 | * it's source position, and not the "element" that the user is dragging with
76 | * his mouse pointer.
77 | */
78 | dndLists.directive('dndDraggable', ['$parse', '$timeout', function($parse, $timeout) {
79 | return function(scope, element, attr) {
80 | // Set the HTML5 draggable attribute on the element.
81 | element.attr("draggable", "true");
82 |
83 | // If the dnd-disable-if attribute is set, we have to watch that.
84 | if (attr.dndDisableIf) {
85 | scope.$watch(attr.dndDisableIf, function(disabled) {
86 | element.attr("draggable", !disabled);
87 | });
88 | }
89 |
90 | /**
91 | * When the drag operation is started we have to prepare the dataTransfer object,
92 | * which is the primary way we communicate with the target element
93 | */
94 | element.on('dragstart', function(event) {
95 | event = event.originalEvent || event;
96 |
97 | // Check whether the element is draggable, since dragstart might be triggered on a child.
98 | if (element.attr('draggable') == 'false') return true;
99 |
100 | // Initialize global state.
101 | dndState.isDragging = true;
102 | dndState.itemType = attr.dndType && scope.$eval(attr.dndType).toLowerCase();
103 |
104 | // Set the allowed drop effects. See below for special IE handling.
105 | dndState.dropEffect = "none";
106 | dndState.effectAllowed = attr.dndEffectAllowed || ALL_EFFECTS[0];
107 | event.dataTransfer.effectAllowed = dndState.effectAllowed;
108 |
109 | // Internet Explorer and Microsoft Edge don't support custom mime types, see design doc:
110 | // https://github.com/marceljuenemann/angular-drag-and-drop-lists/wiki/Data-Transfer-Design
111 | var item = scope.$eval(attr.dndDraggable);
112 | var mimeType = MIME_TYPE + (dndState.itemType ? ('-' + dndState.itemType) : '');
113 | try {
114 | event.dataTransfer.setData(mimeType, angular.toJson(item));
115 | } catch (e) {
116 | // Setting a custom MIME type did not work, we are probably in IE or Edge.
117 | var data = angular.toJson({item: item, type: dndState.itemType});
118 | try {
119 | event.dataTransfer.setData(EDGE_MIME_TYPE, data);
120 | } catch (e) {
121 | // We are in Internet Explorer and can only use the Text MIME type. Also note that IE
122 | // does not allow changing the cursor in the dragover event, therefore we have to choose
123 | // the one we want to display now by setting effectAllowed.
124 | var effectsAllowed = filterEffects(ALL_EFFECTS, dndState.effectAllowed);
125 | event.dataTransfer.effectAllowed = effectsAllowed[0];
126 | event.dataTransfer.setData(MSIE_MIME_TYPE, data);
127 | }
128 | }
129 |
130 | // Add CSS classes. See documentation above.
131 | element.addClass("dndDragging");
132 | $timeout(function() { element.addClass("dndDraggingSource"); }, 0);
133 |
134 | // Try setting a proper drag image if triggered on a dnd-handle (won't work in IE).
135 | if (event._dndHandle && event.dataTransfer.setDragImage) {
136 | event.dataTransfer.setDragImage(element[0], 0, 0);
137 | }
138 |
139 | // Invoke dragstart callback and prepare extra callback for dropzone.
140 | $parse(attr.dndDragstart)(scope, {event: event});
141 | if (attr.dndCallback) {
142 | var callback = $parse(attr.dndCallback);
143 | dndState.callback = function(params) { return callback(scope, params || {}); };
144 | }
145 |
146 | event.stopPropagation();
147 | });
148 |
149 | /**
150 | * The dragend event is triggered when the element was dropped or when the drag
151 | * operation was aborted (e.g. hit escape button). Depending on the executed action
152 | * we will invoke the callbacks specified with the dnd-moved or dnd-copied attribute.
153 | */
154 | element.on('dragend', function(event) {
155 | event = event.originalEvent || event;
156 |
157 | // Invoke callbacks. Usually we would use event.dataTransfer.dropEffect to determine
158 | // the used effect, but Chrome has not implemented that field correctly. On Windows
159 | // it always sets it to 'none', while Chrome on Linux sometimes sets it to something
160 | // else when it's supposed to send 'none' (drag operation aborted).
161 | scope.$apply(function() {
162 | var dropEffect = dndState.dropEffect;
163 | var cb = {copy: 'dndCopied', link: 'dndLinked', move: 'dndMoved', none: 'dndCanceled'};
164 | $parse(attr[cb[dropEffect]])(scope, {event: event});
165 | $parse(attr.dndDragend)(scope, {event: event, dropEffect: dropEffect});
166 | });
167 |
168 | // Clean up
169 | dndState.isDragging = false;
170 | dndState.callback = undefined;
171 | element.removeClass("dndDragging");
172 | element.removeClass("dndDraggingSource");
173 | event.stopPropagation();
174 |
175 | // In IE9 it is possible that the timeout from dragstart triggers after the dragend handler.
176 | $timeout(function() { element.removeClass("dndDraggingSource"); }, 0);
177 | });
178 |
179 | /**
180 | * When the element is clicked we invoke the callback function
181 | * specified with the dnd-selected attribute.
182 | */
183 | element.on('click', function(event) {
184 | if (!attr.dndSelected) return;
185 |
186 | event = event.originalEvent || event;
187 | scope.$apply(function() {
188 | $parse(attr.dndSelected)(scope, {event: event});
189 | });
190 |
191 | // Prevent triggering dndSelected in parent elements.
192 | event.stopPropagation();
193 | });
194 |
195 | /**
196 | * Workaround to make element draggable in IE9
197 | */
198 | element.on('selectstart', function() {
199 | if (this.dragDrop) this.dragDrop();
200 | });
201 | };
202 | }]);
203 |
204 | /**
205 | * Use the dnd-list attribute to make your list element a dropzone. Usually you will add a single
206 | * li element as child with the ng-repeat directive. If you don't do that, we will not be able to
207 | * position the dropped element correctly. If you want your list to be sortable, also add the
208 | * dnd-draggable directive to your li element(s).
209 | *
210 | * Attributes:
211 | * - dnd-list Required attribute. The value has to be the array in which the data of
212 | * the dropped element should be inserted. The value can be blank if used
213 | * with a custom dnd-drop handler that always returns true.
214 | * - dnd-allowed-types Optional array of allowed item types. When used, only items that had a
215 | * matching dnd-type attribute will be dropable. Upper case characters will
216 | * automatically be converted to lower case.
217 | * - dnd-effect-allowed Optional string expression that limits the drop effects that can be
218 | * performed in the list. See dnd-effect-allowed on dnd-draggable for more
219 | * details on allowed options. The default value is all.
220 | * - dnd-disable-if Optional boolean expresssion. When it evaluates to true, no dropping
221 | * into the list is possible. Note that this also disables rearranging
222 | * items inside the list.
223 | * - dnd-horizontal-list Optional boolean expresssion. When it evaluates to true, the positioning
224 | * algorithm will use the left and right halfs of the list items instead of
225 | * the upper and lower halfs.
226 | * - dnd-external-sources Optional boolean expression. When it evaluates to true, the list accepts
227 | * drops from sources outside of the current browser tab. This allows to
228 | * drag and drop accross different browser tabs. The only major browser
229 | * that does not support this is currently Microsoft Edge.
230 | *
231 | * Callbacks:
232 | * - dnd-dragover Optional expression that is invoked when an element is dragged over the
233 | * list. If the expression is set, but does not return true, the element is
234 | * not allowed to be dropped. The following variables will be available:
235 | * - event: The original dragover event sent by the browser.
236 | * - index: The position in the list at which the element would be dropped.
237 | * - type: The dnd-type set on the dnd-draggable, or undefined if non was
238 | * set. Will be null for drops from external sources in IE and Edge,
239 | * since we don't know the type in those cases.
240 | * - dropEffect: One of move, copy or link, see dnd-effect-allowed.
241 | * - external: Whether the element was dragged from an external source.
242 | * - callback: If dnd-callback was set on the source element, this is a
243 | * function reference to the callback. The callback can be invoked with
244 | * custom variables like this: callback({var1: value1, var2: value2}).
245 | * The callback will be executed on the scope of the source element. If
246 | * dnd-external-sources was set and external is true, this callback will
247 | * not be available.
248 | * - dnd-drop Optional expression that is invoked when an element is dropped on the
249 | * list. The same variables as for dnd-dragover will be available, with the
250 | * exception that type is always known and therefore never null. There
251 | * will also be an item variable, which is the transferred object. The
252 | * return value determines the further handling of the drop:
253 | * - falsy: The drop will be canceled and the element won't be inserted.
254 | * - true: Signalises that the drop is allowed, but the dnd-drop
255 | * callback already took care of inserting the element.
256 | * - otherwise: All other return values will be treated as the object to
257 | * insert into the array. In most cases you want to simply return the
258 | * item parameter, but there are no restrictions on what you can return.
259 | * - dnd-inserted Optional expression that is invoked after a drop if the element was
260 | * actually inserted into the list. The same local variables as for
261 | * dnd-drop will be available. Note that for reorderings inside the same
262 | * list the old element will still be in the list due to the fact that
263 | * dnd-moved was not called yet.
264 | *
265 | * CSS classes:
266 | * - dndPlaceholder When an element is dragged over the list, a new placeholder child
267 | * element will be added. This element is of type li and has the class
268 | * dndPlaceholder set. Alternatively, you can define your own placeholder
269 | * by creating a child element with dndPlaceholder class.
270 | * - dndDragover Will be added to the list while an element is dragged over the list.
271 | */
272 | dndLists.directive('dndList', ['$parse', function($parse) {
273 | return function(scope, element, attr) {
274 | // While an element is dragged over the list, this placeholder element is inserted
275 | // at the location where the element would be inserted after dropping.
276 | var placeholder = getPlaceholderElement();
277 | placeholder.remove();
278 |
279 | var placeholderNode = placeholder[0];
280 | var listNode = element[0];
281 | var listSettings = {};
282 |
283 | /**
284 | * The dragenter event is fired when a dragged element or text selection enters a valid drop
285 | * target. According to the spec, we either need to have a dropzone attribute or listen on
286 | * dragenter events and call preventDefault(). It should be noted though that no browser seems
287 | * to enforce this behaviour.
288 | */
289 | element.on('dragenter', function (event) {
290 | event = event.originalEvent || event;
291 |
292 | // Calculate list properties, so that we don't have to repeat this on every dragover event.
293 | var types = attr.dndAllowedTypes && scope.$eval(attr.dndAllowedTypes);
294 | listSettings = {
295 | allowedTypes: angular.isArray(types) && types.join('|').toLowerCase().split('|'),
296 | disabled: attr.dndDisableIf && scope.$eval(attr.dndDisableIf),
297 | externalSources: attr.dndExternalSources && scope.$eval(attr.dndExternalSources),
298 | horizontal: attr.dndHorizontalList && scope.$eval(attr.dndHorizontalList)
299 | };
300 |
301 | var mimeType = getMimeType(event.dataTransfer.types);
302 | if (!mimeType || !isDropAllowed(getItemType(mimeType))) return true;
303 | event.preventDefault();
304 | });
305 |
306 | /**
307 | * The dragover event is triggered "every few hundred milliseconds" while an element
308 | * is being dragged over our list, or over an child element.
309 | */
310 | element.on('dragover', function(event) {
311 | event = event.originalEvent || event;
312 |
313 | // Check whether the drop is allowed and determine mime type.
314 | var mimeType = getMimeType(event.dataTransfer.types);
315 | var itemType = getItemType(mimeType);
316 | if (!mimeType || !isDropAllowed(itemType)) return true;
317 |
318 | // Make sure the placeholder is shown, which is especially important if the list is empty.
319 | if (placeholderNode.parentNode != listNode) {
320 | element.append(placeholder);
321 | }
322 |
323 | if (event.target != listNode) {
324 | // Try to find the node direct directly below the list node.
325 | var listItemNode = event.target;
326 | while (listItemNode.parentNode != listNode && listItemNode.parentNode) {
327 | listItemNode = listItemNode.parentNode;
328 | }
329 |
330 | if (listItemNode.parentNode == listNode && listItemNode != placeholderNode) {
331 | // If the mouse pointer is in the upper half of the list item element,
332 | // we position the placeholder before the list item, otherwise after it.
333 | var rect = listItemNode.getBoundingClientRect();
334 | if (listSettings.horizontal) {
335 | var isFirstHalf = event.clientX < rect.left + rect.width / 2;
336 | } else {
337 | var isFirstHalf = event.clientY < rect.top + rect.height / 2;
338 | }
339 | listNode.insertBefore(placeholderNode,
340 | isFirstHalf ? listItemNode : listItemNode.nextSibling);
341 | }
342 | }
343 |
344 | // In IE we set a fake effectAllowed in dragstart to get the correct cursor, we therefore
345 | // ignore the effectAllowed passed in dataTransfer. We must also not access dataTransfer for
346 | // drops from external sources, as that throws an exception.
347 | var ignoreDataTransfer = mimeType == MSIE_MIME_TYPE;
348 | var dropEffect = getDropEffect(event, ignoreDataTransfer);
349 | if (dropEffect == 'none') return stopDragover();
350 |
351 | // At this point we invoke the callback, which still can disallow the drop.
352 | // We can't do this earlier because we want to pass the index of the placeholder.
353 | if (attr.dndDragover && !invokeCallback(attr.dndDragover, event, dropEffect, itemType)) {
354 | return stopDragover();
355 | }
356 |
357 | // Set dropEffect to modify the cursor shown by the browser, unless we're in IE, where this
358 | // is not supported. This must be done after preventDefault in Firefox.
359 | event.preventDefault();
360 | if (!ignoreDataTransfer) {
361 | event.dataTransfer.dropEffect = dropEffect;
362 | }
363 |
364 | element.addClass("dndDragover");
365 | event.stopPropagation();
366 | return false;
367 | });
368 |
369 | /**
370 | * When the element is dropped, we use the position of the placeholder element as the
371 | * position where we insert the transferred data. This assumes that the list has exactly
372 | * one child element per array element.
373 | */
374 | element.on('drop', function(event) {
375 | event = event.originalEvent || event;
376 |
377 | // Check whether the drop is allowed and determine mime type.
378 | var mimeType = getMimeType(event.dataTransfer.types);
379 | var itemType = getItemType(mimeType);
380 | if (!mimeType || !isDropAllowed(itemType)) return true;
381 |
382 | // The default behavior in Firefox is to interpret the dropped element as URL and
383 | // forward to it. We want to prevent that even if our drop is aborted.
384 | event.preventDefault();
385 |
386 | // Unserialize the data that was serialized in dragstart.
387 | try {
388 | var data = JSON.parse(event.dataTransfer.getData(mimeType));
389 | } catch(e) {
390 | return stopDragover();
391 | }
392 |
393 | // Drops with invalid types from external sources might not have been filtered out yet.
394 | if (mimeType == MSIE_MIME_TYPE || mimeType == EDGE_MIME_TYPE) {
395 | itemType = data.type || undefined;
396 | data = data.item;
397 | if (!isDropAllowed(itemType)) return stopDragover();
398 | }
399 |
400 | // Special handling for internal IE drops, see dragover handler.
401 | var ignoreDataTransfer = mimeType == MSIE_MIME_TYPE;
402 | var dropEffect = getDropEffect(event, ignoreDataTransfer);
403 | if (dropEffect == 'none') return stopDragover();
404 |
405 | // Invoke the callback, which can transform the transferredObject and even abort the drop.
406 | var index = getPlaceholderIndex();
407 | if (attr.dndDrop) {
408 | data = invokeCallback(attr.dndDrop, event, dropEffect, itemType, index, data);
409 | if (!data) return stopDragover();
410 | }
411 |
412 | // The drop is definitely going to happen now, store the dropEffect.
413 | dndState.dropEffect = dropEffect;
414 | if (!ignoreDataTransfer) {
415 | event.dataTransfer.dropEffect = dropEffect;
416 | }
417 |
418 | // Insert the object into the array, unless dnd-drop took care of that (returned true).
419 | if (data !== true) {
420 | scope.$apply(function() {
421 | scope.$eval(attr.dndList).splice(index, 0, data);
422 | });
423 | }
424 | invokeCallback(attr.dndInserted, event, dropEffect, itemType, index, data);
425 |
426 | // Clean up
427 | stopDragover();
428 | event.stopPropagation();
429 | return false;
430 | });
431 |
432 | /**
433 | * We have to remove the placeholder when the element is no longer dragged over our list. The
434 | * problem is that the dragleave event is not only fired when the element leaves our list,
435 | * but also when it leaves a child element. Therefore, we determine whether the mouse cursor
436 | * is still pointing to an element inside the list or not.
437 | */
438 | element.on('dragleave', function(event) {
439 | event = event.originalEvent || event;
440 |
441 | var newTarget = document.elementFromPoint(event.clientX, event.clientY);
442 | if (listNode.contains(newTarget) && !event._dndPhShown) {
443 | // Signalize to potential parent lists that a placeholder is already shown.
444 | event._dndPhShown = true;
445 | } else {
446 | stopDragover();
447 | }
448 | });
449 |
450 | /**
451 | * Given the types array from the DataTransfer object, returns the first valid mime type.
452 | * A type is valid if it starts with MIME_TYPE, or it equals MSIE_MIME_TYPE or EDGE_MIME_TYPE.
453 | */
454 | function getMimeType(types) {
455 | if (!types) return MSIE_MIME_TYPE; // IE 9 workaround.
456 | for (var i = 0; i < types.length; i++) {
457 | if (types[i] == MSIE_MIME_TYPE || types[i] == EDGE_MIME_TYPE ||
458 | types[i].substr(0, MIME_TYPE.length) == MIME_TYPE) {
459 | return types[i];
460 | }
461 | }
462 | return null;
463 | }
464 |
465 | /**
466 | * Determines the type of the item from the dndState, or from the mime type for items from
467 | * external sources. Returns undefined if no item type was set and null if the item type could
468 | * not be determined.
469 | */
470 | function getItemType(mimeType) {
471 | if (dndState.isDragging) return dndState.itemType || undefined;
472 | if (mimeType == MSIE_MIME_TYPE || mimeType == EDGE_MIME_TYPE) return null;
473 | return (mimeType && mimeType.substr(MIME_TYPE.length + 1)) || undefined;
474 | }
475 |
476 | /**
477 | * Checks various conditions that must be fulfilled for a drop to be allowed, including the
478 | * dnd-allowed-types attribute. If the item Type is unknown (null), the drop will be allowed.
479 | */
480 | function isDropAllowed(itemType) {
481 | if (listSettings.disabled) return false;
482 | if (!listSettings.externalSources && !dndState.isDragging) return false;
483 | if (!listSettings.allowedTypes || itemType === null) return true;
484 | return itemType && listSettings.allowedTypes.indexOf(itemType) != -1;
485 | }
486 |
487 | /**
488 | * Determines which drop effect to use for the given event. In Internet Explorer we have to
489 | * ignore the effectAllowed field on dataTransfer, since we set a fake value in dragstart.
490 | * In those cases we rely on dndState to filter effects. Read the design doc for more details:
491 | * https://github.com/marceljuenemann/angular-drag-and-drop-lists/wiki/Data-Transfer-Design
492 | */
493 | function getDropEffect(event, ignoreDataTransfer) {
494 | var effects = ALL_EFFECTS;
495 | if (!ignoreDataTransfer) {
496 | effects = filterEffects(effects, event.dataTransfer.effectAllowed);
497 | }
498 | if (dndState.isDragging) {
499 | effects = filterEffects(effects, dndState.effectAllowed);
500 | }
501 | if (attr.dndEffectAllowed) {
502 | effects = filterEffects(effects, attr.dndEffectAllowed);
503 | }
504 | // MacOS automatically filters dataTransfer.effectAllowed depending on the modifier keys,
505 | // therefore the following modifier keys will only affect other operating systems.
506 | if (!effects.length) {
507 | return 'none';
508 | } else if (event.ctrlKey && effects.indexOf('copy') != -1) {
509 | return 'copy';
510 | } else if (event.altKey && effects.indexOf('link') != -1) {
511 | return 'link';
512 | } else {
513 | return effects[0];
514 | }
515 | }
516 |
517 | /**
518 | * Small helper function that cleans up if we aborted a drop.
519 | */
520 | function stopDragover() {
521 | placeholder.remove();
522 | element.removeClass("dndDragover");
523 | return true;
524 | }
525 |
526 | /**
527 | * Invokes a callback with some interesting parameters and returns the callbacks return value.
528 | */
529 | function invokeCallback(expression, event, dropEffect, itemType, index, item) {
530 | return $parse(expression)(scope, {
531 | callback: dndState.callback,
532 | dropEffect: dropEffect,
533 | event: event,
534 | external: !dndState.isDragging,
535 | index: index !== undefined ? index : getPlaceholderIndex(),
536 | item: item || undefined,
537 | type: itemType
538 | });
539 | }
540 |
541 | /**
542 | * We use the position of the placeholder node to determine at which position of the array the
543 | * object needs to be inserted
544 | */
545 | function getPlaceholderIndex() {
546 | return Array.prototype.indexOf.call(listNode.children, placeholderNode);
547 | }
548 |
549 | /**
550 | * Tries to find a child element that has the dndPlaceholder class set. If none was found, a
551 | * new li element is created.
552 | */
553 | function getPlaceholderElement() {
554 | var placeholder;
555 | angular.forEach(element.children(), function(childNode) {
556 | var child = angular.element(childNode);
557 | if (child.hasClass('dndPlaceholder')) {
558 | placeholder = child;
559 | }
560 | });
561 | return placeholder || angular.element("");
562 | }
563 | };
564 | }]);
565 |
566 | /**
567 | * Use the dnd-nodrag attribute inside of dnd-draggable elements to prevent them from starting
568 | * drag operations. This is especially useful if you want to use input elements inside of
569 | * dnd-draggable elements or create specific handle elements. Note: This directive does not work
570 | * in Internet Explorer 9.
571 | */
572 | dndLists.directive('dndNodrag', function() {
573 | return function(scope, element, attr) {
574 | // Set as draggable so that we can cancel the events explicitly
575 | element.attr("draggable", "true");
576 |
577 | /**
578 | * Since the element is draggable, the browser's default operation is to drag it on dragstart.
579 | * We will prevent that and also stop the event from bubbling up.
580 | */
581 | element.on('dragstart', function(event) {
582 | event = event.originalEvent || event;
583 |
584 | if (!event._dndHandle) {
585 | // If a child element already reacted to dragstart and set a dataTransfer object, we will
586 | // allow that. For example, this is the case for user selections inside of input elements.
587 | if (!(event.dataTransfer.types && event.dataTransfer.types.length)) {
588 | event.preventDefault();
589 | }
590 | event.stopPropagation();
591 | }
592 | });
593 |
594 | /**
595 | * Stop propagation of dragend events, otherwise dnd-moved might be triggered and the element
596 | * would be removed.
597 | */
598 | element.on('dragend', function(event) {
599 | event = event.originalEvent || event;
600 | if (!event._dndHandle) {
601 | event.stopPropagation();
602 | }
603 | });
604 | };
605 | });
606 |
607 | /**
608 | * Use the dnd-handle directive within a dnd-nodrag element in order to allow dragging with that
609 | * element after all. Therefore, by combining dnd-nodrag and dnd-handle you can allow
610 | * dnd-draggable elements to only be dragged via specific "handle" elements. Note that Internet
611 | * Explorer will show the handle element as drag image instead of the dnd-draggable element. You
612 | * can work around this by styling the handle element differently when it is being dragged. Use
613 | * the CSS selector .dndDragging:not(.dndDraggingSource) [dnd-handle] for that.
614 | */
615 | dndLists.directive('dndHandle', function() {
616 | return function(scope, element, attr) {
617 | element.attr("draggable", "true");
618 |
619 | element.on('dragstart dragend', function(event) {
620 | event = event.originalEvent || event;
621 | event._dndHandle = true;
622 | });
623 | };
624 | });
625 |
626 | /**
627 | * Filters an array of drop effects using a HTML5 effectAllowed string.
628 | */
629 | function filterEffects(effects, effectAllowed) {
630 | if (effectAllowed == 'all') return effects;
631 | return effects.filter(function(effect) {
632 | return effectAllowed.toLowerCase().indexOf(effect) != -1;
633 | });
634 | }
635 |
636 | /**
637 | * For some features we need to maintain global state. This is done here, with these fields:
638 | * - callback: A callback function set at dragstart that is passed to internal dropzone handlers.
639 | * - dropEffect: Set in dragstart to "none" and to the actual value in the drop handler. We don't
640 | * rely on the dropEffect passed by the browser, since there are various bugs in Chrome and
641 | * Safari, and Internet Explorer defaults to copy if effectAllowed is copyMove.
642 | * - effectAllowed: Set in dragstart based on dnd-effect-allowed. This is needed for IE because
643 | * setting effectAllowed on dataTransfer might result in an undesired cursor.
644 | * - isDragging: True between dragstart and dragend. Falsy for drops from external sources.
645 | * - itemType: The item type of the dragged element set via dnd-type. This is needed because IE
646 | * and Edge don't support custom mime types that we can use to transfer this information.
647 | */
648 | var dndState = {};
649 |
650 | })(angular.module('dndLists', []));
651 |
--------------------------------------------------------------------------------