2 * @class Ext.tree.ViewDropZone
3 * @extends Ext.view.DropZone
6 Ext.define('Ext.tree.ViewDropZone', {
7 extend: 'Ext.view.DropZone',
10 * @cfg {Boolean} allowParentInsert
11 * Allow inserting a dragged node between an expanded parent node and its first child that will become a
12 * sibling of the parent when dropped (defaults to false)
14 allowParentInserts: false,
17 * @cfg {String} allowContainerDrop
18 * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
20 allowContainerDrops: false,
23 * @cfg {String} appendOnly
24 * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
29 * @cfg {String} expandDelay
30 * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
31 * over the target (defaults to 500)
35 indicatorCls: 'x-tree-ddindicator',
38 expandNode : function(node) {
40 if (!node.isLeaf() && !node.isExpanded()) {
42 this.expandProcId = false;
47 queueExpand : function(node) {
48 this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
52 cancelExpand : function() {
53 if (this.expandProcId) {
54 clearTimeout(this.expandProcId);
55 this.expandProcId = false;
59 getPosition: function(e, node) {
61 record = view.getRecord(node),
63 noAppend = record.isLeaf(),
65 region = Ext.fly(node).getRegion(),
68 // If we are dragging on top of the root node of the tree, we always want to append.
69 if (record.isRoot()) {
73 // Return 'append' if the node we are dragging on top of is not a leaf else return false.
74 if (this.appendOnly) {
75 return noAppend ? false : 'append';
78 if (!this.allowParentInsert) {
79 noBelow = record.hasChildNodes() && record.isExpanded();
82 fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
83 if (y >= region.top && y < (region.top + fragment)) {
86 else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
94 isValidDropPoint : function(node, position, dragZone, e, data) {
95 if (!node || !data.item) {
100 targetNode = view.getRecord(node),
101 draggedRecords = data.records,
102 dataLength = draggedRecords.length,
103 ln = draggedRecords.length,
106 // No drop position, or dragged records: invalid drop point
107 if (!(targetNode && position && dataLength)) {
111 // If the targetNode is within the folder we are dragging
112 for (i = 0; i < ln; i++) {
113 record = draggedRecords[i];
114 if (record.isNode && record.contains(targetNode)) {
119 // Respect the allowDrop field on Tree nodes
120 if (position === 'append' && targetNode.get('allowDrop') == false) {
123 else if (position != 'append' && targetNode.parentNode.get('allowDrop') == false) {
127 // If the target record is in the dragged dataset, then invalid drop
128 if (Ext.Array.contains(draggedRecords, targetNode)) {
132 // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
133 // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
137 onNodeOver : function(node, dragZone, e, data) {
138 var position = this.getPosition(e, node),
139 returnCls = this.dropNotAllowed,
141 targetNode = view.getRecord(node),
142 indicator = this.getIndicator(),
146 // auto node expand check
148 if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
149 this.queueExpand(targetNode);
152 if (this.isValidDropPoint(node, position, dragZone, e, data)) {
154 this.currentPosition = position;
155 this.overRecord = targetNode;
157 indicator.setWidth(Ext.fly(node).getWidth());
158 indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
160 if (position == 'before') {
161 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
162 indicator.showAt(0, indicatorY);
165 else if (position == 'after') {
166 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
167 indicatorY += Ext.fly(node).getHeight();
168 indicator.showAt(0, indicatorY);
172 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
173 // @TODO: set a class on the parent folder node to be able to style it
181 this.currentCls = returnCls;
185 onContainerOver : function(dd, e, data) {
186 return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
189 notifyOut: function() {
190 this.callParent(arguments);
194 handleNodeDrop : function(data, targetNode, position) {
197 parentNode = targetNode.parentNode,
198 store = view.getStore(),
201 insertionMethod, argList,
206 // If the copy flag is set, create a copy of the Models with the same IDs
208 records = data.records;
210 for (i = 0, len = records.length; i < len; i++) {
211 data.records.push(Ext.apply({}, records[i].data));
215 // Cancel any pending expand operation
218 // Grab a reference to the correct node insertion method.
219 // Create an arg list array intended for the apply method of the
220 // chosen node insertion method.
221 // Ensure the target object for the method is referenced by 'targetNode'
222 if (position == 'before') {
223 insertionMethod = parentNode.insertBefore;
224 argList = [null, targetNode];
225 targetNode = parentNode;
227 else if (position == 'after') {
228 if (targetNode.nextSibling) {
229 insertionMethod = parentNode.insertBefore;
230 argList = [null, targetNode.nextSibling];
233 insertionMethod = parentNode.appendChild;
236 targetNode = parentNode;
239 if (!targetNode.isExpanded()) {
240 needTargetExpand = true;
242 insertionMethod = targetNode.appendChild;
246 // A function to transfer the data into the destination tree
247 transferData = function() {
249 for (i = 0, len = data.records.length; i < len; i++) {
250 argList[0] = data.records[i];
251 node = insertionMethod.apply(targetNode, argList);
253 if (Ext.enableFx && me.dropHighlight) {
254 recordDomNodes.push(view.getNode(node));
258 // Kick off highlights after everything's been inserted, so they are
259 // more in sync without insertion/render overhead.
260 if (Ext.enableFx && me.dropHighlight) {
261 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read
262 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
263 Ext.Array.forEach(recordDomNodes, function(n) {
264 Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
269 // If dropping right on an unexpanded node, transfer the data after it is expanded.
270 if (needTargetExpand) {
271 targetNode.expand(false, transferData);
273 // Otherwise, call the data transfer function immediately