3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.tree.ViewDropZone
17 * @extends Ext.view.DropZone
20 Ext.define('Ext.tree.ViewDropZone', {
21 extend: 'Ext.view.DropZone',
24 * @cfg {Boolean} allowParentInsert
25 * Allow inserting a dragged node between an expanded parent node and its first child that will become a
26 * sibling of the parent when dropped.
28 allowParentInserts: false,
31 * @cfg {String} allowContainerDrop
32 * True if drops on the tree container (outside of a specific tree node) are allowed.
34 allowContainerDrops: false,
37 * @cfg {String} appendOnly
38 * True if the tree should only allow append drops (use for trees which are sorted).
43 * @cfg {String} expandDelay
44 * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
49 indicatorCls: 'x-tree-ddindicator',
52 expandNode : function(node) {
54 if (!node.isLeaf() && !node.isExpanded()) {
56 this.expandProcId = false;
61 queueExpand : function(node) {
62 this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
66 cancelExpand : function() {
67 if (this.expandProcId) {
68 clearTimeout(this.expandProcId);
69 this.expandProcId = false;
73 getPosition: function(e, node) {
75 record = view.getRecord(node),
77 noAppend = record.isLeaf(),
79 region = Ext.fly(node).getRegion(),
82 // If we are dragging on top of the root node of the tree, we always want to append.
83 if (record.isRoot()) {
87 // Return 'append' if the node we are dragging on top of is not a leaf else return false.
88 if (this.appendOnly) {
89 return noAppend ? false : 'append';
92 if (!this.allowParentInsert) {
93 noBelow = record.hasChildNodes() && record.isExpanded();
96 fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
97 if (y >= region.top && y < (region.top + fragment)) {
100 else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
108 isValidDropPoint : function(node, position, dragZone, e, data) {
109 if (!node || !data.item) {
113 var view = this.view,
114 targetNode = view.getRecord(node),
115 draggedRecords = data.records,
116 dataLength = draggedRecords.length,
117 ln = draggedRecords.length,
120 // No drop position, or dragged records: invalid drop point
121 if (!(targetNode && position && dataLength)) {
125 // If the targetNode is within the folder we are dragging
126 for (i = 0; i < ln; i++) {
127 record = draggedRecords[i];
128 if (record.isNode && record.contains(targetNode)) {
133 // Respect the allowDrop field on Tree nodes
134 if (position === 'append' && targetNode.get('allowDrop') === false) {
137 else if (position != 'append' && targetNode.parentNode.get('allowDrop') === false) {
141 // If the target record is in the dragged dataset, then invalid drop
142 if (Ext.Array.contains(draggedRecords, targetNode)) {
146 // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
147 // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
151 onNodeOver : function(node, dragZone, e, data) {
152 var position = this.getPosition(e, node),
153 returnCls = this.dropNotAllowed,
155 targetNode = view.getRecord(node),
156 indicator = this.getIndicator(),
160 // auto node expand check
162 if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
163 this.queueExpand(targetNode);
167 if (this.isValidDropPoint(node, position, dragZone, e, data)) {
169 this.currentPosition = position;
170 this.overRecord = targetNode;
172 indicator.setWidth(Ext.fly(node).getWidth());
173 indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
176 * In the code below we show the proxy again. The reason for doing this is showing the indicator will
177 * call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always
178 * want the proxy to be above, so calling show on the proxy will call toFront and bring it forward.
180 if (position == 'before') {
181 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
182 indicator.showAt(0, indicatorY);
183 dragZone.proxy.show();
184 } else if (position == 'after') {
185 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
186 indicatorY += Ext.fly(node).getHeight();
187 indicator.showAt(0, indicatorY);
188 dragZone.proxy.show();
190 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
191 // @TODO: set a class on the parent folder node to be able to style it
198 this.currentCls = returnCls;
202 onContainerOver : function(dd, e, data) {
203 return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
206 notifyOut: function() {
207 this.callParent(arguments);
211 handleNodeDrop : function(data, targetNode, position) {
214 parentNode = targetNode.parentNode,
215 store = view.getStore(),
218 insertionMethod, argList,
223 // If the copy flag is set, create a copy of the Models with the same IDs
225 records = data.records;
227 for (i = 0, len = records.length; i < len; i++) {
228 data.records.push(Ext.apply({}, records[i].data));
232 // Cancel any pending expand operation
235 // Grab a reference to the correct node insertion method.
236 // Create an arg list array intended for the apply method of the
237 // chosen node insertion method.
238 // Ensure the target object for the method is referenced by 'targetNode'
239 if (position == 'before') {
240 insertionMethod = parentNode.insertBefore;
241 argList = [null, targetNode];
242 targetNode = parentNode;
244 else if (position == 'after') {
245 if (targetNode.nextSibling) {
246 insertionMethod = parentNode.insertBefore;
247 argList = [null, targetNode.nextSibling];
250 insertionMethod = parentNode.appendChild;
253 targetNode = parentNode;
256 if (!targetNode.isExpanded()) {
257 needTargetExpand = true;
259 insertionMethod = targetNode.appendChild;
263 // A function to transfer the data into the destination tree
264 transferData = function() {
266 for (i = 0, len = data.records.length; i < len; i++) {
267 argList[0] = data.records[i];
268 node = insertionMethod.apply(targetNode, argList);
270 if (Ext.enableFx && me.dropHighlight) {
271 recordDomNodes.push(view.getNode(node));
275 // Kick off highlights after everything's been inserted, so they are
276 // more in sync without insertion/render overhead.
277 if (Ext.enableFx && me.dropHighlight) {
278 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read
279 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
280 Ext.Array.forEach(recordDomNodes, function(n) {
282 Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
288 // If dropping right on an unexpanded node, transfer the data after it is expanded.
289 if (needTargetExpand) {
290 targetNode.expand(false, transferData);
292 // Otherwise, call the data transfer function immediately