kms 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/kms/application/controllers/pages_controller.coffee.erb +1 -2
  3. data/app/assets/javascripts/templates/assets/index.html.slim +4 -12
  4. data/app/assets/javascripts/templates/help/scopes.html.slim +74 -0
  5. data/app/assets/javascripts/templates/pages/index.html.slim +4 -3
  6. data/app/assets/javascripts/templates/snippets/index.html.slim +4 -3
  7. data/app/assets/javascripts/templates/templates/index.html.slim +4 -3
  8. data/app/assets/javascripts/templates/users/index.html.slim +3 -2
  9. data/app/assets/stylesheets/kms/custom.css.scss +3 -0
  10. data/app/controllers/kms/pages_controller.rb +2 -2
  11. data/app/models/ability.rb +0 -1
  12. data/app/models/concerns/kms/positioned.rb +18 -0
  13. data/app/models/kms/page.rb +2 -12
  14. data/app/services/kms/functions_registry.rb +11 -0
  15. data/app/uploaders/kms/asset_uploader.rb +1 -1
  16. data/config/initializers/help.rb +1 -1
  17. data/config/initializers/liquor.rb +0 -47
  18. data/config/locales/en.yml +22 -0
  19. data/config/locales/ru.yml +22 -0
  20. data/lib/generators/kms/install/install_generator.rb +2 -0
  21. data/lib/generators/kms/install/templates/carrierwave.rb +14 -0
  22. data/lib/kms/dependencies.rb +2 -0
  23. data/lib/kms/engine.rb +1 -1
  24. data/lib/kms/version.rb +1 -1
  25. data/spec/controllers/kms/snippets_controller_spec.rb +6 -6
  26. data/spec/internal/log/test.log +2439 -0
  27. data/spec/services/kms/functions_registry_spec.rb +14 -0
  28. data/spec/spec_helper.rb +1 -1
  29. data/vendor/assets/bower.json +1 -1
  30. data/vendor/assets/bower_components/angular-ui-tree/CHANGELOG.md +164 -0
  31. data/vendor/assets/bower_components/angular-ui-tree/CONTRIBUTING.md +39 -0
  32. data/vendor/assets/bower_components/angular-ui-tree/README.md +145 -37
  33. data/vendor/assets/bower_components/angular-ui-tree/bower.json +28 -18
  34. data/vendor/assets/bower_components/angular-ui-tree/{source → dist}/angular-ui-tree.css +18 -16
  35. data/vendor/assets/bower_components/angular-ui-tree/dist/angular-ui-tree.js +1408 -822
  36. data/vendor/assets/bower_components/angular-ui-tree/dist/angular-ui-tree.min.css +1 -2
  37. data/vendor/assets/bower_components/angular-ui-tree/dist/angular-ui-tree.min.js +3 -3
  38. data/vendor/assets/bower_components/angular-ui-tree/e2e/basic-example/basic-example.js +81 -0
  39. data/vendor/assets/bower_components/angular-ui-tree/e2e/basic-example/page.js +42 -0
  40. data/vendor/assets/bower_components/angular-ui-tree/e2e/table-example/page.js +31 -0
  41. data/vendor/assets/bower_components/angular-ui-tree/e2e/table-example/table-example.js +34 -0
  42. data/vendor/assets/bower_components/angular-ui-tree/index.js +2 -0
  43. data/vendor/assets/bower_components/angular-ui-tree/protractor.conf.js +19 -0
  44. data/vendor/assets/bower_components/angular/angular.js +4880 -2111
  45. data/vendor/assets/bower_components/angular/angular.min.js +320 -297
  46. data/vendor/assets/bower_components/angular/angular.min.js.gzip +0 -0
  47. data/vendor/assets/bower_components/angular/angular.min.js.map +3 -3
  48. data/vendor/assets/bower_components/angular/bower.json +1 -1
  49. data/vendor/assets/bower_components/angular/package.json +1 -1
  50. metadata +32 -40
  51. data/vendor/assets/bower_components/angular-ui-tree/Gruntfile.js +0 -229
  52. data/vendor/assets/bower_components/angular-ui-tree/build/compiler.jar +0 -0
  53. data/vendor/assets/bower_components/angular-ui-tree/demo/css/demo-horizontal.css +0 -47
  54. data/vendor/assets/bower_components/angular-ui-tree/demo/css/demo.css +0 -31
  55. data/vendor/assets/bower_components/angular-ui-tree/demo/css/tree.css +0 -25
  56. data/vendor/assets/bower_components/angular-ui-tree/demo/dist/angular-ui-tree.js +0 -1243
  57. data/vendor/assets/bower_components/angular-ui-tree/demo/dist/angular-ui-tree.min.css +0 -2
  58. data/vendor/assets/bower_components/angular-ui-tree/demo/dist/angular-ui-tree.min.js +0 -6
  59. data/vendor/assets/bower_components/angular-ui-tree/demo/filter.html +0 -64
  60. data/vendor/assets/bower_components/angular-ui-tree/demo/groups.html +0 -100
  61. data/vendor/assets/bower_components/angular-ui-tree/demo/index.html +0 -101
  62. data/vendor/assets/bower_components/angular-ui-tree/demo/js/demo.js +0 -63
  63. data/vendor/assets/bower_components/angular-ui-tree/demo/js/filter.js +0 -91
  64. data/vendor/assets/bower_components/angular-ui-tree/demo/js/groups.js +0 -143
  65. data/vendor/assets/bower_components/angular-ui-tree/demo/js/tree.js +0 -102
  66. data/vendor/assets/bower_components/angular-ui-tree/demo/js/trees.js +0 -60
  67. data/vendor/assets/bower_components/angular-ui-tree/demo/test.html +0 -60
  68. data/vendor/assets/bower_components/angular-ui-tree/demo/tree-horizontal.html +0 -66
  69. data/vendor/assets/bower_components/angular-ui-tree/demo/tree.html +0 -66
  70. data/vendor/assets/bower_components/angular-ui-tree/demo/trees.html +0 -92
  71. data/vendor/assets/bower_components/angular-ui-tree/guide/00_usage.ngdoc +0 -78
  72. data/vendor/assets/bower_components/angular-ui-tree/guide/01_development_setup.ngdoc +0 -58
  73. data/vendor/assets/bower_components/angular-ui-tree/guide/index.ngdoc +0 -33
  74. data/vendor/assets/bower_components/angular-ui-tree/karma.conf.js +0 -49
  75. data/vendor/assets/bower_components/angular-ui-tree/package.json +0 -40
  76. data/vendor/assets/bower_components/angular-ui-tree/source/angular-ui-tree.scss +0 -63
  77. data/vendor/assets/bower_components/angular-ui-tree/source/controllers/handleCtrl.js +0 -16
  78. data/vendor/assets/bower_components/angular-ui-tree/source/controllers/nodeCtrl.js +0 -141
  79. data/vendor/assets/bower_components/angular-ui-tree/source/controllers/nodesCtrl.js +0 -100
  80. data/vendor/assets/bower_components/angular-ui-tree/source/controllers/treeCtrl.js +0 -63
  81. data/vendor/assets/bower_components/angular-ui-tree/source/directives/uiTree.js +0 -115
  82. data/vendor/assets/bower_components/angular-ui-tree/source/directives/uiTreeHandle.js +0 -27
  83. data/vendor/assets/bower_components/angular-ui-tree/source/directives/uiTreeNode.js +0 -427
  84. data/vendor/assets/bower_components/angular-ui-tree/source/directives/uiTreeNodes.js +0 -59
  85. data/vendor/assets/bower_components/angular-ui-tree/source/main.js +0 -23
  86. data/vendor/assets/bower_components/angular-ui-tree/source/services/helper.js +0 -265
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "angular-ui-tree",
3
- "version": "2.1.5",
4
- "homepage": "https://github.com/JimLiu/angular-ui-tree",
3
+ "version": "2.22.5",
4
+ "homepage": "https://github.com/angular-ui-tree/angular-ui-tree",
5
5
  "authors": [
6
6
  "Jim Liu <https://github.com/JimLiu>",
7
- "Voles <https://github.com/Voles>"
7
+ "Voles <https://github.com/Voles>",
8
+ "Stefan Mohr <https://github.com/StefanCodes>"
8
9
  ],
9
- "description": "Tree directive for the angular.js",
10
+ "description": "Tree directive for AngularJS",
10
11
  "main": [
11
12
  "dist/angular-ui-tree.js",
12
- "dist/angular-ui-tree.min.css"
13
+ "dist/angular-ui-tree.css"
13
14
  ],
14
15
  "keywords": [
15
16
  "angular",
@@ -22,22 +23,31 @@
22
23
  "node"
23
24
  ],
24
25
  "dependencies": {
25
- "angular": ">= 1.2.0",
26
- "es5-shim": "2.3.0"
26
+ "angular": "1.2.x || 1.3.x || 1.4.x || 1.5.x"
27
27
  },
28
28
  "devDependencies": {
29
- "angular": ">= 1.2.0",
30
- "bootstrap": "~3.1.0",
31
- "jquery": ">=1.10.x",
32
- "jasmine-jquery": "~1.0.0",
33
- "angular-mocks": "~1.2.11",
34
- "firebase": "~1.0.11",
35
- "angularfire": "~0.7.1"
29
+ "angular-bootstrap": "~0.13.4",
30
+ "angular-mocks": "1.2.x || 1.3.x || 1.4.x || 1.5.x",
31
+ "angular-route": "1.2.x || 1.3.x || 1.4.x || 1.5.x",
32
+ "angularfire": "2.0.x",
33
+ "bootstrap-css": "~3.2.0",
34
+ "jasmine-jquery": "2.1.1",
35
+ "jquery": "~2.1.1"
36
36
  },
37
37
  "ignore": [
38
+ "examples",
39
+ "demo",
40
+ "build",
41
+ "guide",
38
42
  "node_modules",
39
- "bower_components",
40
- "test",
41
- "tests"
42
- ]
43
+ "source",
44
+ "tasks",
45
+ "tests",
46
+ "gulpfile.js",
47
+ "karma.conf.js",
48
+ "package.json"
49
+ ],
50
+ "resolutions": {
51
+ "angular": "1.2.x || 1.3.x || 1.4.x || 1.5.x"
52
+ }
43
53
  }
@@ -1,5 +1,4 @@
1
1
  .angular-ui-tree {
2
-
3
2
  }
4
3
 
5
4
  .angular-ui-tree-empty {
@@ -11,26 +10,26 @@
11
10
  background-image: linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff), linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
12
11
  background-size: 60px 60px;
13
12
  background-position: 0 0, 30px 30px;
13
+ pointer-events: none;
14
14
  }
15
15
 
16
16
  .angular-ui-tree-nodes {
17
- display: block;
18
- position: relative;
19
- margin: 0px;
20
- padding: 0px;
21
- list-style: none;
17
+ position: relative;
18
+ margin: 0;
19
+ padding: 0;
20
+ list-style: none;
22
21
  }
23
22
 
24
23
  .angular-ui-tree-nodes .angular-ui-tree-nodes {
25
24
  padding-left: 20px;
26
25
  }
26
+
27
27
  .angular-ui-tree-node, .angular-ui-tree-placeholder {
28
- display: block;
29
- position: relative;
30
- margin: 0px;
31
- padding: 0px;
32
- min-height: 20px;
33
- line-height: 20px;
28
+ position: relative;
29
+ margin: 0;
30
+ padding: 0;
31
+ min-height: 20px;
32
+ line-height: 20px;
34
33
  }
35
34
 
36
35
  .angular-ui-tree-hidden {
@@ -38,7 +37,7 @@
38
37
  }
39
38
 
40
39
  .angular-ui-tree-placeholder {
41
- margin: 5px 0;
40
+ margin: 10px;
42
41
  padding: 0;
43
42
  min-height: 30px;
44
43
  }
@@ -54,10 +53,13 @@
54
53
  line-height: 20px;
55
54
  }
56
55
 
57
-
58
56
  .angular-ui-tree-drag {
59
57
  position: absolute;
60
58
  pointer-events: none;
61
59
  z-index: 999;
62
- opacity: .8;
63
- }
60
+ opacity: .8;
61
+ }
62
+
63
+ .angular-ui-tree-drag .tree-node-content {
64
+ margin-top: 0;
65
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @license Angular UI Tree v2.1.5
3
- * (c) 2010-2014. https://github.com/JimLiu/angular-ui-tree
2
+ * @license Angular UI Tree v2.22.5
3
+ * (c) 2010-2017. https://github.com/angular-ui-tree/angular-ui-tree
4
4
  * License: MIT
5
5
  */
6
6
  (function () {
@@ -14,10 +14,11 @@
14
14
  nodesClass: 'angular-ui-tree-nodes',
15
15
  nodeClass: 'angular-ui-tree-node',
16
16
  handleClass: 'angular-ui-tree-handle',
17
- placeHolderClass: 'angular-ui-tree-placeholder',
17
+ placeholderClass: 'angular-ui-tree-placeholder',
18
18
  dragClass: 'angular-ui-tree-drag',
19
19
  dragThreshold: 3,
20
- levelThreshold: 30
20
+ defaultCollapsed: false,
21
+ appendChildOnHover: true
21
22
  });
22
23
 
23
24
  })();
@@ -27,326 +28,166 @@
27
28
 
28
29
  angular.module('ui.tree')
29
30
 
