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