]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * Base class from Ext.ux.TabReorderer.\r | |
3 | */\r | |
4 | Ext.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 |