1
+ ( function ( $ ) {
2
+ /*
3
+ * Simulate drag of a JQuery UI sortable list
4
+ * Repository: https://github.com/mattheworiordan/jquery.simulate.drag-sortable.js
5
+ * Author: http://mattheworiordan.com
6
+ *
7
+ * options are:
8
+ * - move: move item up (positive) or down (negative) by Integer amount
9
+ * - dropOn: move item to a new linked list, move option now represents position in the new list (zero indexed)
10
+ * - handle: selector for the draggable handle element (optional)
11
+ * - listItem: selector to limit which sibling items can be used for reordering
12
+ * - placeHolder: if a placeholder is used during dragging, we need to consider it's height
13
+ * - tolerance: (optional) number of pixels to overlap by instead of the default 50% of the element height
14
+ *
15
+ */
16
+ $ . fn . simulateDragSortable = function ( options ) {
17
+ // build main options before element iteration
18
+ var opts = $ . extend ( { } , $ . fn . simulateDragSortable . defaults , options ) ;
19
+
20
+ applyDrag = function ( options ) {
21
+ // allow for a drag handle if item is not draggable
22
+ var that = this ,
23
+ options = options || opts , // default to plugin opts unless options explicitly provided
24
+ handle = options . handle ? $ ( this ) . find ( options . handle ) [ 0 ] : $ ( this ) [ 0 ] ,
25
+ listItem = options . listItem ,
26
+ placeHolder = options . placeHolder ,
27
+ sibling = $ ( this ) ,
28
+ moveCounter = Math . floor ( options . move ) ,
29
+ direction = moveCounter > 0 ? 'down' : 'up' ,
30
+ moveVerticalAmount = 0 ,
31
+ initialVerticalPosition = 0 ,
32
+ extraDrag = ! isNaN ( parseInt ( options . tolerance , 10 ) ) ? function ( ) { return Number ( options . tolerance ) ; } : function ( obj ) { return ( $ ( obj ) . outerHeight ( ) / 2 ) + 5 ; } ,
33
+ dragPastBy = 0 , // represents the additional amount one drags past an element and bounce back
34
+ dropOn = options . dropOn ? $ ( options . dropOn ) : false ,
35
+ center = findCenter ( handle ) ,
36
+ x = Math . floor ( center . x ) ,
37
+ y = Math . floor ( center . y ) ,
38
+ mouseUpAfter = ( opts . debug ? 2500 : 10 ) ;
39
+
40
+ if ( dropOn ) {
41
+ if ( dropOn . length === 0 ) {
42
+ if ( console && console . log ) { console . log ( 'simulate.drag-sortable.js ERROR: Drop on target could not be found' ) ; console . log ( options . dropOn ) ; }
43
+ return ;
44
+ }
45
+ sibling = dropOn . find ( '>*:last' ) ;
46
+ moveCounter = - ( dropOn . find ( '>*' ) . length + 1 ) + ( moveCounter + 1 ) ; // calculate length of list after this move, use moveCounter as a positive index position in list to reverse back up
47
+ if ( dropOn . offset ( ) . top - $ ( this ) . offset ( ) . top < 0 ) {
48
+ // moving to a list above this list, so move to just above top of last item (tried moving to top but JQuery UI wouldn't bite)
49
+ initialVerticalPosition = sibling . offset ( ) . top - $ ( this ) . offset ( ) . top - extraDrag ( this ) ;
50
+ } else {
51
+ // moving to a list below this list, so move to bottom and work up (JQuery UI does not trigger new list below unless you move past top item first)
52
+ initialVerticalPosition = sibling . offset ( ) . top - $ ( this ) . offset ( ) . top - $ ( this ) . height ( ) ;
53
+ }
54
+ } else if ( moveCounter === 0 ) {
55
+ if ( console && console . log ) { console . log ( 'simulate.drag-sortable.js WARNING: Drag with move set to zero has no effect' ) ; }
56
+ return ;
57
+ } else {
58
+ while ( moveCounter !== 0 ) {
59
+ if ( direction === 'down' ) {
60
+ if ( sibling . next ( listItem ) . length ) {
61
+ sibling = sibling . next ( listItem ) ;
62
+ moveVerticalAmount += sibling . outerHeight ( ) ;
63
+ }
64
+ moveCounter -= 1 ;
65
+ } else {
66
+ if ( sibling . prev ( listItem ) . length ) {
67
+ sibling = sibling . prev ( listItem ) ;
68
+ moveVerticalAmount -= sibling . outerHeight ( ) ;
69
+ }
70
+ moveCounter += 1 ;
71
+ }
72
+ }
73
+ }
74
+
75
+ dispatchEvent ( handle , 'mousedown' , createEvent ( 'mousedown' , handle , { clientX : x , clientY : y } ) ) ;
76
+ // simulate drag start
77
+ dispatchEvent ( document , 'mousemove' , createEvent ( 'mousemove' , document , { clientX : x + 1 , clientY : y + 1 } ) ) ;
78
+
79
+ if ( dropOn ) {
80
+ // jump to top or bottom of new list but do it in increments so that JQuery UI registers the drag events
81
+ slideUpTo ( x , y , initialVerticalPosition ) ;
82
+
83
+ // reset y position to top or bottom of list and move from there
84
+ y += initialVerticalPosition ;
85
+
86
+ // now call regular shift/down in a list
87
+ options = jQuery . extend ( options , { move : moveCounter } ) ;
88
+ delete options . dropOn ;
89
+
90
+ // add some delays to allow JQuery UI to catch up
91
+ setTimeout ( function ( ) {
92
+ dispatchEvent ( document , 'mousemove' , createEvent ( 'mousemove' , document , { clientX : x , clientY : y } ) ) ;
93
+ } , 5 ) ;
94
+ setTimeout ( function ( ) {
95
+ dispatchEvent ( handle , 'mouseup' , createEvent ( 'mouseup' , handle , { clientX : x , clientY : y } ) ) ;
96
+ setTimeout ( function ( ) {
97
+ if ( options . move ) {
98
+ applyDrag . call ( that , options ) ;
99
+ }
100
+ } , 5 ) ;
101
+ } , mouseUpAfter ) ;
102
+
103
+ // stop execution as applyDrag has been called again
104
+ return ;
105
+ }
106
+
107
+ // Sortable is using a fixed height placeholder meaning items jump up and down as you drag variable height items into fixed height placeholder
108
+ placeHolder = placeHolder && $ ( this ) . parent ( ) . find ( placeHolder ) ;
109
+
110
+ if ( ! placeHolder && ( direction === 'down' ) ) {
111
+ // need to move at least as far as this item and or the last sibling
112
+ if ( $ ( this ) . outerHeight ( ) > $ ( sibling ) . outerHeight ( ) ) {
113
+ moveVerticalAmount += $ ( this ) . outerHeight ( ) - $ ( sibling ) . outerHeight ( ) ;
114
+ }
115
+ moveVerticalAmount += extraDrag ( sibling ) ;
116
+ dragPastBy += extraDrag ( sibling ) ;
117
+ } else if ( direction === 'up' ) {
118
+ // move a little extra to ensure item clips into next position
119
+ moveVerticalAmount -= Math . max ( extraDrag ( this ) , 5 ) ;
120
+ } else if ( direction === 'down' ) {
121
+ // moving down with a place holder
122
+ if ( placeHolder . height ( ) < $ ( this ) . height ( ) ) {
123
+ moveVerticalAmount += Math . max ( placeHolder . height ( ) , 5 ) ;
124
+ } else {
125
+ moveVerticalAmount += extraDrag ( sibling ) ;
126
+ }
127
+ }
128
+
129
+ if ( sibling [ 0 ] !== $ ( this ) [ 0 ] ) {
130
+ // step through so that the UI controller can determine when to show the placeHolder
131
+ slideUpTo ( x , y , moveVerticalAmount , dragPastBy ) ;
132
+ } else {
133
+ if ( window . console ) {
134
+ console . log ( 'simulate.drag-sortable.js WARNING: Could not move as at top or bottom already' ) ;
135
+ }
136
+ }
137
+
138
+ setTimeout ( function ( ) {
139
+ dispatchEvent ( document , 'mousemove' , createEvent ( 'mousemove' , document , { clientX : x , clientY : y + moveVerticalAmount } ) ) ;
140
+ } , 5 ) ;
141
+ setTimeout ( function ( ) {
142
+ dispatchEvent ( handle , 'mouseup' , createEvent ( 'mouseup' , handle , { clientX : x , clientY : y + moveVerticalAmount } ) ) ;
143
+ } , mouseUpAfter ) ;
144
+ } ;
145
+
146
+ // iterate and move each matched element
147
+ return this . each ( applyDrag ) ;
148
+ } ;
149
+
150
+ // fire mouse events, go half way, then the next half, so small mouse movements near target and big at the start
151
+ function slideUpTo ( x , y , targetOffset , goPastBy ) {
152
+ var moveBy , offset ;
153
+
154
+ if ( ! goPastBy ) { goPastBy = 0 ; }
155
+ if ( ( targetOffset < 0 ) && ( goPastBy > 0 ) ) { goPastBy = - goPastBy ; } // ensure go past is in the direction as often passed in from object height so always positive
156
+
157
+ // go forwards including goPastBy
158
+ for ( offset = 0 ; Math . abs ( offset ) + 1 < Math . abs ( targetOffset + goPastBy ) ; offset += ( ( targetOffset + goPastBy - offset ) / 2 ) ) {
159
+ dispatchEvent ( document , 'mousemove' , createEvent ( 'mousemove' , document , { clientX : x , clientY : y + Math . ceil ( offset ) } ) ) ;
160
+ }
161
+ offset = targetOffset + goPastBy ;
162
+ dispatchEvent ( document , 'mousemove' , createEvent ( 'mousemove' , document , { clientX : x , clientY : y + offset } ) ) ;
163
+
164
+ // now bounce back
165
+ for ( ; Math . abs ( offset ) - 1 >= Math . abs ( targetOffset ) ; offset += ( ( targetOffset - offset ) / 2 ) ) {
166
+ dispatchEvent ( document , 'mousemove' , createEvent ( 'mousemove' , document , { clientX : x , clientY : y + Math . ceil ( offset ) } ) ) ;
167
+ }
168
+ dispatchEvent ( document , 'mousemove' , createEvent ( 'mousemove' , document , { clientX : x , clientY : y + targetOffset } ) ) ;
169
+ }
170
+
171
+ function createEvent ( type , target , options ) {
172
+ var evt ;
173
+ var e = $ . extend ( {
174
+ target : target ,
175
+ preventDefault : function ( ) { } ,
176
+ stopImmediatePropagation : function ( ) { } ,
177
+ stopPropagation : function ( ) { } ,
178
+ isPropagationStopped : function ( ) { return true ; } ,
179
+ isImmediatePropagationStopped : function ( ) { return true ; } ,
180
+ isDefaultPrevented : function ( ) { return true ; } ,
181
+ bubbles : true ,
182
+ cancelable : ( type != "mousemove" ) ,
183
+ view : window ,
184
+ detail : 0 ,
185
+ screenX : 0 ,
186
+ screenY : 0 ,
187
+ clientX : 0 ,
188
+ clientY : 0 ,
189
+ ctrlKey : false ,
190
+ altKey : false ,
191
+ shiftKey : false ,
192
+ metaKey : false ,
193
+ button : 0 ,
194
+ relatedTarget : undefined
195
+ } , options || { } ) ;
196
+
197
+ if ( $ . isFunction ( document . createEvent ) ) {
198
+ evt = document . createEvent ( "MouseEvents" ) ;
199
+ evt . initMouseEvent ( type , e . bubbles , e . cancelable , e . view , e . detail ,
200
+ e . screenX , e . screenY , e . clientX , e . clientY ,
201
+ e . ctrlKey , e . altKey , e . shiftKey , e . metaKey ,
202
+ e . button , e . relatedTarget || document . body . parentNode ) ;
203
+ } else if ( document . createEventObject ) {
204
+ evt = document . createEventObject ( ) ;
205
+ $ . extend ( evt , e ) ;
206
+ evt . button = { 0 :1 , 1 :4 , 2 :2 } [ evt . button ] || evt . button ;
207
+ }
208
+ return evt ;
209
+ }
210
+
211
+ function dispatchEvent ( el , type , evt ) {
212
+ if ( el . dispatchEvent ) {
213
+ el . dispatchEvent ( evt ) ;
214
+ } else if ( el . fireEvent ) {
215
+ el . fireEvent ( 'on' + type , evt ) ;
216
+ }
217
+ return evt ;
218
+ }
219
+
220
+ function findCenter ( el ) {
221
+ var elm = $ ( el ) ,
222
+ o = elm . offset ( ) ;
223
+ return {
224
+ x : o . left + elm . outerWidth ( ) / 2 ,
225
+ y : o . top + elm . outerHeight ( ) / 2
226
+ } ;
227
+ }
228
+
229
+ //
230
+ // plugin defaults
231
+ //
232
+ $ . fn . simulateDragSortable . defaults = {
233
+ move : 0
234
+ } ;
235
+ } ) ( jQuery ) ;
0 commit comments