]> git.proxmox.com Git - extjs.git/blame - extjs/packages/ux/classic/src/BoxReorderer.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / ux / classic / src / BoxReorderer.js
CommitLineData
6527f429
DM
1/**\r
2 * Base class from Ext.ux.TabReorderer.\r
3 */\r
4Ext.define('Ext.ux.BoxReorderer', {\r
5 requires: [\r
6 'Ext.dd.DD'\r
7 ],\r
8\r
9 mixins: {\r
10 observable: 'Ext.util.Observable'\r
11 },\r
12\r
13 /**\r
14 * @cfg {String} itemSelector\r
15 * A {@link Ext.DomQuery DomQuery} selector which identifies the encapsulating elements of child\r
16 * Components which participate in reordering.\r
17 */\r
18 itemSelector: '.x-box-item',\r
19\r
20 /**\r
21 * @cfg {Mixed} animate\r
22 * If truthy, child reordering is animated so that moved boxes slide smoothly into position.\r
23 * If this option is numeric, it is used as the animation duration in milliseconds.\r
24 */\r
25 animate: 100,\r
26\r
27 /**\r
28 * @event StartDrag\r
29 * Fires when dragging of a child Component begins.\r
30 * @param {Ext.ux.BoxReorderer} this\r
31 * @param {Ext.container.Container} container The owning Container\r
32 * @param {Ext.Component} dragCmp The Component being dragged\r
33 * @param {Number} idx The start index of the Component being dragged.\r
34 */\r
35\r
36 /**\r
37 * @event Drag\r
38 * Fires during dragging of a child Component.\r
39 * @param {Ext.ux.BoxReorderer} this\r
40 * @param {Ext.container.Container} container The owning Container\r
41 * @param {Ext.Component} dragCmp The Component being dragged\r
42 * @param {Number} startIdx The index position from which the Component was initially dragged.\r
43 * @param {Number} idx The current closest index to which the Component would drop.\r
44 */\r
45\r
46 /**\r
47 * @event ChangeIndex\r
48 * Fires when dragging of a child Component causes its drop index to change.\r
49 * @param {Ext.ux.BoxReorderer} this\r
50 * @param {Ext.container.Container} container The owning Container\r
51 * @param {Ext.Component} dragCmp The Component being dragged\r
52 * @param {Number} startIdx The index position from which the Component was initially dragged.\r
53 * @param {Number} idx The current closest index to which the Component would drop.\r
54 */\r
55\r
56 /**\r
57 * @event Drop\r
58 * Fires when a child Component is dropped at a new index position.\r
59 * @param {Ext.ux.BoxReorderer} this\r
60 * @param {Ext.container.Container} container The owning Container\r
61 * @param {Ext.Component} dragCmp The Component being dropped\r
62 * @param {Number} startIdx The index position from which the Component was initially dragged.\r
63 * @param {Number} idx The index at which the Component is being dropped.\r
64 */\r
65\r
66 constructor: function() {\r
67 this.mixins.observable.constructor.apply(this, arguments);\r
68 },\r
69\r
70 init: function(container) {\r
71 var me = this;\r
72\r
73 me.container = container;\r
74\r
75 // Set our animatePolicy to animate the start position (ie x for HBox, y for VBox)\r
76 me.animatePolicy = {};\r
77 me.animatePolicy[container.getLayout().names.x] = true;\r
78\r
79\r
80\r
81 // Initialize the DD on first layout, when the innerCt has been created.\r
82 me.container.on({\r
83 scope: me,\r
84 boxready: me.onBoxReady,\r
85 beforedestroy: me.onContainerDestroy\r
86 });\r
87 },\r
88\r
89 /**\r
90 * @private\r
91 * Clear up on Container destroy\r
92 */\r
93 onContainerDestroy: function() {\r
94 var dd = this.dd;\r
95 if (dd) {\r
96 dd.unreg();\r
97 this.dd = null;\r
98 }\r
99 },\r
100\r
101 onBoxReady: function() {\r
102 var me = this,\r
103 layout = me.container.getLayout(),\r
104 names = layout.names,\r
105 dd;\r
106\r
107 // Create a DD instance. Poke the handlers in.\r
108 // TODO: Ext5's DD classes should apply config to themselves.\r
109 // TODO: Ext5's DD classes should not use init internally because it collides with use as a plugin\r
110 // TODO: Ext5's DD classes should be Observable.\r
111 // TODO: When all the above are trus, this plugin should extend the DD class.\r
112 dd = me.dd = new Ext.dd.DD(layout.innerCt, me.container.id + '-reorderer');\r
113 Ext.apply(dd, {\r
114 animate: me.animate,\r
115 reorderer: me,\r
116 container: me.container,\r
117 getDragCmp: me.getDragCmp,\r
118 clickValidator: Ext.Function.createInterceptor(dd.clickValidator, me.clickValidator, me, false),\r
119 onMouseDown: me.onMouseDown,\r
120 startDrag: me.startDrag,\r
121 onDrag: me.onDrag,\r
122 endDrag: me.endDrag,\r
123 getNewIndex: me.getNewIndex,\r
124 doSwap: me.doSwap,\r
125 findReorderable: me.findReorderable\r
126 });\r
127\r
128 // Decide which dimension we are measuring, and which measurement metric defines\r
129 // the *start* of the box depending upon orientation.\r
130 dd.dim = names.width;\r
131 dd.startAttr = names.beforeX;\r
132 dd.endAttr = names.afterX;\r
133 },\r
134\r
135 getDragCmp: function(e) {\r
136 return this.container.getChildByElement(e.getTarget(this.itemSelector, 10));\r
137 },\r
138\r
139 // check if the clicked component is reorderable\r
140 clickValidator: function(e) {\r
141 var cmp = this.getDragCmp(e);\r
142\r
143 // If cmp is null, this expression MUST be coerced to boolean so that createInterceptor is able to test it against false\r
144 return !!(cmp && cmp.reorderable !== false);\r
145 },\r
146\r
147 onMouseDown: function(e) {\r
148 var me = this,\r
149 container = me.container,\r
150 containerBox,\r
151 cmpEl,\r
152 cmpBox;\r
153\r
154 // Ascertain which child Component is being mousedowned\r
155 me.dragCmp = me.getDragCmp(e);\r
156 if (me.dragCmp) {\r
157 cmpEl = me.dragCmp.getEl();\r
158 me.startIndex = me.curIndex = container.items.indexOf(me.dragCmp);\r
159\r
160 // Start position of dragged Component\r
161 cmpBox = cmpEl.getBox();\r
162\r
163 // Last tracked start position\r
164 me.lastPos = cmpBox[me.startAttr];\r
165\r
166 // Calculate constraints depending upon orientation\r
167 // Calculate offset from mouse to dragEl position\r
168 containerBox = container.el.getBox();\r
169 if (me.dim === 'width') {\r
170 me.minX = containerBox.left;\r
171 me.maxX = containerBox.right - cmpBox.width;\r
172 me.minY = me.maxY = cmpBox.top;\r
173 me.deltaX = e.getX() - cmpBox.left;\r
174 } else {\r
175 me.minY = containerBox.top;\r
176 me.maxY = containerBox.bottom - cmpBox.height;\r
177 me.minX = me.maxX = cmpBox.left;\r
178 me.deltaY = e.getY() - cmpBox.top;\r
179 }\r
180 me.constrainY = me.constrainX = true;\r
181 }\r
182 },\r
183\r
184 startDrag: function() {\r
185 var me = this,\r
186 dragCmp = me.dragCmp;\r
187\r
188 if (dragCmp) {\r
189 // For the entire duration of dragging the *Element*, defeat any positioning and animation of the dragged *Component*\r
190 dragCmp.setPosition = Ext.emptyFn;\r
191 dragCmp.animate = false;\r
192\r
193 // Animate the BoxLayout just for the duration of the drag operation.\r
194 if (me.animate) {\r
195 me.container.getLayout().animatePolicy = me.reorderer.animatePolicy;\r
196 }\r
197 // We drag the Component element\r
198 me.dragElId = dragCmp.getEl().id;\r
199 me.reorderer.fireEvent('StartDrag', me, me.container, dragCmp, me.curIndex);\r
200 // Suspend events, and set the disabled flag so that the mousedown and mouseup events\r
201 // that are going to take place do not cause any other UI interaction.\r
202 dragCmp.suspendEvents();\r
203 dragCmp.disabled = true;\r
204 dragCmp.el.setStyle('zIndex', 100);\r
205 } else {\r
206 me.dragElId = null;\r
207 }\r
208 },\r
209\r
210 /**\r
211 * @private\r
212 * Find next or previous reorderable component index.\r
213 * @param {Number} newIndex The initial drop index.\r
214 * @return {Number} The index of the reorderable component.\r
215 */\r
216 findReorderable: function(newIndex) {\r
217 var me = this,\r
218 items = me.container.items,\r
219 newItem;\r
220\r
221 if (items.getAt(newIndex).reorderable === false) {\r
222 newItem = items.getAt(newIndex);\r
223 if (newIndex > me.startIndex) {\r
224 while(newItem && newItem.reorderable === false) {\r
225 newIndex++;\r
226 newItem = items.getAt(newIndex);\r
227 }\r
228 } else {\r
229 while(newItem && newItem.reorderable === false) {\r
230 newIndex--;\r
231 newItem = items.getAt(newIndex);\r
232 }\r
233 }\r
234 }\r
235\r
236 newIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);\r
237\r
238 if (items.getAt(newIndex).reorderable === false) {\r
239 return -1;\r
240 }\r
241 return newIndex;\r
242 },\r
243\r
244 /**\r
245 * @private\r
246 * Swap 2 components.\r
247 * @param {Number} newIndex The initial drop index.\r
248 */\r
249 doSwap: function(newIndex) {\r
250 var me = this,\r
251 items = me.container.items,\r
252 container = me.container,\r
253 wasRoot = me.container._isLayoutRoot,\r
254 orig, dest, tmpIndex;\r
255\r
256 newIndex = me.findReorderable(newIndex);\r
257\r
258 if (newIndex === -1) {\r
259 return;\r
260 }\r
261\r
262 me.reorderer.fireEvent('ChangeIndex', me, container, me.dragCmp, me.startIndex, newIndex);\r
263 orig = items.getAt(me.curIndex);\r
264 dest = items.getAt(newIndex);\r
265 items.remove(orig);\r
266 tmpIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);\r
267 items.insert(tmpIndex, orig);\r
268 items.remove(dest);\r
269 items.insert(me.curIndex, dest);\r
270\r
271 // Make the Box Container the topmost layout participant during the layout.\r
272 container._isLayoutRoot = true;\r
273 container.updateLayout();\r
274 container._isLayoutRoot = wasRoot;\r
275 me.curIndex = newIndex;\r
276 },\r
277\r
278 onDrag: function(e) {\r
279 var me = this,\r
280 newIndex;\r
281\r
282 newIndex = me.getNewIndex(e.getPoint());\r
283 if ((newIndex !== undefined)) {\r
284 me.reorderer.fireEvent('Drag', me, me.container, me.dragCmp, me.startIndex, me.curIndex);\r
285 me.doSwap(newIndex);\r
286 }\r
287\r
288 },\r
289\r
290 endDrag: function(e) {\r
291 if (e) {\r
292 e.stopEvent();\r
293 }\r
294 var me = this,\r
295 layout = me.container.getLayout(),\r
296 temp;\r
297\r
298 if (me.dragCmp) {\r
299 delete me.dragElId;\r
300\r
301 // Reinstate the Component's positioning method after mouseup, and allow the layout system to animate it.\r
302 delete me.dragCmp.setPosition;\r
303 me.dragCmp.animate = true;\r
304\r
305 // Ensure the lastBox is correct for the animation system to restore to when it creates the "from" animation frame\r
306 me.dragCmp.lastBox[layout.names.x] = me.dragCmp.getPosition(true)[layout.names.widthIndex];\r
307\r
308 // Make the Box Container the topmost layout participant during the layout.\r
309 me.container._isLayoutRoot = true;\r
310 me.container.updateLayout();\r
311 me.container._isLayoutRoot = undefined;\r
312\r
313 // Attempt to hook into the afteranimate event of the drag Component to call the cleanup\r
314 temp = Ext.fx.Manager.getFxQueue(me.dragCmp.el.id)[0];\r
315 if (temp) {\r
316 temp.on({\r
317 afteranimate: me.reorderer.afterBoxReflow,\r
318 scope: me\r
319 });\r
320 }\r
321 // If not animated, clean up after the mouseup has happened so that we don't click the thing being dragged\r
322 else {\r
323 Ext.Function.defer(me.reorderer.afterBoxReflow, 1, me);\r
324 }\r
325\r
326 if (me.animate) {\r
327 delete layout.animatePolicy;\r
328 }\r
329 me.reorderer.fireEvent('drop', me, me.container, me.dragCmp, me.startIndex, me.curIndex);\r
330 }\r
331 },\r
332\r
333 /**\r
334 * @private\r
335 * Called after the boxes have been reflowed after the drop.\r
336 * Re-enabled the dragged Component.\r
337 */\r
338 afterBoxReflow: function() {\r
339 var me = this;\r
340 me.dragCmp.el.setStyle('zIndex', '');\r
341 me.dragCmp.disabled = false;\r
342 me.dragCmp.resumeEvents();\r
343 },\r
344\r
345 /**\r
346 * @private\r
347 * Calculate drop index based upon the dragEl's position.\r
348 */\r
349 getNewIndex: function(pointerPos) {\r
350 var me = this,\r
351 dragEl = me.getDragEl(),\r
352 dragBox = Ext.fly(dragEl).getBox(),\r
353 targetEl,\r
354 targetBox,\r
355 targetMidpoint,\r
356 i = 0,\r
357 it = me.container.items.items,\r
358 ln = it.length,\r
359 lastPos = me.lastPos;\r
360\r
361 me.lastPos = dragBox[me.startAttr];\r
362\r
363 for (; i < ln; i++) {\r
364 targetEl = it[i].getEl();\r
365\r
366 // Only look for a drop point if this found item is an item according to our selector\r
367 if (targetEl.is(me.reorderer.itemSelector)) {\r
368 targetBox = targetEl.getBox();\r
369 targetMidpoint = targetBox[me.startAttr] + (targetBox[me.dim] >> 1);\r
370 if (i < me.curIndex) {\r
371 if ((dragBox[me.startAttr] < lastPos) && (dragBox[me.startAttr] < (targetMidpoint - 5))) {\r
372 return i;\r
373 }\r
374 } else if (i > me.curIndex) {\r
375 if ((dragBox[me.startAttr] > lastPos) && (dragBox[me.endAttr] > (targetMidpoint + 5))) {\r
376 return i;\r
377 }\r
378 }\r
379 }\r
380 }\r
381 }\r
382});\r