30
- /**
31
- * @ngdoc service
32
- * @name ui.tree.service:$helper
33
- * @requires ng.$document
34
- * @requires ng.$window
35
- *
36
- * @description
37
- * angular-ui-tree.
38
- */
39
- .factory('$uiTreeHelper', ['$document', '$window',
40
- function ($document, $window) {
41
- return {
42
-
43
- /**
44
- * A hashtable used to storage data of nodes
45
- * @type {Object}
46
- */
47
- nodesData: {
48
- },
49
-
50
- setNodeAttribute: function(scope, attrName, val) {
51
- var data = this.nodesData[scope.$modelValue.$$hashKey];
52
- if (!data) {
53
- data = {};
54
- this.nodesData[scope.$modelValue.$$hashKey] = data;
55
- }
56
- data[attrName] = val;
57
- },
58
-
59
- getNodeAttribute: function(scope, attrName) {
60
- var data = this.nodesData[scope.$modelValue.$$hashKey];
61
- if (data) {
62
- return data[attrName];
63
- }
64
- return null;
65
- },
66
-
67
- /**
68
- * @ngdoc method
69
- * @methodOf ui.tree.service:$nodrag
70
- * @param {Object} targetElm angular element
71
- * @return {Bool} check if the node can be dragged.
72
- */
73
- nodrag: function (targetElm) {
74
- return (typeof targetElm.attr('data-nodrag')) != "undefined";
75
- },
76
-
77
- /**
78
- * get the event object for touchs
79
- * @param {[type]} e [description]
80
- * @return {[type]} [description]
81
- */
82
- eventObj: function(e) {
83
- var obj = e;
84
- if (e.targetTouches !== undefined) {
85
- obj = e.targetTouches.item(0);
86
- } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) {
87
- obj = e.originalEvent.targetTouches.item(0);
88
- }
89
- return obj;
90
- },
91
-
92
- dragInfo: function(node) {
93
- return {
94
- source: node,
95
- sourceInfo: {
96
- nodeScope: node,
97
- index: node.index(),
98
- nodesScope: node.$parentNodesScope
99
- },
100
- index: node.index(),
101
- siblings: node.siblings().slice(0),
102
- parent: node.$parentNodesScope,
103
-
104
- moveTo: function(parent, siblings, index) { // Move the node to a new position
105
- this.parent = parent;
106
- this.siblings = siblings.slice(0);
107
- var i = this.siblings.indexOf(this.source); // If source node is in the target nodes
108
- if (i > -1) {
109
- this.siblings.splice(i, 1);
110
- if (this.source.index() < index) {
111
- index--;
112
- }
113
- }
114
- this.siblings.splice(index, 0, this.source);
115
- this.index = index;
116
- },
117
-
118
- parentNode: function() {
119
- return this.parent.$nodeScope;
120
- },
121
-
122
- prev: function() {
123
- if (this.index > 0) {
124
- return this.siblings[this.index - 1];
125
- }
126
- return null;
127
- },
128
-
129
- next: function() {
130
- if (this.index < this.siblings.length - 1) {
131
- return this.siblings[this.index + 1];
132
- }
133
- return null;
134
- },
135
-
136
- isDirty: function() {
137
- return this.source.$parentNodesScope != this.parent ||
138
- this.source.index() != this.index;
139
- },
140
-
141
- eventArgs: function(elements, pos) {
142
- return {
143
- source: this.sourceInfo,
144
- dest: {
145
- index: this.index,
146
- nodesScope: this.parent
147
- },
148
- elements: elements,
149
- pos: pos
150
- };
151
- },
31
+ .controller('TreeHandleController', ['$scope', '$element',
32
+ function ($scope, $element) {
33
+ this.scope = $scope;
152
34
 
153
- apply: function() {
154
- var nodeData = this.source.$modelValue;
155
- this.source.remove();
156
- this.parent.insertNode(this.index, nodeData);
157
- }
158
- };
159
- },
35
+ $scope.$element = $element;
36
+ $scope.$nodeScope = null;
37
+ $scope.$type = 'uiTreeHandle';
160
38
 
161
- /**
162
- * @ngdoc method
163
- * @name hippo.theme#height
164
- * @methodOf ui.tree.service:$helper
165
- *
166
- * @description
167
- * Get the height of an element.
168
- *
169
- * @param {Object} element Angular element.
170
- * @returns {String} Height
171
- */
172
- height: function (element) {
173
- return element.prop('scrollHeight');
174
- },
39
+ }
40
+ ]);
41
+ })();
175
42
 
176
- /**
177
- * @ngdoc method
178
- * @name hippo.theme#width
179
- * @methodOf ui.tree.service:$helper
180
- *
181
- * @description
182
- * Get the width of an element.
183
- *
184
- * @param {Object} element Angular element.
185
- * @returns {String} Width
186
- */
187
- width: function (element) {
188
- return element.prop('scrollWidth');
189
- },
43
+ (function () {
44
+ 'use strict';
190
45
 
191
- /**
192
- * @ngdoc method
193
- * @name hippo.theme#offset
194
- * @methodOf ui.nestedSortable.service:$helper
195
- *
196
- * @description
197
- * Get the offset values of an element.
198
- *
199
- * @param {Object} element Angular element.
200
- * @returns {Object} Object with properties width, height, top and left
201
- */
202
- offset: function (element) {
203
- var boundingClientRect = element[0].getBoundingClientRect();
46
+ angular.module('ui.tree')
47
+ .controller('TreeNodeController', ['$scope', '$element',
48
+ function ($scope, $element) {
49
+ this.scope = $scope;
204
50
 
205
- return {
206
- width: element.prop('offsetWidth'),
207
- height: element.prop('offsetHeight'),
208
- top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
209
- left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
210
- };
211
- },
51
+ $scope.$element = $element;
52
+ $scope.$modelValue = null; // Model value for node;
53
+ $scope.$parentNodeScope = null; // uiTreeNode Scope of parent node;
54
+ $scope.$childNodesScope = null; // uiTreeNodes Scope of child nodes.
55
+ $scope.$parentNodesScope = null; // uiTreeNodes Scope of parent nodes.
56
+ $scope.$treeScope = null; // uiTree scope
57
+ $scope.$handleScope = null; // it's handle scope
58
+ $scope.$type = 'uiTreeNode';
59
+ $scope.$$allowNodeDrop = false;
60
+ $scope.collapsed = false;
61
+ $scope.expandOnHover = false;
212
62
 
213
- /**
214
- * @ngdoc method
215
- * @name hippo.theme#positionStarted
216
- * @methodOf ui.tree.service:$helper
217
- *
218
- * @description
219
- * Get the start position of the target element according to the provided event properties.
220
- *
221
- * @param {Object} e Event
222
- * @param {Object} target Target element
223
- * @returns {Object} Object with properties offsetX, offsetY, startX, startY, nowX and dirX.
224
- */
225
- positionStarted: function (e, target) {
226
- var pos = {};
227
- pos.offsetX = e.pageX - this.offset(target).left;
228
- pos.offsetY = e.pageY - this.offset(target).top;
229
- pos.startX = pos.lastX = e.pageX;
230
- pos.startY = pos.lastY = e.pageY;
231
- pos.nowX = pos.nowY = pos.distX = pos.distY = pos.dirAx = 0;
232
- pos.dirX = pos.dirY = pos.lastDirX = pos.lastDirY = pos.distAxX = pos.distAxY = 0;
233
- return pos;
234
- },
63
+ //Called by uiTreeNode Directive on load.
64
+ $scope.init = function (controllersArr) {
65
+ var treeNodesCtrl = controllersArr[0];
66
+ $scope.$treeScope = controllersArr[1] ? controllersArr[1].scope : null;
235
67
 
236
- positionMoved: function (e, pos, firstMoving) {
237
- // mouse position last events
238
- pos.lastX = pos.nowX;
239
- pos.lastY = pos.nowY;
68
+ //Find the scope of it's parent node.
69
+ $scope.$parentNodeScope = treeNodesCtrl.scope.$nodeScope;
240
70
 
241
- // mouse position this events
242
- pos.nowX = e.pageX;
243
- pos.nowY = e.pageY;
71
+ //modelValue for current node.
72
+ $scope.$modelValue = treeNodesCtrl.scope.$modelValue[$scope.$index];
73
+ $scope.$parentNodesScope = treeNodesCtrl.scope;
244
74
 
245
- // distance mouse moved between events
246
- pos.distX = pos.nowX - pos.lastX;
247
- pos.distY = pos.nowY - pos.lastY;
75
+ //Init sub nodes.
76
+ treeNodesCtrl.scope.initSubNode($scope);
248
77
 
249
- // direction mouse was moving
250
- pos.lastDirX = pos.dirX;
251
- pos.lastDirY = pos.dirY;
78
+ $element.on('$destroy', function () {
252
79
 
253
- // direction mouse is now moving (on both axis)
254
- pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;
255
- pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;
80
+ //Destroy sub nodes.
81
+ treeNodesCtrl.scope.destroySubNode($scope);
82
+ });
83
+ };
256
84
 
257
- // axis mouse is now moving on
258
- var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;
85
+ //Return the index of child node in parent node (nodesScope).
86
+ $scope.index = function () {
87
+ return $scope.$parentNodesScope.$modelValue.indexOf($scope.$modelValue);
88
+ };
259
89
 
260
- // do nothing on first move
261
- if (firstMoving) {
262
- pos.dirAx = newAx;
263
- pos.moving = true;
264
- return;
265
- }
90
+ $scope.dragEnabled = function () {
91
+ return !($scope.$treeScope && !$scope.$treeScope.dragEnabled);
92
+ };
266
93
 
267
- // calc distance moved on this axis (and direction)
268
- if (pos.dirAx !== newAx) {
269
- pos.distAxX = 0;
270
- pos.distAxY = 0;
271
- } else {
272
- pos.distAxX += Math.abs(pos.distX);
273
- if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {
274
- pos.distAxX = 0;
275
- }
94
+ $scope.isSibling = function (targetNode) {
95
+ return $scope.$parentNodesScope == targetNode.$parentNodesScope;
96
+ };
276
97
 
277
- pos.distAxY += Math.abs(pos.distY);
278
- if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {
279
- pos.distAxY = 0;
280
- }
281
- }
98
+ $scope.isChild = function (targetNode) {
99
+ var nodes = $scope.childNodes();
100
+ return nodes && nodes.indexOf(targetNode) > -1;
101
+ };
282
102
 
283
- pos.dirAx = newAx;
103
+ //TODO(jcarter): This method is on uiTreeHelper already.
104
+ $scope.prev = function () {
105
+ var index = $scope.index();
106
+ if (index > 0) {
107
+ return $scope.siblings()[index - 1];
284
108
  }
109
+ return null;
285
110
  };
286
- }
287
- ]);
288
-
289
- })();
290
- (function () {
291
- 'use strict';
292
111
 
293
- angular.module('ui.tree')
112
+ //Calls childNodes on parent.
113
+ $scope.siblings = function () {
114
+ return $scope.$parentNodesScope.childNodes();
115
+ };
294
116
 
295
- .controller('TreeController', ['$scope', '$element', '$attrs', 'treeConfig',
296
- function ($scope, $element, $attrs, treeConfig) {
297
- this.scope = $scope;
117
+ $scope.childNodesCount = function () {
118
+ return $scope.childNodes() ? $scope.childNodes().length : 0;
119
+ };
298
120
 
299
- $scope.$element = $element;
300
- $scope.$nodesScope = null; // root nodes
301
- $scope.$type = 'uiTree';
302
- $scope.$emptyElm = null;
303
- $scope.$callbacks = null;
121
+ $scope.hasChild = function () {
122
+ return $scope.childNodesCount() > 0;
123
+ };
304
124
 
305
- $scope.dragEnabled = true;
306
- $scope.emptyPlaceHolderEnabled = true;
307
- $scope.maxDepth = 0;
308
- $scope.dragDelay = 0;
125
+ $scope.childNodes = function () {
126
+ return $scope.$childNodesScope && $scope.$childNodesScope.$modelValue ?
127
+ $scope.$childNodesScope.childNodes() :
128
+ null;
129
+ };
309
130
 
310
- // Check if it's a empty tree
311
- $scope.isEmpty = function() {
312
- return ($scope.$nodesScope && $scope.$nodesScope.$modelValue
313
- && $scope.$nodesScope.$modelValue.length === 0);
131
+ $scope.accept = function (sourceNode, destIndex) {
132
+ return $scope.$childNodesScope &&
133
+ $scope.$childNodesScope.$modelValue &&
134
+ $scope.$childNodesScope.accept(sourceNode, destIndex);
314
135
  };
315
136
 
316
- // add placeholder to empty tree
317
- $scope.place = function(placeElm) {
318
- $scope.$nodesScope.$element.append(placeElm);
319
- $scope.$emptyElm.remove();
137
+ $scope.remove = function () {
138
+ return $scope.$parentNodesScope.removeNode($scope);
320
139
  };
321
140
 
322
- $scope.resetEmptyElement = function() {
323
- if ($scope.$nodesScope.$modelValue.length === 0 &&
324
- $scope.emptyPlaceHolderEnabled) {
325
- $element.append($scope.$emptyElm);
326
- } else {
327
- $scope.$emptyElm.remove();
328
- }
141
+ $scope.toggle = function () {
142
+ $scope.collapsed = !$scope.collapsed;
143
+ $scope.$treeScope.$callbacks.toggle($scope.collapsed, $scope);
329
144
  };
330
145
 
331
- var collapseOrExpand = function(scope, collapsed) {
332
- var nodes = scope.childNodes();
333
- for (var i = 0; i < nodes.length; i++) {
334
- collapsed ? nodes[i].collapse() : nodes[i].expand();
335
- var subScope = nodes[i].$childNodesScope;
336
- if (subScope) {
337
- collapseOrExpand(subScope, collapsed);
338
- }
339
- }
146
+ $scope.collapse = function () {
147
+ $scope.collapsed = true;
340
148
  };
341
149
 
342
- $scope.collapseAll = function() {
343
- collapseOrExpand($scope.$nodesScope, true);
150
+ $scope.expand = function () {
151
+ $scope.collapsed = false;
344
152
  };
345
153
 
346
- $scope.expandAll = function() {
347
- collapseOrExpand($scope.$nodesScope, false);
154
+ $scope.depth = function () {
155
+ var parentNode = $scope.$parentNodeScope;
156
+ if (parentNode) {
157
+ return parentNode.depth() + 1;
158
+ }
159
+ return 1;
348
160
  };
349
161
 
162
+ /**
163
+ * Returns the depth of the deepest subtree under this node
164
+ * @param scope a TreeNodesController scope object
165
+ * @returns Depth of all nodes *beneath* this node. If scope belongs to a leaf node, the
166
+ * result is 0 (it has no subtree).
167
+ */
168
+ function countSubTreeDepth(scope) {
169
+ if (!scope) {
170
+ return 0;
171
+ }
172
+ var thisLevelDepth = 0,
173
+ childNodes = scope.childNodes(),
174
+ childNode,
175
+ childDepth,
176
+ i;
177
+ if (!childNodes || childNodes.length === 0) {
178
+ return 0;
179
+ }
180
+ for (i = childNodes.length - 1; i >= 0 ; i--) {
181
+ childNode = childNodes[i],
182
+ childDepth = 1 + countSubTreeDepth(childNode);
183
+ thisLevelDepth = Math.max(thisLevelDepth, childDepth);
184
+ }
185
+ return thisLevelDepth;
186
+ }
187
+
188
+ $scope.maxSubDepth = function () {
189
+ return $scope.$childNodesScope ? countSubTreeDepth($scope.$childNodesScope) : 0;
190
+ };
350
191
  }
351
192
  ]);
352
193
  })();
@@ -356,8 +197,8 @@
356
197
 
357
198
  angular.module('ui.tree')
358
199
 
359
- .controller('TreeNodesController', ['$scope', '$element', 'treeConfig',
360
- function ($scope, $element, treeConfig) {
200
+ .controller('TreeNodesController', ['$scope', '$element',
201
+ function ($scope, $element) {
361
202
  this.scope = $scope;
362
203
 
363
204
  $scope.$element = $element;
@@ -367,37 +208,44 @@
367
208
  $scope.$type = 'uiTreeNodes';
368
209
  $scope.$nodesMap = {};
369
210
 
370
- $scope.nodrop = false;
211
+ $scope.nodropEnabled = false;
371
212
  $scope.maxDepth = 0;
213
+ $scope.cloneEnabled = false;
372
214
 
373
- $scope.initSubNode = function(subNode) {
215
+ $scope.initSubNode = function (subNode) {
216
+ if (!subNode.$modelValue) {
217
+ return null;
218
+ }
374
219
  $scope.$nodesMap[subNode.$modelValue.$$hashKey] = subNode;
375
220
  };
376
221
 
377
- $scope.destroySubNode = function(subNode) {
222
+ $scope.destroySubNode = function (subNode) {
223
+ if (!subNode.$modelValue) {
224
+ return null;
225
+ }
378
226
  $scope.$nodesMap[subNode.$modelValue.$$hashKey] = null;
379
227
  };
380
228
 
381
- $scope.accept = function(sourceNode, destIndex) {
229
+ $scope.accept = function (sourceNode, destIndex) {
382
230
  return $scope.$treeScope.$callbacks.accept(sourceNode, $scope, destIndex);
383
231
  };
384
232
 
385
- $scope.beforeDrag = function(sourceNode) {
233
+ $scope.beforeDrag = function (sourceNode) {
386
234
  return $scope.$treeScope.$callbacks.beforeDrag(sourceNode);
387
235
  };
388
236
 
389
- $scope.isParent = function(node) {
237
+ $scope.isParent = function (node) {
390
238
  return node.$parentNodesScope == $scope;
391
239
  };
392
240
 
393
- $scope.hasChild = function() {
241
+ $scope.hasChild = function () {
394
242
  return $scope.$modelValue.length > 0;
395
243
  };
396
244
 
397
- $scope.safeApply = function(fn) {
245
+ $scope.safeApply = function (fn) {
398
246
  var phase = this.$root.$$phase;
399
- if(phase == '$apply' || phase == '$digest') {
400
- if(fn && (typeof(fn) === 'function')) {
247
+ if (phase == '$apply' || phase == '$digest') {
248
+ if (fn && (typeof (fn) === 'function')) {
401
249
  fn();
402
250
  }
403
251
  } else {
@@ -405,34 +253,36 @@
405
253
  }
406
254
  };
407
255
 
408
- $scope.removeNode = function(node) {
256
+ //Called in apply method of UiTreeHelper.dragInfo.
257
+ $scope.removeNode = function (node) {
409
258
  var index = $scope.$modelValue.indexOf(node.$modelValue);
410
259
  if (index > -1) {
411
- $scope.safeApply(function() {
260
+ $scope.safeApply(function () {
412
261
  $scope.$modelValue.splice(index, 1)[0];
413
262
  });
414
- return node;
263
+ return $scope.$treeScope.$callbacks.removed(node);
415
264
  }
416
265
  return null;
417
266
  };
418
267
 
419
- $scope.insertNode = function(index, nodeData) {
420
- $scope.safeApply(function() {
268
+ //Called in apply method of UiTreeHelper.dragInfo.
269
+ $scope.insertNode = function (index, nodeData) {
270
+ $scope.safeApply(function () {
421
271
  $scope.$modelValue.splice(index, 0, nodeData);
422
272
  });
423
273
  };
424
274
 
425
- $scope.childNodes = function() {
426
- var nodes = [];
275
+ $scope.childNodes = function () {
276
+ var i, nodes = [];
427
277
  if ($scope.$modelValue) {
428
- for (var i = 0; i < $scope.$modelValue.length; i++) {
278
+ for (i = 0; i < $scope.$modelValue.length; i++) {
429
279
  nodes.push($scope.$nodesMap[$scope.$modelValue[i].$$hashKey]);
430
280
  }
431
281
  }
432
282
  return nodes;
433
283
  };
434
284
 
435
- $scope.depth = function() {
285
+ $scope.depth = function () {
436
286
  if ($scope.$nodeScope) {
437
287
  return $scope.$nodeScope.depth();
438
288
  }
@@ -440,7 +290,7 @@
440
290
  };
441
291
 
442
292
  // check if depth limit has reached
443
- $scope.outOfDepth = function(sourceNode) {
293
+ $scope.outOfDepth = function (sourceNode) {
444
294
  var maxDepth = $scope.maxDepth || $scope.$treeScope.maxDepth;
445
295
  if (maxDepth > 0) {
446
296
  return $scope.depth() + sourceNode.maxSubDepth() + 1 > maxDepth;
@@ -451,339 +301,290 @@
451
301
  }
452
302
  ]);
453
303
  })();
304
+
454
305
  (function () {
455
306
  'use strict';
456
307
 
457
308
  angular.module('ui.tree')
458
309
 
459
- .controller('TreeNodeController', ['$scope', '$element', '$attrs', 'treeConfig',
460
- function ($scope, $element, $attrs, treeConfig) {
310
+ .controller('TreeController', ['$scope', '$element',
311
+ function ($scope, $element) {
461
312
  this.scope = $scope;
462
313
 
463
314
  $scope.$element = $element;
464
- $scope.$modelValue = null; // Model value for node;
465
- $scope.$parentNodeScope = null; // uiTreeNode Scope of parent node;
466
- $scope.$childNodesScope = null; // uiTreeNodes Scope of child nodes.
467
- $scope.$parentNodesScope = null; // uiTreeNodes Scope of parent nodes.
468
- $scope.$treeScope = null; // uiTree scope
469
- $scope.$handleScope = null; // it's handle scope
470
- $scope.$type = 'uiTreeNode';
471
- $scope.$$apply = false; //
315
+ $scope.$nodesScope = null; // root nodes
316
+ $scope.$type = 'uiTree';
317
+ $scope.$emptyElm = null;
318
+ $scope.$callbacks = null;
472
319
 
473
- $scope.collapsed = false;
320
+ $scope.dragEnabled = true;
321
+ $scope.emptyPlaceholderEnabled = true;
322
+ $scope.maxDepth = 0;
323
+ $scope.dragDelay = 0;
324
+ $scope.cloneEnabled = false;
325
+ $scope.nodropEnabled = false;
474
326
 
475
- $scope.init = function(controllersArr) {
476
- var treeNodesCtrl = controllersArr[0];
477
- $scope.$treeScope = controllersArr[1] ? controllersArr[1].scope : null;
478
-
479
- // find the scope of it's parent node
480
- $scope.$parentNodeScope = treeNodesCtrl.scope.$nodeScope;
481
- // modelValue for current node
482
- $scope.$modelValue = treeNodesCtrl.scope.$modelValue[$scope.$index];
483
- $scope.$parentNodesScope = treeNodesCtrl.scope;
484
- treeNodesCtrl.scope.initSubNode($scope); // init sub nodes
485
-
486
- $element.on('$destroy', function() {
487
- treeNodesCtrl.scope.destroySubNode($scope); // destroy sub nodes
488
- });
489
- };
490
-
491
- $scope.index = function() {
492
- return $scope.$parentNodesScope.$modelValue.indexOf($scope.$modelValue);
493
- };
494
-
495
- $scope.dragEnabled = function() {
496
- return !($scope.$treeScope && !$scope.$treeScope.dragEnabled);
497
- };
498
-
499
- $scope.isSibling = function(targetNode) {
500
- return $scope.$parentNodesScope == targetNode.$parentNodesScope;
327
+ // Check if it's a empty tree
328
+ $scope.isEmpty = function () {
329
+ return ($scope.$nodesScope && $scope.$nodesScope.$modelValue
330
+ && $scope.$nodesScope.$modelValue.length === 0);
501
331
  };
502
332
 
503
- $scope.isChild = function(targetNode) {
504
- var nodes = $scope.childNodes();
505
- return nodes && nodes.indexOf(targetNode) > -1;
333
+ // add placeholder to empty tree
334
+ $scope.place = function (placeElm) {
335
+ $scope.$nodesScope.$element.append(placeElm);
336
+ $scope.$emptyElm.remove();
506
337
  };
507
338
 
508
- $scope.prev = function() {
509
- var index = $scope.index();
510
- if (index > 0) {
511
- return $scope.siblings()[index - 1];
339
+ this.resetEmptyElement = function () {
340
+ if ((!$scope.$nodesScope.$modelValue || $scope.$nodesScope.$modelValue.length === 0) &&
341
+ $scope.emptyPlaceholderEnabled) {
342
+ $element.append($scope.$emptyElm);
343
+ } else {
344
+ $scope.$emptyElm.remove();
512
345
  }
513
- return null;
514
- };
515
-
516
- $scope.siblings = function() {
517
- return $scope.$parentNodesScope.childNodes();
518
- };
519
-
520
- $scope.childNodesCount = function() {
521
- return $scope.childNodes() ? $scope.childNodes().length : 0;
522
- };
523
-
524
- $scope.hasChild = function() {
525
- return $scope.childNodesCount() > 0;
526
346
  };
527
347
 
528
- $scope.childNodes = function() {
529
- return $scope.$childNodesScope && $scope.$childNodesScope.$modelValue ?
530
- $scope.$childNodesScope.childNodes() :
531
- null;
532
- };
533
-
534
- $scope.accept = function(sourceNode, destIndex) {
535
- return $scope.$childNodesScope &&
536
- $scope.$childNodesScope.$modelValue &&
537
- $scope.$childNodesScope.accept(sourceNode, destIndex);
538
- };
348
+ $scope.resetEmptyElement = this.resetEmptyElement;
349
+ }
350
+ ]);
351
+ })();
539
352
 
540
- $scope.removeNode = function(){
541
- var node = $scope.remove();
542
- $scope.$callbacks.removed(node);
543
- return node;
544
- };
353
+ (function () {
354
+ 'use strict';
545
355
 
546
- $scope.remove = function() {
547
- return $scope.$parentNodesScope.removeNode($scope);
548
- };
356
+ angular.module('ui.tree')
357
+ .directive('uiTree', ['treeConfig', '$window',
358
+ function (treeConfig, $window) {
359
+ return {
360
+ restrict: 'A',
361
+ scope: true,
362
+ controller: 'TreeController',
363
+ link: function (scope, element, attrs, ctrl) {
364
+ var callbacks = {
365
+ accept: null,
366
+ beforeDrag: null
367
+ },
368
+ config = {},
369
+ tdElm,
370
+ $trElm,
371
+ emptyElmColspan;
372
+
373
+ //Adding configured class to uiTree.
374
+ angular.extend(config, treeConfig);
549
375
 
550
- $scope.toggle = function() {
551
- $scope.collapsed = !$scope.collapsed;
552
- };
376
+ if (config.treeClass) {
377
+ element.addClass(config.treeClass);
378
+ }
553
379
 
554
- $scope.collapse = function() {
555
- $scope.collapsed = true;
556
- };
380
+ //Determining if uiTree is on a table.
381
+ if (element.prop('tagName').toLowerCase() === 'table') {
382
+ scope.$emptyElm = angular.element($window.document.createElement('tr'));
383
+ $trElm = element.find('tr');
384
+
385
+ //If we can find a tr, then we can use its td children as the empty element colspan.
386
+ if ($trElm.length > 0) {
387
+ emptyElmColspan = angular.element($trElm).children().length;
388
+ } else {
389
+
390
+ //If not, by setting a huge colspan we make sure it takes full width.
391
+ //TODO(jcarter): Check for negative side effects.
392
+ emptyElmColspan = 1000000;
393
+ }
394
+ tdElm = angular.element($window.document.createElement('td'))
395
+ .attr('colspan', emptyElmColspan);
396
+ scope.$emptyElm.append(tdElm);
397
+ } else {
398
+ scope.$emptyElm = angular.element($window.document.createElement('div'));
399
+ }
557
400
 
558
- $scope.expand = function() {
559
- $scope.collapsed = false;
560
- };
401
+ if (config.emptyTreeClass) {
402
+ scope.$emptyElm.addClass(config.emptyTreeClass);
403
+ }
561
404
 
562
- $scope.depth = function() {
563
- var parentNode = $scope.$parentNodeScope;
564
- if (parentNode) {
565
- return parentNode.depth() + 1;
566
- }
567
- return 1;
568
- };
405
+ scope.$watch('$nodesScope.$modelValue.length', function (val) {
406
+ if (!angular.isNumber(val)) {
407
+ return;
408
+ }
569
409
 
570
- var subDepth = 0;
571
- var countSubDepth = function(scope) {
572
- var count = 0;
573
- var nodes = scope.childNodes();
574
- for (var i = 0; i < nodes.length; i++) {
575
- var childNodes = nodes[i].$childNodesScope;
576
- if (childNodes) {
577
- count = 1;
578
- countSubDepth(childNodes);
579
- }
580
- }
581
- subDepth += count;
582
- };
410
+ ctrl.resetEmptyElement();
411
+ }, true);
583
412
 
584
- $scope.maxSubDepth = function() {
585
- subDepth = 0;
586
- if ($scope.$childNodesScope) {
587
- countSubDepth($scope.$childNodesScope);
588
- }
589
- return subDepth;
590
- };
413
+ scope.$watch(attrs.dragEnabled, function (val) {
414
+ if ((typeof val) == 'boolean') {
415
+ scope.dragEnabled = val;
416
+ }
417
+ });
591
418
 
592
- }
593
- ]);
594
- })();
419
+ scope.$watch(attrs.emptyPlaceholderEnabled, function (val) {
420
+ if ((typeof val) == 'boolean') {
421
+ scope.emptyPlaceholderEnabled = val;
422
+ ctrl.resetEmptyElement();
423
+ }
424
+ });
595
425
 
596
- (function () {
597
- 'use strict';
426
+ scope.$watch(attrs.nodropEnabled, function (val) {
427
+ if ((typeof val) == 'boolean') {
428
+ scope.nodropEnabled = val;
429
+ }
430
+ });
598
431
 
599
- angular.module('ui.tree')
432
+ scope.$watch(attrs.cloneEnabled, function (val) {
433
+ if ((typeof val) == 'boolean') {
434
+ scope.cloneEnabled = val;
435
+ }
436
+ });
600
437
 
601
- .controller('TreeHandleController', ['$scope', '$element', '$attrs', 'treeConfig',
602
- function ($scope, $element, $attrs, treeConfig) {
603
- this.scope = $scope;
438
+ scope.$watch(attrs.maxDepth, function (val) {
439
+ if ((typeof val) == 'number') {
440
+ scope.maxDepth = val;
441
+ }
442
+ });
604
443
 
605
- $scope.$element = $element;
606
- $scope.$nodeScope = null;
607
- $scope.$type = 'uiTreeHandle';
444
+ scope.$watch(attrs.dragDelay, function (val) {
445
+ if ((typeof val) == 'number') {
446
+ scope.dragDelay = val;
447
+ }
448
+ });
608
449
 
609
- }
610
- ]);
611
- })();
450
+ /**
451
+ * Callback checks if the destination node can accept the dragged node.
452
+ * By default, ui-tree will check that 'data-nodrop-enabled' is not set for the
453
+ * destination ui-tree-nodes, and that the 'max-depth' attribute will not be exceeded
454
+ * if it is set on the ui-tree or ui-tree-nodes.
455
+ * This callback can be overridden, but callers must manually enforce nodrop and max-depth
456
+ * themselves if they need those to be enforced.
457
+ * @param sourceNodeScope Scope of the ui-tree-node being dragged
458
+ * @param destNodesScope Scope of the ui-tree-nodes where the node is hovering
459
+ * @param destIndex Index in the destination nodes array where the source node will drop
460
+ * @returns {boolean} True if the node is permitted to be dropped here
461
+ */
462
+ callbacks.accept = function (sourceNodeScope, destNodesScope, destIndex) {
463
+ return !(destNodesScope.nodropEnabled || destNodesScope.$treeScope.nodropEnabled || destNodesScope.outOfDepth(sourceNodeScope));
464
+ };
612
465
 
613
- (function () {
614
- 'use strict';
466
+ callbacks.beforeDrag = function (sourceNodeScope) {
467
+ return true;
468
+ };
615
469
 
616
- angular.module('ui.tree')
617
- .directive('uiTree', [ 'treeConfig', '$window',
618
- function(treeConfig, $window) {
619
- return {
620
- restrict: 'A',
621
- scope: true,
622
- controller: 'TreeController',
623
- link: function(scope, element, attrs) {
624
- var callbacks = {
625
- accept: null,
626
- beforeDrag: null
627
- };
628
-
629
- var config = {};
630
- angular.extend(config, treeConfig);
631
- if (config.treeClass) {
632
- element.addClass(config.treeClass);
633
- }
470
+ callbacks.expandTimeoutStart = function()
471
+ {
634
472
 
635
- scope.$emptyElm = angular.element($window.document.createElement('div'));
636
- if (config.emptyTreeClass) {
637
- scope.$emptyElm.addClass(config.emptyTreeClass);
638
- }
473
+ };
639
474
 
640
- scope.$watch('$nodesScope.$modelValue.length', function() {
641
- if (scope.$nodesScope.$modelValue) {
642
- scope.resetEmptyElement();
643
- }
644
- }, true);
475
+ callbacks.expandTimeoutCancel = function()
476
+ {
645
477
 
646
- scope.$watch(attrs.dragEnabled, function(val) {
647
- if((typeof val) == "boolean") {
648
- scope.dragEnabled = val;
649
- }
650
- });
478
+ };
651
479
 
652
- scope.$watch(attrs.emptyPlaceHolderEnabled, function(val) {
653
- if((typeof val) == "boolean") {
654
- scope.emptyPlaceHolderEnabled = val;
655
- }
656
- });
480
+ callbacks.expandTimeoutEnd = function()
481
+ {
657
482
 
658
- scope.$watch(attrs.maxDepth, function(val) {
659
- if((typeof val) == "number") {
660
- scope.maxDepth = val;
661
- }
662
- });
483
+ };
663
484
 
664
- scope.$watch(attrs.dragDelay, function(val) {
665
- if((typeof val) == "number") {
666
- scope.dragDelay = val;
667
- }
668
- });
485
+ callbacks.removed = function (node) {
669
486
 
670
- // check if the dest node can accept the dragging node
671
- // by default, we check the 'data-nodrop' attribute in `ui-tree-nodes`
672
- // and the 'max-depth' attribute in `ui-tree` or `ui-tree-nodes`.
673
- // the method can be overrided
674
- callbacks.accept = function(sourceNodeScope, destNodesScope, destIndex) {
675
- if (destNodesScope.nodrop || destNodesScope.outOfDepth(sourceNodeScope)) {
676
- return false;
677
- }
678
- return true;
679
- };
487
+ };
680
488
 
681
- callbacks.beforeDrag = function(sourceNodeScope) {
682
- return true;
683
- };
489
+ /**
490
+ * Callback is fired when a node is successfully dropped in a new location
491
+ * @param event
492
+ */
493
+ callbacks.dropped = function (event) {
684
494
 
685
- callbacks.removed = function(node){
686
-
687
- };
495
+ };
688
496
 
689
- callbacks.dropped = function(event) {
497
+ /**
498
+ * Callback is fired each time the user starts dragging a node
499
+ * @param event
500
+ */
501
+ callbacks.dragStart = function (event) {
690
502
 
691
- };
503
+ };
692
504
 
693
- //
694
- callbacks.dragStart = function(event) {
505
+ /**
506
+ * Callback is fired each time a dragged node is moved with the mouse/touch.
507
+ * @param event
508
+ */
509
+ callbacks.dragMove = function (event) {
695
510
 
696
- };
511
+ };
697
512
 
698
- callbacks.dragMove = function(event) {
513
+ /**
514
+ * Callback is fired when the tree exits drag mode. If the user dropped a node, the drop may have been
515
+ * accepted or reverted.
516
+ * @param event
517
+ */
518
+ callbacks.dragStop = function (event) {
699
519
 
700
- };
520
+ };
701
521
 
702
- callbacks.dragStop = function(event) {
522
+ /**
523
+ * Callback is fired when a user drops a node (but prior to processing the drop action)
524
+ * beforeDrop can return a Promise, truthy, or falsy (returning nothing is falsy).
525
+ * If it returns falsy, or a resolve Promise, the node move is accepted
526
+ * If it returns truthy, or a rejected Promise, the node move is reverted
527
+ * @param event
528
+ * @returns {Boolean|Promise} Truthy (or rejected Promise) to cancel node move; falsy (or resolved promise)
529
+ */
530
+ callbacks.beforeDrop = function (event) {
703
531
 
704
- };
532
+ };
705
533
 
706
- callbacks.beforeDrop = function(event) {
534
+ /**
535
+ * Callback is fired when a user toggles node (but after processing the toggle action)
536
+ * @param sourceNodeScope
537
+ * @param collapsed
538
+ */
539
+ callbacks.toggle = function (collapsed, sourceNodeScope) {
707
540
 
708
- };
541
+ };
709
542
 
710
- scope.$watch(attrs.uiTree, function(newVal, oldVal){
711
- angular.forEach(newVal, function(value, key){
712
- if (callbacks[key]) {
713
- if (typeof value === "function") {
714
- callbacks[key] = value;
543
+ scope.$watch(attrs.uiTree, function (newVal, oldVal) {
544
+ angular.forEach(newVal, function (value, key) {
545
+ if (callbacks[key]) {
546
+ if (typeof value === 'function') {
547
+ callbacks[key] = value;
548
+ }
715
549
  }
716
- }
717
- });
550
+ });
718
551
 
719
- scope.$callbacks = callbacks;
720
- }, true);
552
+ scope.$callbacks = callbacks;
553
+ }, true);
721
554
 
722
555
 
723
- }
724
- };
725
- }
726
- ]);
556
+ }
557
+ };
558
+ }
559
+ ]);
727
560
  })();
728
561
 
729
562
  (function () {
730
563
  'use strict';
731
564
 
732
565
  angular.module('ui.tree')
733
- .directive('uiTreeNodes', [ 'treeConfig', '$window',
734
- function(treeConfig) {
735
- return {
736
- require: ['ngModel', '?^uiTreeNode', '^uiTree'],
737
- restrict: 'A',
738
- scope: true,
739
- controller: 'TreeNodesController',
740
- link: function(scope, element, attrs, controllersArr) {
741
-
742
- var config = {};
743
- angular.extend(config, treeConfig);
744
- if (config.nodesClass) {
745
- element.addClass(config.nodesClass);
746
- }
747
-
748
- var ngModel = controllersArr[0];
749
- var treeNodeCtrl = controllersArr[1];
750
- var treeCtrl = controllersArr[2];
751
- if (treeNodeCtrl) {
752
- treeNodeCtrl.scope.$childNodesScope = scope;
753
- scope.$nodeScope = treeNodeCtrl.scope;
754
- }
755
- else { // find the root nodes if there is no parent node and have a parent ui-tree
756
- treeCtrl.scope.$nodesScope = scope;
757
- }
758
- scope.$treeScope = treeCtrl.scope;
759
-
760
- if (ngModel) {
761
- ngModel.$render = function() {
762
- if (!ngModel.$modelValue || !angular.isArray(ngModel.$modelValue)) {
763
- scope.$modelValue = [];
764
- }
765
- scope.$modelValue = ngModel.$modelValue;
766
- };
767
- }
768
-
769
- scope.$watch(attrs.maxDepth, function(val) {
770
- if((typeof val) == "number") {
771
- scope.maxDepth = val;
566
+ .directive('uiTreeHandle', ['treeConfig',
567
+ function (treeConfig) {
568
+ return {
569
+ require: '^uiTreeNode',
570
+ restrict: 'A',
571
+ scope: true,
572
+ controller: 'TreeHandleController',
573
+ link: function (scope, element, attrs, treeNodeCtrl) {
574
+ var config = {};
575
+ angular.extend(config, treeConfig);
576
+ if (config.handleClass) {
577
+ element.addClass(config.handleClass);
772
578
  }
773
- });
774
-
775
- attrs.$observe('nodrop', function(val) {
776
- scope.nodrop = ((typeof val) != "undefined");
777
- });
778
-
779
- attrs.$observe('horizontal', function(val) {
780
- scope.horizontal = ((typeof val) != "undefined");
781
- });
782
-
783
- }
784
- };
785
- }
786
- ]);
579
+ // connect with the tree node.
580
+ if (scope != treeNodeCtrl.scope) {
581
+ scope.$nodeScope = treeNodeCtrl.scope;
582
+ treeNodeCtrl.scope.$handleScope = scope;
583
+ }
584
+ }
585
+ };
586
+ }
587
+ ]);
787
588
  })();
788
589
 
789
590
  (function () {
@@ -791,453 +592,1238 @@
791
592
 
792
593
  angular.module('ui.tree')
793
594
 
794
- .directive('uiTreeNode', ['treeConfig', '$uiTreeHelper', '$window', '$document','$timeout',
795
- function (treeConfig, $uiTreeHelper, $window, $document, $timeout) {
595
+ .directive('uiTreeNode', ['treeConfig', 'UiTreeHelper', '$window', '$document', '$timeout', '$q',
596
+ function (treeConfig, UiTreeHelper, $window, $document, $timeout, $q) {
796
597
  return {
797
598
  require: ['^uiTreeNodes', '^uiTree'],
798
599
  restrict: 'A',
799
600
  controller: 'TreeNodeController',
800
- link: function(scope, element, attrs, controllersArr) {
801
- var config = {};
601
+ link: function (scope, element, attrs, controllersArr) {
602
+ var config = {},
603
+ hasTouch = 'ontouchstart' in window,
604
+ firstMoving,
605
+ dragInfo,
606
+ pos,
607
+ placeElm,
608
+ hiddenPlaceElm,
609
+ dragElm,
610
+ scrollContainerElm,
611
+ unhover,
612
+ treeScope = null,
613
+ elements, // As a parameter for callbacks
614
+ dragDelaying = true,
615
+ dragStarted = false,
616
+ dragTimer = null,
617
+ body = document.body,
618
+ html = document.documentElement,
619
+ document_height,
620
+ document_width,
621
+ dragStart,
622
+ tagName,
623
+ dragMove,
624
+ dragEnd,
625
+ dragStartEvent,
626
+ dragMoveEvent,
627
+ dragEndEvent,
628
+ dragCancelEvent,
629
+ dragDelay,
630
+ bindDragStartEvents,
631
+ bindDragMoveEvents,
632
+ unbindDragMoveEvents,
633
+ keydownHandler,
634
+ isHandleChild,
635
+ el,
636
+ isUiTreeRoot,
637
+ treeOfOrigin;
638
+
639
+ //Adding configured class to ui-tree-node.
802
640
  angular.extend(config, treeConfig);
641
+
803
642
  if (config.nodeClass) {
804
643
  element.addClass(config.nodeClass);
805
644
  }
645
+
646
+ //Call init function in nodeCtrl, sets parent node and sets up sub nodes.
806
647
  scope.init(controllersArr);
807
648
 
808
- scope.collapsed = !!$uiTreeHelper.getNodeAttribute(scope, 'collapsed');
649
+ scope.collapsed = !!UiTreeHelper.getNodeAttribute(scope, 'collapsed') || treeConfig.defaultCollapsed;
650
+ scope.expandOnHover = !!UiTreeHelper.getNodeAttribute(scope, 'expandOnHover');
651
+ scope.scrollContainer = UiTreeHelper.getNodeAttribute(scope, 'scrollContainer') || attrs.scrollContainer || null;
652
+ scope.sourceOnly = scope.nodropEnabled || scope.$treeScope.nodropEnabled;
809
653
 
810
- scope.$watch(attrs.collapsed, function(val) {
811
- if((typeof val) == "boolean") {
654
+ scope.$watch(attrs.collapsed, function (val) {
655
+ if ((typeof val) == 'boolean') {
812
656
  scope.collapsed = val;
813
657
  }
814
658
  });
815
659
 
816
- scope.$watch('collapsed', function(val) {
817
- $uiTreeHelper.setNodeAttribute(scope, 'collapsed', val);
660
+ //Watches to trigger behavior based on actions and settings.
661
+ scope.$watch('collapsed', function (val) {
662
+ UiTreeHelper.setNodeAttribute(scope, 'collapsed', val);
818
663
  attrs.$set('collapsed', val);
819
664
  });
820
665
 
821
- var hasTouch = 'ontouchstart' in window;
822
- // todo startPos is unused
823
- var startPos, firstMoving, dragInfo, pos;
824
- var placeElm, hiddenPlaceElm, dragElm;
825
- var treeScope = null;
826
- var elements; // As a parameter for callbacks
827
- var dragDelaying = true;
828
- var dragStarted = false;
829
- var dragTimer = null;
830
- var body = document.body,
831
- html = document.documentElement,
832
- document_height,
833
- document_width;
834
-
835
- var dragStart = function(e) {
836
- if (!hasTouch && (e.button == 2 || e.which == 3)) {
837
- // disable right click
838
- return;
666
+ scope.$watch(attrs.expandOnHover, function(val) {
667
+ if ((typeof val) === 'boolean' || (typeof val) === 'number') {
668
+ scope.expandOnHover = val;
669
+ }
670
+ });
671
+
672
+ scope.$watch('expandOnHover', function (val) {
673
+ UiTreeHelper.setNodeAttribute(scope, 'expandOnHover', val);
674
+ attrs.$set('expandOnHover', val);
675
+ });
676
+
677
+ attrs.$observe('scrollContainer', function(val) {
678
+ if ((typeof val) === 'string') {
679
+ scope.scrollContainer = val;
839
680
  }
840
- if (e.uiTreeDragging || (e.originalEvent && e.originalEvent.uiTreeDragging)) { // event has already fired in other scope.
681
+ });
682
+
683
+ scope.$watch('scrollContainer', function(val) {
684
+ UiTreeHelper.setNodeAttribute(scope, 'scrollContainer', val);
685
+ attrs.$set('scrollContainer', val);
686
+ scrollContainerElm = document.querySelector(val);
687
+ });
688
+
689
+ scope.$on('angular-ui-tree:collapse-all', function () {
690
+ scope.collapsed = true;
691
+ });
692
+
693
+ scope.$on('angular-ui-tree:expand-all', function () {
694
+ scope.collapsed = false;
695
+ });
696
+
697
+ /**
698
+ * Called when the user has grabbed a node and started dragging it.
699
+ *
700
+ * @param {MouseEvent} e event that is triggered by DOM.
701
+ * @return undefined?
702
+ */
703
+ dragStart = function (e) {
704
+
705
+ //Disable right click.
706
+ if (!hasTouch && (e.button === 2 || e.which === 3)) {
841
707
  return;
842
708
  }
843
709
 
844
- // the element which is clicked.
845
- var eventElm = angular.element(e.target);
846
- var eventScope = eventElm.scope();
847
- if (!eventScope || !eventScope.$type) {
710
+ //Event has already fired in other scope.
711
+ if (e.uiTreeDragging || (e.originalEvent && e.originalEvent.uiTreeDragging)) {
848
712
  return;
849
713
  }
850
- if (eventScope.$type != 'uiTreeNode'
851
- && eventScope.$type != 'uiTreeHandle') { // Check if it is a node or a handle
714
+
715
+ //The node being dragged.
716
+ var eventElm = angular.element(e.target),
717
+ isHandleChild,
718
+ cloneElm,
719
+ eventElmTagName,
720
+ tagName,
721
+ eventObj,
722
+ tdElm,
723
+ hStyle,
724
+ isTreeNode,
725
+ isTreeNodeHandle;
726
+
727
+ //If the target element is a child element of a ui-tree-handle,
728
+ // use the containing handle element as target element.
729
+ isHandleChild = UiTreeHelper.treeNodeHandlerContainerOfElement(eventElm);
730
+ if (isHandleChild) {
731
+ eventElm = angular.element(isHandleChild);
732
+ }
733
+
734
+ cloneElm = element.clone();
735
+ isTreeNode = UiTreeHelper.elementIsTreeNode(eventElm);
736
+ isTreeNodeHandle = UiTreeHelper.elementIsTreeNodeHandle(eventElm);
737
+
738
+ //If we are not triggering mousedown on our uiTree or any of it's parts, return.
739
+ if (!isTreeNode && !isTreeNodeHandle) {
852
740
  return;
853
741
  }
854
- if (eventScope.$type == 'uiTreeNode'
855
- && eventScope.$handleScope) { // If the node has a handle, then it should be clicked by the handle
742
+
743
+ //If we are not triggering mousedown on our uiTree or any of it's parts, return.
744
+ if (isTreeNode && UiTreeHelper.elementContainsTreeNodeHandler(eventElm)) {
856
745
  return;
857
746
  }
858
747
 
859
- var eventElmTagName = eventElm.prop('tagName').toLowerCase();
748
+ //Dragging not allowed on inputs or buttons.
749
+ eventElmTagName = eventElm.prop('tagName').toLowerCase();
860
750
  if (eventElmTagName == 'input' ||
861
- eventElmTagName == 'textarea' ||
862
- eventElmTagName == 'button' ||
863
- eventElmTagName == 'select') { // if it's a input or button, ignore it
751
+ eventElmTagName == 'textarea' ||
752
+ eventElmTagName == 'button' ||
753
+ eventElmTagName == 'select') {
864
754
  return;
865
755
  }
866
756
 
867
- // check if it or it's parents has a 'data-nodrag' attribute
868
- while (eventElm && eventElm[0] && eventElm[0] != element) {
869
- if ($uiTreeHelper.nodrag(eventElm)) { // if the node mark as `nodrag`, DONOT drag it.
757
+ //Check if it or it's parents has a 'data-nodrag' attribute
758
+ el = angular.element(e.target);
759
+ isUiTreeRoot = el[0].attributes['ui-tree'];
760
+ while (el && el[0] && el[0] !== element && !isUiTreeRoot) {
761
+
762
+ //Checking that I can access attributes.
763
+ if (el[0].attributes) {
764
+ isUiTreeRoot = el[0].attributes['ui-tree'];
765
+ }
766
+
767
+ //If the node mark as `nodrag`, DONOT drag it.
768
+ if (UiTreeHelper.nodrag(el)) {
870
769
  return;
871
770
  }
872
- eventElm = eventElm.parent();
771
+ el = el.parent();
873
772
  }
874
773
 
875
- if (!scope.beforeDrag(scope)){
774
+ //If users beforeDrag calback returns falsey, do not initiate.
775
+ if (!scope.beforeDrag(scope)) {
876
776
  return;
877
777
  }
878
778
 
879
- e.uiTreeDragging = true; // stop event bubbling
779
+ //Set property checked at start of function to prevent running logic again.
780
+ e.uiTreeDragging = true;
880
781
  if (e.originalEvent) {
881
782
  e.originalEvent.uiTreeDragging = true;
882
783
  }
883
784
  e.preventDefault();
884
- var eventObj = $uiTreeHelper.eventObj(e);
885
785
 
786
+ //Get original event if TouchEvent.
787
+ eventObj = UiTreeHelper.eventObj(e);
788
+
789
+ //Set boolean used to specify beginning of move.
886
790
  firstMoving = true;
887
- dragInfo = $uiTreeHelper.dragInfo(scope);
888
791
 
889
- var tagName = scope.$element.prop('tagName');
792
+ //Setting drag info properties and methods in scope of node being moved.
793
+ dragInfo = UiTreeHelper.dragInfo(scope);
794
+
795
+ //Setting original tree to adjust horizontal behavior in drag move.
796
+ treeOfOrigin = dragInfo.source.$treeScope.$id;
797
+
798
+ //Determine tage name of element ui-tree-node is on.
799
+ tagName = element.prop('tagName');
800
+
890
801
  if (tagName.toLowerCase() === 'tr') {
802
+
803
+ //Create a new table column as placeholder.
891
804
  placeElm = angular.element($window.document.createElement(tagName));
892
- var tdElm = angular.element($window.document.createElement('td'))
893
- .addClass(config.placeHolderClass);
805
+
806
+ //Create a column placeholder and set colspan to whole row length.
807
+ tdElm = angular.element($window.document.createElement('td'))
808
+ .addClass(config.placeholderClass)
809
+ .attr('colspan', element[0].children.length);
894
810
  placeElm.append(tdElm);
895
811
  } else {
812
+
813
+ //If not a table just duplicate element and add placeholder class.
896
814
  placeElm = angular.element($window.document.createElement(tagName))
897
- .addClass(config.placeHolderClass);
815
+ .addClass(config.placeholderClass);
898
816
  }
817
+
818
+ //Create a hidden placeholder and add class from config.
899
819
  hiddenPlaceElm = angular.element($window.document.createElement(tagName));
900
820
  if (config.hiddenClass) {
901
821
  hiddenPlaceElm.addClass(config.hiddenClass);
902
822
  }
903
- pos = $uiTreeHelper.positionStarted(eventObj, scope.$element);
904
- placeElm.css('height', $uiTreeHelper.height(scope.$element) + 'px');
905
- placeElm.css('width', $uiTreeHelper.width(scope.$element) + 'px');
823
+
824
+ //Getting starting position of element being moved.
825
+ pos = UiTreeHelper.positionStarted(eventObj, element);
826
+ placeElm.css('height', element.prop('offsetHeight') + 'px');
827
+
828
+ //Creating drag element to represent node.
906
829
  dragElm = angular.element($window.document.createElement(scope.$parentNodesScope.$element.prop('tagName')))
907
- .addClass(scope.$parentNodesScope.$element.attr('class')).addClass(config.dragClass);
908
- dragElm.css('width', $uiTreeHelper.width(scope.$element) + 'px');
830
+ .addClass(scope.$parentNodesScope.$element.attr('class')).addClass(config.dragClass);
831
+ dragElm.css('width', UiTreeHelper.width(element) + 'px');
909
832
  dragElm.css('z-index', 9999);
910
833
 
911
- // Prevents cursor to change rapidly in Opera 12.16 and IE when dragging an element
912
- var hStyle = (scope.$element[0].querySelector('.angular-ui-tree-handle') || scope.$element[0]).currentStyle;
834
+ //Prevents cursor to change rapidly in Opera 12.16 and IE when dragging an element.
835
+ hStyle = (element[0].querySelector('.angular-ui-tree-handle') || element[0]).currentStyle;
913
836
  if (hStyle) {
914
837
  document.body.setAttribute('ui-tree-cursor', $document.find('body').css('cursor') || '');
915
838
  $document.find('body').css({'cursor': hStyle.cursor + '!important'});
916
839
  }
917
840
 
918
- scope.$element.after(placeElm);
919
- scope.$element.after(hiddenPlaceElm);
920
- dragElm.append(scope.$element);
841
+ //If tree is sourceOnly (noDragDrop) don't show placeholder when moving about it.
842
+ if (scope.sourceOnly) {
843
+ placeElm.css('display', 'none');
844
+ }
845
+
846
+ //Insert placeholder.
847
+ element.after(placeElm);
848
+ element.after(hiddenPlaceElm);
849
+ if (dragInfo.isClone() && scope.sourceOnly) {
850
+ dragElm.append(cloneElm);
851
+ } else {
852
+ dragElm.append(element);
853
+ }
854
+
855
+ //Create drag element.
921
856
  $document.find('body').append(dragElm);
857
+
858
+ //Set drag elements position on screen.
922
859
  dragElm.css({
923
- 'left' : eventObj.pageX - pos.offsetX + 'px',
924
- 'top' : eventObj.pageY - pos.offsetY + 'px'
860
+ 'left': eventObj.pageX - pos.offsetX + 'px',
861
+ 'top': eventObj.pageY - pos.offsetY + 'px'
925
862
  });
926
863
  elements = {
927
864
  placeholder: placeElm,
928
865
  dragging: dragElm
929
866
  };
930
867
 
931
- angular.element($document).bind('touchend', dragEndEvent);
932
- angular.element($document).bind('touchcancel', dragEndEvent);
933
- angular.element($document).bind('touchmove', dragMoveEvent);
934
- angular.element($document).bind('mouseup', dragEndEvent);
935
- angular.element($document).bind('mousemove', dragMoveEvent);
936
- angular.element($document).bind('mouseleave', dragCancelEvent);
868
+ //Create all drag/move bindings.
869
+ bindDragMoveEvents();
870
+
871
+ //Fire dragStart callback.
872
+ scope.$apply(function () {
873
+ scope.$treeScope.$callbacks.dragStart(dragInfo.eventArgs(elements, pos));
874
+ });
937
875
 
876
+ //Get bounds of document.
938
877
  document_height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
939
878
  document_width = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth);
940
879
  };
941
880
 
942
- var dragMove = function(e) {
943
- if (!dragStarted) {
944
- if (!dragDelaying) {
945
- dragStarted = true;
946
- scope.$apply(function() {
947
- scope.$callbacks.dragStart(dragInfo.eventArgs(elements, pos));
948
- });
949
- }
950
- return;
951
- }
952
-
953
- var eventObj = $uiTreeHelper.eventObj(e);
954
- var prev, leftElmPos, topElmPos;
955
-
881
+ dragMove = function (e) {
882
+ var eventObj = UiTreeHelper.eventObj(e),
883
+ prev,
884
+ next,
885
+ leftElmPos,
886
+ topElmPos,
887
+ top_scroll,
888
+ bottom_scroll,
889
+ scrollContainerElmRect,
890
+ target,
891
+ targetX,
892
+ targetY,
893
+ displayElm,
894
+ targetNode,
895
+ targetElm,
896
+ isEmpty,
897
+ scrollDownBy,
898
+ scrollUpBy,
899
+ targetOffset,
900
+ targetBefore,
901
+ moveWithinTree,
902
+ targetBeforeBuffer,
903
+ targetHeight,
904
+ targetChildElm,
905
+ targetChildHeight;
906
+
907
+ //If check ensures that drag element was created.
956
908
  if (dragElm) {
957
909
  e.preventDefault();
958
910
 
911
+ //Deselect anything (text, etc.) that was selected when move began.
959
912
  if ($window.getSelection) {
960
913
  $window.getSelection().removeAllRanges();
961
914
  } else if ($window.document.selection) {
962
915
  $window.document.selection.empty();
963
916
  }
964
917
 
918
+ //Get top left positioning of element being moved.
965
919
  leftElmPos = eventObj.pageX - pos.offsetX;
966
920
  topElmPos = eventObj.pageY - pos.offsetY;
967
921
 
968
- //dragElm can't leave the screen on the left
969
- if(leftElmPos < 0){
922
+ //dragElm can't leave the screen on the left.
923
+ if (leftElmPos < 0) {
970
924
  leftElmPos = 0;
971
925
  }
972
926
 
973
- //dragElm can't leave the screen on the top
974
- if(topElmPos < 0){
927
+ //dragElm can't leave the screen on the top.
928
+ if (topElmPos < 0) {
975
929
  topElmPos = 0;
976
930
  }
977
931
 
978
- //dragElm can't leave the screen on the bottom
979
- if ((topElmPos + 10) > document_height){
932
+ //dragElm can't leave the screen on the bottom.
933
+ if ((topElmPos + 10) > document_height) {
980
934
  topElmPos = document_height - 10;
981
935
  }
982
936
 
983
- //dragElm can't leave the screen on the right
984
- if((leftElmPos + 10) > document_width) {
937
+ //dragElm can't leave the screen on the right.
938
+ if ((leftElmPos + 10) > document_width) {
985
939
  leftElmPos = document_width - 10;
986
940
  }
987
941
 
942
+ //Updating element being moved css.
988
943
  dragElm.css({
989
944
  'left': leftElmPos + 'px',
990
945
  'top': topElmPos + 'px'
991
946
  });
992
947
 
993
- var top_scroll = window.pageYOffset || $window.document.documentElement.scrollTop;
994
- var bottom_scroll = top_scroll + (window.innerHeight || $window.document.clientHeight || $window.document.clientHeight);
948
+ if (scrollContainerElm) {
949
+ //Getting position to top and bottom of container element.
950
+ scrollContainerElmRect = scrollContainerElm.getBoundingClientRect();
951
+ top_scroll = scrollContainerElm.scrollTop;
952
+ bottom_scroll = top_scroll + scrollContainerElm.clientHeight;
995
953
 
996
- // to scroll down if cursor y-position is greater than the bottom position the vertical scroll
997
- if (bottom_scroll < eventObj.pageY && bottom_scroll <= document_height) {
998
- window.scrollBy(0, 10);
999
- }
954
+ //To scroll down if cursor y-position is greater than the bottom position of the container vertical scroll
955
+ if (scrollContainerElmRect.bottom < eventObj.clientY && bottom_scroll < scrollContainerElm.scrollHeight) {
956
+ scrollDownBy = Math.min(scrollContainerElm.scrollHeight - bottom_scroll, 10);
957
+ scrollContainerElm.scrollTop += scrollDownBy;
958
+ }
959
+
960
+ //To scroll top if cursor y-position is less than the top position of the container vertical scroll
961
+ if (scrollContainerElmRect.top > eventObj.clientY && top_scroll > 0) {
962
+ scrollUpBy = Math.min(top_scroll, 10);
963
+ scrollContainerElm.scrollTop -= scrollUpBy;
964
+ }
965
+ } else {
966
+ //Getting position to top and bottom of page.
967
+ top_scroll = window.pageYOffset || $window.document.documentElement.scrollTop;
968
+ bottom_scroll = top_scroll + (window.innerHeight || $window.document.clientHeight || $window.document.clientHeight);
969
+
970
+ //To scroll down if cursor y-position is greater than the bottom position of the window vertical scroll
971
+ if (bottom_scroll < eventObj.pageY && bottom_scroll < document_height) {
972
+ scrollDownBy = Math.min(document_height - bottom_scroll, 10);
973
+ window.scrollBy(0, scrollDownBy);
974
+ }
1000
975
 
1001
- // to scroll top if cursor y-position is less than the top position the vertical scroll
1002
- if (top_scroll > eventObj.pageY) {
1003
- window.scrollBy(0, -10);
976
+ //To scroll top if cursor y-position is less than the top position of the window vertical scroll
977
+ if (top_scroll > eventObj.pageY) {
978
+ scrollUpBy = Math.min(top_scroll, 10);
979
+ window.scrollBy(0, -scrollUpBy);
980
+ }
1004
981
  }
1005
982
 
1006
- $uiTreeHelper.positionMoved(e, pos, firstMoving);
983
+ //Calling service to update position coordinates based on move.
984
+ UiTreeHelper.positionMoved(e, pos, firstMoving);
1007
985
  if (firstMoving) {
1008
986
  firstMoving = false;
1009
987
  return;
1010
988
  }
1011
989
 
1012
- // move horizontal
1013
- if (pos.dirAx && pos.distAxX >= config.levelThreshold) {
1014
- pos.distAxX = 0;
1015
-
1016
- // increase horizontal level if previous sibling exists and is not collapsed
1017
- if (pos.distX > 0) {
1018
- prev = dragInfo.prev();
1019
- if (prev && !prev.collapsed
1020
- && prev.accept(scope, prev.childNodesCount())) {
1021
- prev.$childNodesScope.$element.append(placeElm);
1022
- dragInfo.moveTo(prev.$childNodesScope, prev.childNodes(), prev.childNodesCount());
1023
- }
1024
- }
1025
-
1026
- // decrease horizontal level
1027
- if (pos.distX < 0) {
1028
- // we can't decrease a level if an item preceeds the current one
1029
- var next = dragInfo.next();
1030
- if (!next) {
1031
- var target = dragInfo.parentNode(); // As a sibling of it's parent node
1032
- if (target
1033
- && target.$parentNodesScope.accept(scope, target.index() + 1)) {
1034
- target.$element.after(placeElm);
1035
- dragInfo.moveTo(target.$parentNodesScope, target.siblings(), target.index() + 1);
1036
- }
1037
- }
1038
- }
1039
- }
990
+ //Setting X point for elementFromPoint.
991
+ targetX = eventObj.pageX - ($window.pageXOffset ||
992
+ $window.document.body.scrollLeft ||
993
+ $window.document.documentElement.scrollLeft) -
994
+ ($window.document.documentElement.clientLeft || 0);
1040
995
 
1041
- // check if add it as a child node first
1042
- // todo decrease is unused
1043
- var decrease = ($uiTreeHelper.offset(dragElm).left - $uiTreeHelper.offset(placeElm).left) >= config.threshold;
1044
- var targetX = eventObj.pageX - $window.document.body.scrollLeft;
1045
- var targetY = eventObj.pageY - (window.pageYOffset || $window.document.documentElement.scrollTop);
996
+ targetY = eventObj.pageY - ($window.pageYOffset ||
997
+ $window.document.body.scrollTop ||
998
+ $window.document.documentElement.scrollTop) -
999
+ ($window.document.documentElement.clientTop || 0);
1046
1000
 
1047
- // Select the drag target. Because IE does not support CSS 'pointer-events: none', it will always
1001
+ //Select the drag target. Because IE does not support CSS 'pointer-events: none', it will always
1048
1002
  // pick the drag element itself as the target. To prevent this, we hide the drag element while
1049
1003
  // selecting the target.
1050
- var displayElm;
1051
1004
  if (angular.isFunction(dragElm.hide)) {
1052
1005
  dragElm.hide();
1053
- }else{
1006
+ } else {
1054
1007
  displayElm = dragElm[0].style.display;
1055
- dragElm[0].style.display = "none";
1008
+ dragElm[0].style.display = 'none';
1056
1009
  }
1057
1010
 
1058
- // when using elementFromPoint() inside an iframe, you have to call
1011
+ //When using elementFromPoint() inside an iframe, you have to call
1059
1012
  // elementFromPoint() twice to make sure IE8 returns the correct value
1013
+ //MDN: The elementFromPoint() method of the Document interface returns the topmost element at the specified coordinates.
1060
1014
  $window.document.elementFromPoint(targetX, targetY);
1061
1015
 
1062
- var targetElm = angular.element($window.document.elementFromPoint(targetX, targetY));
1016
+ //Set target element (element in specified x/y coordinates).
1017
+ targetElm = angular.element($window.document.elementFromPoint(targetX, targetY));
1018
+
1019
+ //If the target element is a child element of a ui-tree-handle,
1020
+ // use the containing handle element as target element
1021
+ isHandleChild = UiTreeHelper.treeNodeHandlerContainerOfElement(targetElm);
1022
+ if (isHandleChild) {
1023
+ targetElm = angular.element(isHandleChild);
1024
+ }
1025
+
1063
1026
  if (angular.isFunction(dragElm.show)) {
1064
1027
  dragElm.show();
1065
- }else{
1028
+ } else {
1066
1029
  dragElm[0].style.display = displayElm;
1067
1030
  }
1068
1031
 
1069
- // move vertical
1070
- if (!pos.dirAx) {
1071
- var targetBefore, targetNode;
1072
- // check it's new position
1073
- targetNode = targetElm.scope();
1074
- var isEmpty = false;
1032
+ //Assigning scope to target you are moving draggable over.
1033
+ if (UiTreeHelper.elementIsTree(targetElm)) {
1034
+ targetNode = targetElm.controller('uiTree').scope;
1035
+ } else if (UiTreeHelper.elementIsTreeNodeHandle(targetElm)) {
1036
+ targetNode = targetElm.controller('uiTreeHandle').scope;
1037
+ } else if (UiTreeHelper.elementIsTreeNode(targetElm)) {
1038
+ targetNode = targetElm.controller('uiTreeNode').scope;
1039
+ } else if (UiTreeHelper.elementIsTreeNodes(targetElm)) {
1040
+ targetNode = targetElm.controller('uiTreeNodes').scope;
1041
+ } else if (UiTreeHelper.elementIsPlaceholder(targetElm)) {
1042
+ targetNode = targetElm.controller('uiTreeNodes').scope;
1043
+ } else if (targetElm.controller('uiTreeNode')) {
1044
+ //Is a child element of a node.
1045
+ targetNode = targetElm.controller('uiTreeNode').scope;
1046
+ }
1047
+
1048
+ moveWithinTree = (targetNode && targetNode.$treeScope && targetNode.$treeScope.$id && targetNode.$treeScope.$id === treeOfOrigin);
1049
+
1050
+ /* (jcarter) Notes to developers:
1051
+ * pos.dirAx is either 0 or 1
1052
+ * 1 means horizontal movement is happening
1053
+ * 0 means vertical movement is happening
1054
+ */
1055
+
1056
+ // Move nodes up and down in nesting level.
1057
+ if (moveWithinTree && pos.dirAx) {
1058
+
1059
+ // increase horizontal level if previous sibling exists and is not collapsed
1060
+ // example 1.1.1 becomes 1.2
1061
+ if (pos.distX > 0) {
1062
+ prev = dragInfo.prev();
1063
+ if (prev && !prev.collapsed
1064
+ && prev.accept(scope, prev.childNodesCount())) {
1065
+ prev.$childNodesScope.$element.append(placeElm);
1066
+ dragInfo.moveTo(prev.$childNodesScope, prev.childNodes(), prev.childNodesCount());
1067
+ }
1068
+ }
1069
+
1070
+ // decrease horizontal level
1071
+ // example 1.2 become 1.1.1
1072
+ if (pos.distX < 0) {
1073
+ // we can't decrease a level if an item preceeds the current one
1074
+ next = dragInfo.next();
1075
+ if (!next) {
1076
+ target = dragInfo.parentNode(); // As a sibling of it's parent node
1077
+ if (target
1078
+ && target.$parentNodesScope.accept(scope, target.index() + 1)) {
1079
+ target.$element.after(placeElm);
1080
+ dragInfo.moveTo(target.$parentNodesScope, target.siblings(), target.index() + 1);
1081
+ }
1082
+ }
1083
+ }
1084
+ } else { //Either in origin tree and moving horizontally OR you are moving within a new tree.
1085
+
1086
+ //Check it's new position.
1087
+ isEmpty = false;
1088
+
1089
+ //Exit if target is not a uiTree or child of one.
1075
1090
  if (!targetNode) {
1076
1091
  return;
1077
1092
  }
1078
- if (targetNode.$type == 'uiTree' && targetNode.dragEnabled) {
1079
- isEmpty = targetNode.isEmpty(); // Check if it's empty tree
1093
+
1094
+ //Show the placeholder if it was hidden for nodrop-enabled and this is a new tree
1095
+ if (targetNode.$treeScope && !targetNode.$parent.nodropEnabled && !targetNode.$treeScope.nodropEnabled) {
1096
+ placeElm.css('display', '');
1080
1097
  }
1081
- if (targetNode.$type == 'uiTreeHandle') {
1098
+
1099
+ //Set whether target tree is empty or not.
1100
+ if (targetNode.$type === 'uiTree' && targetNode.dragEnabled) {
1101
+ isEmpty = targetNode.isEmpty();
1102
+ }
1103
+
1104
+ //If target is a handle set new target to handle's node.
1105
+ if (targetNode.$type === 'uiTreeHandle') {
1082
1106
  targetNode = targetNode.$nodeScope;
1083
1107
  }
1084
- if (targetNode.$type != 'uiTreeNode'
1085
- && !isEmpty) { // Check if it is a uiTreeNode or it's an empty tree
1108
+
1109
+ //Check if it is a uiTreeNode or it's an empty tree.
1110
+ if (targetNode.$type !== 'uiTreeNode' && !isEmpty) {
1111
+
1112
+ // Allow node to return to its original position if no longer hovering over target
1113
+ if (config.appendChildOnHover) {
1114
+ next = dragInfo.next();
1115
+ if (!next && unhover) {
1116
+ target = dragInfo.parentNode();
1117
+ target.$element.after(placeElm);
1118
+ dragInfo.moveTo(target.$parentNodesScope, target.siblings(), target.index() + 1);
1119
+ unhover = false;
1120
+ }
1121
+ }
1086
1122
  return;
1087
1123
  }
1088
1124
 
1089
- // if placeholder move from empty tree, reset it.
1125
+ //If placeholder move from empty tree, reset it.
1090
1126
  if (treeScope && placeElm.parent()[0] != treeScope.$element[0]) {
1091
1127
  treeScope.resetEmptyElement();
1092
1128
  treeScope = null;
1093
1129
  }
1094
1130
 
1095
- if (isEmpty) { // it's an empty tree
1131
+ //It's an empty tree
1132
+ if (isEmpty) {
1096
1133
  treeScope = targetNode;
1097
1134
  if (targetNode.$nodesScope.accept(scope, 0)) {
1098
1135
  targetNode.place(placeElm);
1099
1136
  dragInfo.moveTo(targetNode.$nodesScope, targetNode.$nodesScope.childNodes(), 0);
1100
1137
  }
1101
- } else if (targetNode.dragEnabled()){ // drag enabled
1102
- targetElm = targetNode.$element; // Get the element of ui-tree-node
1103
- var targetOffset = $uiTreeHelper.offset(targetElm);
1104
- targetBefore = targetNode.horizontal ? eventObj.pageX < (targetOffset.left + $uiTreeHelper.width(targetElm) / 2)
1105
- : eventObj.pageY < (targetOffset.top + $uiTreeHelper.height(targetElm) / 2);
1138
+ //Not empty and drag enabled.
1139
+ } else if (targetNode.dragEnabled()) {
1140
+
1141
+ //Setting/Resetting data for exanding on hover.
1142
+ if (angular.isDefined(scope.expandTimeoutOn) && scope.expandTimeoutOn !== targetNode.id) {
1143
+ $timeout.cancel(scope.expandTimeout);
1144
+ delete scope.expandTimeout;
1145
+ delete scope.expandTimeoutOn;
1146
+
1147
+ scope.$callbacks.expandTimeoutCancel();
1148
+ }
1149
+
1150
+ //Determining if expansion is needed.
1151
+ if (targetNode.collapsed) {
1152
+ if (scope.expandOnHover === true || (angular.isNumber(scope.expandOnHover) && scope.expandOnHover === 0)) {
1153
+ targetNode.collapsed = false;
1154
+ targetNode.$treeScope.$callbacks.toggle(false, targetNode);
1155
+ } else if (scope.expandOnHover !== false && angular.isNumber(scope.expandOnHover) && scope.expandOnHover > 0) {
1156
+
1157
+ //Triggering expansion.
1158
+ if (angular.isUndefined(scope.expandTimeoutOn)) {
1159
+ scope.expandTimeoutOn = targetNode.$id;
1160
+
1161
+ scope.$callbacks.expandTimeoutStart();
1162
+ scope.expandTimeout = $timeout(function()
1163
+ {
1164
+ scope.$callbacks.expandTimeoutEnd();
1165
+ targetNode.collapsed = false;
1166
+ targetNode.$treeScope.$callbacks.toggle(false, targetNode);
1167
+ }, scope.expandOnHover);
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ //Get the element of ui-tree-node
1173
+ targetElm = targetNode.$element;
1174
+ targetOffset = UiTreeHelper.offset(targetElm);
1175
+ targetHeight = UiTreeHelper.height(targetElm);
1176
+ targetChildElm = targetNode.$childNodesScope ? targetNode.$childNodesScope.$element : null;
1177
+ targetChildHeight = targetChildElm ? UiTreeHelper.height(targetChildElm) : 0;
1178
+ targetHeight -= targetChildHeight;
1179
+ targetBeforeBuffer = config.appendChildOnHover ? targetHeight * 0.25 : UiTreeHelper.height(targetElm) / 2;
1180
+ targetBefore = eventObj.pageY < (targetOffset.top + targetBeforeBuffer);
1106
1181
 
1107
1182
  if (targetNode.$parentNodesScope.accept(scope, targetNode.index())) {
1108
1183
  if (targetBefore) {
1109
1184
  targetElm[0].parentNode.insertBefore(placeElm[0], targetElm[0]);
1110
1185
  dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index());
1111
1186
  } else {
1112
- targetElm.after(placeElm);
1113
- dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index() + 1);
1187
+ // Try to append as a child if dragged upwards onto targetNode
1188
+ if (config.appendChildOnHover && targetNode.accept(scope, targetNode.childNodesCount())) {
1189
+ targetNode.$childNodesScope.$element.prepend(placeElm);
1190
+ dragInfo.moveTo(targetNode.$childNodesScope, targetNode.childNodes(), 0);
1191
+ unhover = true;
1192
+ } else {
1193
+ targetElm.after(placeElm);
1194
+ dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index() + 1);
1195
+ }
1114
1196
  }
1115
- }
1116
- else if (!targetBefore && targetNode.accept(scope, targetNode.childNodesCount())) { // we have to check if it can add the dragging node as a child
1197
+
1198
+ //We have to check if it can add the dragging node as a child.
1199
+ } else if (!targetBefore && targetNode.accept(scope, targetNode.childNodesCount())) {
1117
1200
  targetNode.$childNodesScope.$element.append(placeElm);
1118
1201
  dragInfo.moveTo(targetNode.$childNodesScope, targetNode.childNodes(), targetNode.childNodesCount());
1119
1202
  }
1120
1203
  }
1121
-
1122
1204
  }
1123
1205
 
1124
- scope.$apply(function() {
1125
- scope.$callbacks.dragMove(dragInfo.eventArgs(elements, pos));
1206
+ //Triggering dragMove callback.
1207
+ scope.$apply(function () {
1208
+ scope.$treeScope.$callbacks.dragMove(dragInfo.eventArgs(elements, pos));
1126
1209
  });
1127
1210
  }
1128
1211
  };
1129
1212
 
1130
- var dragEnd = function(e) {
1213
+ dragEnd = function (e) {
1214
+
1215
+ var dragEventArgs = dragInfo.eventArgs(elements, pos);
1216
+
1131
1217
  e.preventDefault();
1132
1218
 
1133
- if (dragElm) {
1134
- scope.$treeScope.$apply(function() {
1135
- scope.$callbacks.beforeDrop(dragInfo.eventArgs(elements, pos));
1136
- });
1137
- // roll back elements changed
1138
- hiddenPlaceElm.replaceWith(scope.$element);
1139
- placeElm.remove();
1140
-
1141
- dragElm.remove();
1142
- dragElm = null;
1143
- if (scope.$$apply) {
1144
- dragInfo.apply();
1145
- scope.$treeScope.$apply(function() {
1146
- scope.$callbacks.dropped(dragInfo.eventArgs(elements, pos));
1147
- });
1148
- } else {
1149
- bindDrag();
1150
- }
1151
- scope.$treeScope.$apply(function() {
1152
- scope.$callbacks.dragStop(dragInfo.eventArgs(elements, pos));
1153
- });
1154
- scope.$$apply = false;
1155
- dragInfo = null;
1219
+ //TODO(jcarter): Is dragStart need to be unbound?
1220
+ unbindDragMoveEvents();
1156
1221
 
1157
- }
1222
+ //This cancel the collapse/expand login running.
1223
+ $timeout.cancel(scope.expandTimeout);
1158
1224
 
1159
- // Restore cursor in Opera 12.16 and IE
1160
- var oldCur = document.body.getAttribute('ui-tree-cursor');
1161
- if (oldCur !== null) {
1162
- $document.find('body').css({'cursor': oldCur});
1163
- document.body.removeAttribute('ui-tree-cursor');
1164
- }
1225
+ scope.$treeScope.$apply(function () {
1226
+ $q.when(scope.$treeScope.$callbacks.beforeDrop(dragEventArgs))
1165
1227
 
1166
- angular.element($document).unbind('touchend', dragEndEvent); // Mobile
1167
- angular.element($document).unbind('touchcancel', dragEndEvent); // Mobile
1168
- angular.element($document).unbind('touchmove', dragMoveEvent); // Mobile
1169
- angular.element($document).unbind('mouseup', dragEndEvent);
1170
- angular.element($document).unbind('mousemove', dragMoveEvent);
1171
- angular.element($window.document.body).unbind('mouseleave', dragCancelEvent);
1228
+ //Promise resolved (or callback didn't return false)
1229
+ .then(function (allowDrop) {
1230
+ if (allowDrop !== false && scope.$$allowNodeDrop) {
1231
+ //Node drop accepted.
1232
+ dragInfo.apply();
1233
+
1234
+ //Fire the dropped callback only if the move was successful.
1235
+ scope.$treeScope.$callbacks.dropped(dragEventArgs);
1236
+ } else {
1237
+ //Drop canceled - revert the node to its original position.
1238
+ bindDragStartEvents();
1239
+ }
1240
+ })
1241
+
1242
+ //Promise rejected - revert the node to its original position.
1243
+ .catch(function () {
1244
+ bindDragStartEvents();
1245
+ })
1246
+ .finally(function () {
1247
+
1248
+ //Replace placeholder with newly dropped element.
1249
+ hiddenPlaceElm.replaceWith(scope.$element);
1250
+ placeElm.remove();
1251
+
1252
+ //Remove drag element if still in DOM.
1253
+ if (dragElm) {
1254
+ dragElm.remove();
1255
+ dragElm = null;
1256
+ }
1257
+
1258
+ //Fire dragStope callback.
1259
+ scope.$treeScope.$callbacks.dragStop(dragEventArgs);
1260
+ scope.$$allowNodeDrop = false;
1261
+ dragInfo = null;
1262
+
1263
+ //Restore cursor in Opera 12.16 and IE
1264
+ var oldCur = document.body.getAttribute('ui-tree-cursor');
1265
+ if (oldCur !== null) {
1266
+ $document.find('body').css({'cursor': oldCur});
1267
+ document.body.removeAttribute('ui-tree-cursor');
1268
+ }
1269
+ });
1270
+ });
1172
1271
  };
1173
1272
 
1174
- var dragStartEvent = function(e) {
1273
+ dragStartEvent = function (e) {
1175
1274
  if (scope.dragEnabled()) {
1176
1275
  dragStart(e);
1177
1276
  }
1178
1277
  };
1179
1278
 
1180
- var dragMoveEvent = function(e) {
1279
+ dragMoveEvent = function (e) {
1181
1280
  dragMove(e);
1182
1281
  };
1183
1282
 
1184
- var dragEndEvent = function(e) {
1185
- scope.$$apply = true;
1283
+ dragEndEvent = function (e) {
1284
+ scope.$$allowNodeDrop = true;
1186
1285
  dragEnd(e);
1187
1286
  };
1188
1287
 
1189
- var dragCancelEvent = function(e) {
1288
+ dragCancelEvent = function (e) {
1190
1289
  dragEnd(e);
1191
1290
  };
1192
1291
 
1193
- var bindDrag = function() {
1292
+ dragDelay = (function () {
1293
+ var to;
1294
+
1295
+ return {
1296
+ exec: function (fn, ms) {
1297
+ if (!ms) {
1298
+ ms = 0;
1299
+ }
1300
+ this.cancel();
1301
+ to = $timeout(fn, ms);
1302
+ },
1303
+ cancel: function () {
1304
+ $timeout.cancel(to);
1305
+ }
1306
+ };
1307
+ })();
1308
+
1309
+ keydownHandler = function (e) {
1310
+ if (e.keyCode === 27) {
1311
+ dragEndEvent(e);
1312
+ }
1313
+ };
1314
+
1315
+ /**
1316
+ * Binds the mouse/touch events to enable drag start for this node.
1317
+ */
1318
+ //This is outside of bindDragMoveEvents because of the potential for a delay setting.
1319
+ bindDragStartEvents = function () {
1194
1320
  element.bind('touchstart mousedown', function (e) {
1195
- dragDelaying = true;
1196
- dragStarted = false;
1197
- dragStartEvent(e);
1198
- dragTimer = $timeout(function(){dragDelaying = false;}, scope.dragDelay);
1321
+ //Don't call drag delay if no delay was specified.
1322
+ if (scope.dragDelay > 0) {
1323
+ dragDelay.exec(function () {
1324
+ dragStartEvent(e);
1325
+ }, scope.dragDelay);
1326
+ } else {
1327
+ dragStartEvent(e);
1328
+ }
1199
1329
  });
1200
- element.bind('touchend touchcancel mouseup',function(){$timeout.cancel(dragTimer);});
1330
+ element.bind('touchend touchcancel mouseup', function () {
1331
+ if (scope.dragDelay > 0) {
1332
+ dragDelay.cancel();
1333
+ }
1334
+ });
1335
+ };
1336
+ bindDragStartEvents();
1337
+
1338
+ /**
1339
+ * Binds mouse/touch events that handle moving/dropping this dragged node
1340
+ */
1341
+ bindDragMoveEvents = function () {
1342
+ angular.element($document).bind('touchend', dragEndEvent);
1343
+ angular.element($document).bind('touchcancel', dragEndEvent);
1344
+ angular.element($document).bind('touchmove', dragMoveEvent);
1345
+ angular.element($document).bind('mouseup', dragEndEvent);
1346
+ angular.element($document).bind('mousemove', dragMoveEvent);
1347
+ angular.element($document).bind('mouseleave', dragCancelEvent);
1348
+ angular.element($document).bind('keydown', keydownHandler);
1201
1349
  };
1202
- bindDrag();
1203
1350
 
1204
- angular.element($window.document.body).bind("keydown", function(e) {
1205
- if (e.keyCode == 27) {
1206
- scope.$$apply = false;
1207
- dragEnd(e);
1351
+ /**
1352
+ * Unbinds mouse/touch events that handle moving/dropping this dragged node.
1353
+ */
1354
+ unbindDragMoveEvents = function () {
1355
+ angular.element($document).unbind('touchend', dragEndEvent);
1356
+ angular.element($document).unbind('touchcancel', dragEndEvent);
1357
+ angular.element($document).unbind('touchmove', dragMoveEvent);
1358
+ angular.element($document).unbind('mouseup', dragEndEvent);
1359
+ angular.element($document).unbind('mousemove', dragMoveEvent);
1360
+ angular.element($document).unbind('mouseleave', dragCancelEvent);
1361
+ angular.element($document).unbind('keydown', keydownHandler);
1362
+ };
1363
+ }
1364
+ };
1365
+ }
1366
+ ]);
1367
+ })();
1368
+
1369
+ (function () {
1370
+ 'use strict';
1371
+
1372
+ angular.module('ui.tree')
1373
+ .directive('uiTreeNodes', ['treeConfig', '$window',
1374
+ function (treeConfig) {
1375
+ return {
1376
+ require: ['ngModel', '?^uiTreeNode', '^uiTree'],
1377
+ restrict: 'A',
1378
+ scope: true,
1379
+ controller: 'TreeNodesController',
1380
+ link: function (scope, element, attrs, controllersArr) {
1381
+
1382
+ var config = {},
1383
+ ngModel = controllersArr[0],
1384
+ treeNodeCtrl = controllersArr[1],
1385
+ treeCtrl = controllersArr[2];
1386
+
1387
+ angular.extend(config, treeConfig);
1388
+ if (config.nodesClass) {
1389
+ element.addClass(config.nodesClass);
1390
+ }
1391
+
1392
+ if (treeNodeCtrl) {
1393
+ treeNodeCtrl.scope.$childNodesScope = scope;
1394
+ scope.$nodeScope = treeNodeCtrl.scope;
1395
+ } else {
1396
+ // find the root nodes if there is no parent node and have a parent ui-tree
1397
+ treeCtrl.scope.$nodesScope = scope;
1398
+ }
1399
+ scope.$treeScope = treeCtrl.scope;
1400
+
1401
+ if (ngModel) {
1402
+ ngModel.$render = function () {
1403
+ scope.$modelValue = ngModel.$modelValue;
1404
+ };
1405
+ }
1406
+
1407
+ scope.$watch(function () {
1408
+ return attrs.maxDepth;
1409
+ }, function (val) {
1410
+ if ((typeof val) == 'number') {
1411
+ scope.maxDepth = val;
1208
1412
  }
1209
1413
  });
1414
+
1415
+ scope.$watch(function () {
1416
+ return attrs.nodropEnabled;
1417
+ }, function (newVal) {
1418
+ if ((typeof newVal) != 'undefined') {
1419
+ scope.nodropEnabled = true;
1420
+ }
1421
+ }, true);
1422
+
1210
1423
  }
1211
1424
  };
1212
1425
  }
1213
1426
  ]);
1214
-
1215
1427
  })();
1216
1428
 
1217
1429
  (function () {
1218
1430
  'use strict';
1219
1431
 
1220
1432
  angular.module('ui.tree')
1221
- .directive('uiTreeHandle', [ 'treeConfig', '$window',
1222
- function(treeConfig) {
1223
- return {
1224
- require: '^uiTreeNode',
1225
- restrict: 'A',
1226
- scope: true,
1227
- controller: 'TreeHandleController',
1228
- link: function(scope, element, attrs, treeNodeCtrl) {
1229
- var config = {};
1230
- angular.extend(config, treeConfig);
1231
- if (config.handleClass) {
1232
- element.addClass(config.handleClass);
1233
- }
1234
- // connect with the tree node.
1235
- if (scope != treeNodeCtrl.scope) {
1236
- scope.$nodeScope = treeNodeCtrl.scope;
1237
- treeNodeCtrl.scope.$handleScope = scope;
1433
+
1434
+ /**
1435
+ * @ngdoc service
1436
+ * @name ui.tree.service:UiTreeHelper
1437
+ * @requires ng.$document
1438
+ * @requires ng.$window
1439
+ *
1440
+ * @description
1441
+ * angular-ui-tree.
1442
+ */
1443
+ .factory('UiTreeHelper', ['$document', '$window', 'treeConfig',
1444
+ function ($document, $window, treeConfig) {
1445
+ return {
1446
+
1447
+ /**
1448
+ * A hashtable used to storage data of nodes
1449
+ * @type {Object}
1450
+ */
1451
+ nodesData: {},
1452
+
1453
+ setNodeAttribute: function (scope, attrName, val) {
1454
+ if (!scope.$modelValue) {
1455
+ return null;
1456
+ }
1457
+ var data = this.nodesData[scope.$modelValue.$$hashKey];
1458
+ if (!data) {
1459
+ data = {};
1460
+ this.nodesData[scope.$modelValue.$$hashKey] = data;
1461
+ }
1462
+ data[attrName] = val;
1463
+ },
1464
+
1465
+ getNodeAttribute: function (scope, attrName) {
1466
+ if (!scope.$modelValue) {
1467
+ return null;
1468
+ }
1469
+ var data = this.nodesData[scope.$modelValue.$$hashKey];
1470
+ if (data) {
1471
+ return data[attrName];
1472
+ }
1473
+ return null;
1474
+ },
1475
+
1476
+ /**
1477
+ * @ngdoc method
1478
+ * @methodOf ui.tree.service:$nodrag
1479
+ * @param {Object} targetElm angular element
1480
+ * @return {Bool} check if the node can be dragged.
1481
+ */
1482
+ nodrag: function (targetElm) {
1483
+ if (typeof targetElm.attr('data-nodrag') != 'undefined') {
1484
+ return targetElm.attr('data-nodrag') !== 'false';
1485
+ }
1486
+ return false;
1487
+ },
1488
+
1489
+ /**
1490
+ * Get the event object for touches.
1491
+ *
1492
+ * @param {MouseEvent|TouchEvent} e MouseEvent or TouchEvent that kicked off dragX method.
1493
+ * @return {MouseEvent|TouchEvent} Object returned as original event object.
1494
+ */
1495
+ eventObj: function (e) {
1496
+ var obj = e;
1497
+ if (e.targetTouches !== undefined) {
1498
+ //Set obj equal to the first Touch object in the TouchList.
1499
+ obj = e.targetTouches.item(0);
1500
+ //Logic to set obj to original TouchEvent.
1501
+ } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) {
1502
+ obj = e.originalEvent.targetTouches.item(0);
1503
+ }
1504
+ return obj;
1505
+ },
1506
+
1507
+ /**
1508
+ * Generate object used to store data about node being moved.
1509
+ *
1510
+ * {angular.$scope} node Scope of the node that is being moved.
1511
+ */
1512
+ dragInfo: function (node) {
1513
+ return {
1514
+ source: node,
1515
+ sourceInfo: {
1516
+ cloneModel: node.$treeScope.cloneEnabled === true ? angular.copy(node.$modelValue) : undefined,
1517
+ nodeScope: node,
1518
+ index: node.index(),
1519
+ nodesScope: node.$parentNodesScope
1520
+ },
1521
+ index: node.index(),
1522
+
1523
+ //Slice(0) just duplicates an array.
1524
+ siblings: node.siblings().slice(0),
1525
+ parent: node.$parentNodesScope,
1526
+
1527
+ //Reset parent to source parent.
1528
+ resetParent: function() {
1529
+ this.parent = node.$parentNodesScope;
1530
+ },
1531
+
1532
+ //Move the node to a new position, determining where the node will be inserted to when dropped happens here.
1533
+ moveTo: function (parent, siblings, index) {
1534
+ this.parent = parent;
1535
+
1536
+ //Duplicate siblings array.
1537
+ this.siblings = siblings.slice(0);
1538
+
1539
+ //If source node is in the target nodes
1540
+ var i = this.siblings.indexOf(this.source);
1541
+ if (i > -1) {
1542
+ this.siblings.splice(i, 1);
1543
+ if (this.source.index() < index) {
1544
+ index--;
1545
+ }
1546
+ }
1547
+
1548
+ this.siblings.splice(index, 0, this.source);
1549
+ this.index = index;
1550
+ },
1551
+
1552
+ //Get parent nodes nodeScope.
1553
+ parentNode: function () {
1554
+ return this.parent.$nodeScope;
1555
+ },
1556
+
1557
+ //Get previous sibling node.
1558
+ prev: function () {
1559
+ if (this.index > 0) {
1560
+ return this.siblings[this.index - 1];
1561
+ }
1562
+
1563
+ return null;
1564
+ },
1565
+
1566
+ //Get next sibling node.
1567
+ next: function () {
1568
+ if (this.index < this.siblings.length - 1) {
1569
+ return this.siblings[this.index + 1];
1570
+ }
1571
+
1572
+ return null;
1573
+ },
1574
+
1575
+ //Return what cloneEnabled is set to on uiTree.
1576
+ isClone: function () {
1577
+ return this.source.$treeScope.cloneEnabled === true;
1578
+ },
1579
+
1580
+ //Returns a copy of node passed in.
1581
+ clonedNode: function (node) {
1582
+ return angular.copy(node);
1583
+ },
1584
+
1585
+ //Returns true if parent or index have changed (move happened within any uiTree).
1586
+ isDirty: function () {
1587
+ return this.source.$parentNodesScope != this.parent ||
1588
+ this.source.index() != this.index;
1589
+ },
1590
+
1591
+ //Return whether node has a new parent (set on moveTo method).
1592
+ isForeign: function () {
1593
+ return this.source.$treeScope !== this.parent.$treeScope;
1594
+ },
1595
+
1596
+ //Sets arguments passed to user callbacks.
1597
+ eventArgs: function (elements, pos) {
1598
+ return {
1599
+ source: this.sourceInfo,
1600
+ dest: {
1601
+ index: this.index,
1602
+ nodesScope: this.parent
1603
+ },
1604
+ elements: elements,
1605
+ pos: pos
1606
+ };
1607
+ },
1608
+
1609
+ //Method that actually manipulates the node being moved.
1610
+ apply: function () {
1611
+
1612
+ var nodeData = this.source.$modelValue;
1613
+
1614
+ //Nodrop enabled on tree or parent
1615
+ if (this.parent.nodropEnabled || this.parent.$treeScope.nodropEnabled) {
1616
+ return;
1617
+ }
1618
+
1619
+ //Node was dropped in the same place - do nothing.
1620
+ if (!this.isDirty()) {
1621
+ return;
1622
+ }
1623
+
1624
+ //CloneEnabled and cross-tree so copy and do not remove from source.
1625
+ if (this.isClone() && this.isForeign()) {
1626
+ this.parent.insertNode(this.index, this.sourceInfo.cloneModel);
1627
+ //Any other case, remove and reinsert.
1628
+ } else {
1629
+ this.source.remove();
1630
+ this.parent.insertNode(this.index, nodeData);
1631
+ }
1632
+ }
1633
+ };
1634
+ },
1635
+
1636
+ /**
1637
+ * @ngdoc method
1638
+ * @name ui.tree#height
1639
+ * @methodOf ui.tree.service:UiTreeHelper
1640
+ *
1641
+ * @description
1642
+ * Get the height of an element.
1643
+ *
1644
+ * @param {Object} element Angular element.
1645
+ * @returns {String} Height
1646
+ */
1647
+ height: function (element) {
1648
+ return element.prop('scrollHeight');
1649
+ },
1650
+
1651
+ /**
1652
+ * @ngdoc method
1653
+ * @name ui.tree#width
1654
+ * @methodOf ui.tree.service:UiTreeHelper
1655
+ *
1656
+ * @description
1657
+ * Get the width of an element.
1658
+ *
1659
+ * @param {Object} element Angular element.
1660
+ * @returns {String} Width
1661
+ */
1662
+ width: function (element) {
1663
+ return element.prop('scrollWidth');
1664
+ },
1665
+
1666
+ /**
1667
+ * @ngdoc method
1668
+ * @name ui.tree#offset
1669
+ * @methodOf ui.nestedSortable.service:UiTreeHelper
1670
+ *
1671
+ * @description
1672
+ * Get the offset values of an element.
1673
+ *
1674
+ * @param {Object} element Angular element.
1675
+ * @returns {Object} Object with properties width, height, top and left
1676
+ */
1677
+ offset: function (element) {
1678
+ var boundingClientRect = element[0].getBoundingClientRect();
1679
+
1680
+ return {
1681
+ width: element.prop('offsetWidth'),
1682
+ height: element.prop('offsetHeight'),
1683
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
1684
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
1685
+ };
1686
+ },
1687
+
1688
+ /**
1689
+ * @ngdoc method
1690
+ * @name ui.tree#positionStarted
1691
+ * @methodOf ui.tree.service:UiTreeHelper
1692
+ *
1693
+ * @description
1694
+ * Get the start position of the target element according to the provided event properties.
1695
+ *
1696
+ * @param {Object} e Event
1697
+ * @param {Object} target Target element
1698
+ * @returns {Object} Object with properties offsetX, offsetY, startX, startY, nowX and dirX.
1699
+ */
1700
+ positionStarted: function (e, target) {
1701
+ var pos = {},
1702
+ pageX = e.pageX,
1703
+ pageY = e.pageY;
1704
+
1705
+ //Check to set correct data for TouchEvents
1706
+ if (e.originalEvent && e.originalEvent.touches && (e.originalEvent.touches.length > 0)) {
1707
+ pageX = e.originalEvent.touches[0].pageX;
1708
+ pageY = e.originalEvent.touches[0].pageY;
1709
+ }
1710
+ pos.offsetX = pageX - this.offset(target).left;
1711
+ pos.offsetY = pageY - this.offset(target).top;
1712
+ pos.startX = pos.lastX = pageX;
1713
+ pos.startY = pos.lastY = pageY;
1714
+ pos.nowX = pos.nowY = pos.distX = pos.distY = pos.dirAx = 0;
1715
+ pos.dirX = pos.dirY = pos.lastDirX = pos.lastDirY = pos.distAxX = pos.distAxY = 0;
1716
+ return pos;
1717
+ },
1718
+
1719
+ positionMoved: function (e, pos, firstMoving) {
1720
+
1721
+ var pageX = e.pageX,
1722
+ pageY = e.pageY,
1723
+ newAx;
1724
+
1725
+ //If there are multiple touch points, choose one to use as X and Y.
1726
+ if (e.originalEvent && e.originalEvent.touches && (e.originalEvent.touches.length > 0)) {
1727
+ pageX = e.originalEvent.touches[0].pageX;
1728
+ pageY = e.originalEvent.touches[0].pageY;
1729
+ }
1730
+
1731
+ //Mouse position last event.
1732
+ pos.lastX = pos.nowX;
1733
+ pos.lastY = pos.nowY;
1734
+
1735
+ //Mouse position this event.
1736
+ pos.nowX = pageX;
1737
+ pos.nowY = pageY;
1738
+
1739
+ //Distance mouse moved between events.
1740
+ pos.distX = pos.nowX - pos.lastX;
1741
+ pos.distY = pos.nowY - pos.lastY;
1742
+
1743
+ //Direction mouse was moving.
1744
+ pos.lastDirX = pos.dirX;
1745
+ pos.lastDirY = pos.dirY;
1746
+
1747
+ //Direction mouse is now moving (on both axis).
1748
+ pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;
1749
+ pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;
1750
+
1751
+ //Axis mouse is now moving on.
1752
+ newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;
1753
+
1754
+ //Do nothing on first move.
1755
+ if (firstMoving) {
1756
+ pos.dirAx = newAx;
1757
+ pos.moving = true;
1758
+ return;
1759
+ }
1760
+
1761
+ //Calc distance moved on this axis (and direction).
1762
+ if (pos.dirAx !== newAx) {
1763
+ pos.distAxX = 0;
1764
+ pos.distAxY = 0;
1765
+ } else {
1766
+ pos.distAxX += Math.abs(pos.distX);
1767
+ if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {
1768
+ pos.distAxX = 0;
1769
+ }
1770
+ pos.distAxY += Math.abs(pos.distY);
1771
+ if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {
1772
+ pos.distAxY = 0;
1773
+ }
1774
+ }
1775
+ pos.dirAx = newAx;
1776
+ },
1777
+
1778
+ elementIsTreeNode: function (element) {
1779
+ return typeof element.attr('ui-tree-node') !== 'undefined';
1780
+ },
1781
+
1782
+ elementIsTreeNodeHandle: function (element) {
1783
+ return typeof element.attr('ui-tree-handle') !== 'undefined';
1784
+ },
1785
+ elementIsTree: function (element) {
1786
+ return typeof element.attr('ui-tree') !== 'undefined';
1787
+ },
1788
+ elementIsTreeNodes: function (element) {
1789
+ return typeof element.attr('ui-tree-nodes') !== 'undefined';
1790
+ },
1791
+ elementIsPlaceholder: function (element) {
1792
+ return element.hasClass(treeConfig.placeholderClass);
1793
+ },
1794
+ elementContainsTreeNodeHandler: function (element) {
1795
+ return element[0].querySelectorAll('[ui-tree-handle]').length >= 1;
1796
+ },
1797
+ treeNodeHandlerContainerOfElement: function (element) {
1798
+ return findFirstParentElementWithAttribute('ui-tree-handle', element[0]);
1238
1799
  }
1239
- }
1240
- };
1800
+ };
1801
+ }
1802
+ ]);
1803
+
1804
+ // TODO: optimize this loop
1805
+ //(Jcarter): Suggest adding a parent element property on uiTree, then all these bubble
1806
+ // to <html> can trigger to stop when they reach the parent.
1807
+ function findFirstParentElementWithAttribute(attributeName, childObj) {
1808
+ //Undefined if the mouse leaves the browser window
1809
+ if (childObj === undefined) {
1810
+ return null;
1241
1811
  }
1242
- ]);
1243
- })();
1812
+ var testObj = childObj.parentNode,
1813
+ count = 1,
1814
+ //Check for setAttribute due to exception thrown by Firefox when a node is dragged outside the browser window
1815
+ res = (typeof testObj.setAttribute === 'function' && testObj.hasAttribute(attributeName)) ? testObj : null;
1816
+ while (testObj && typeof testObj.setAttribute === 'function' && !testObj.hasAttribute(attributeName)) {
1817
+ testObj = testObj.parentNode;
1818
+ res = testObj;
1819
+ //Stop once we reach top of page.
1820
+ if (testObj === document.documentElement) {
1821
+ res = null;
1822
+ break;
1823
+ }
1824
+ count++;
1825
+ }
1826
+ return res;
1827
+ }
1828
+
1829
+ })();