Skip to content

Commit 1a9dab6

Browse files
committed
#9 added slide dnd spec
1 parent eba2fc4 commit 1a9dab6

File tree

4 files changed

+365
-9
lines changed

4 files changed

+365
-9
lines changed

test/components/appTest.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/*eslint-env node, mocha */
21
/*global expect */
32
/*eslint no-console: 0*/
43

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
import { appWrapper } from 'helpers/fullRenderHelper'
2+
import 'helpers/jquery.simulate.drag-sortable.js'
23

34
describe('slides > well', () => {
45
it('should add a slide when clicking top insert slide button', () => {
5-
expect(appWrapper.state().deck.getActiveSlide().components.length).to.equal(
6-
1
7-
)
86
expect(appWrapper.state().deck.components.length).to.equal(4)
97
appWrapper
10-
.find(
11-
'.sp-well-slide-creator > .btn-success'
12-
)
13-
.first().simulate('click')
8+
.find('.sp-well-slide-creator > .btn-success')
9+
.first()
10+
.simulate('click')
1411
expect(appWrapper.state().deck.components.length).to.equal(5)
1512
})
13+
it('should move the 1st slide to 3rd when performing DnD', done => {
14+
expect(appWrapper.state().deck.components[2].components.length).to.equal(0)
15+
$('.sp-well-slide-wrapper')
16+
.first()
17+
.simulateDragSortable({ move: 3 })
18+
setTimeout(() => {
19+
expect(
20+
appWrapper.state().deck.components[2].components[0].text
21+
).to.contain('Welcome to ShowPreper!')
22+
done()
23+
}, 10)
24+
})
1625
})

test/helpers/fullRenderHelper.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import App from 'components/app'
33
import React from 'react'
44
export let appWrapper
55
beforeEach(() => {
6-
appWrapper = mount(<App />, { attachTo: app })
6+
appWrapper = mount(<App />, { attachTo: document.getElementById('app') })
77
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
/*eslint no-console: 0*/
2+
;(function($) {
3+
/*
4+
* Simulate drag of a JQuery UI sortable list
5+
* Repository: https://github.com/mattheworiordan/jquery.simulate.drag-sortable.js
6+
* Author: http://mattheworiordan.com
7+
*
8+
* options are:
9+
* - move: move item up (positive) or down (negative) by Integer amount
10+
* - dropOn: move item to a new linked list, move option now represents position in the new list (zero indexed)
11+
* - handle: selector for the draggable handle element (optional)
12+
* - listItem: selector to limit which sibling items can be used for reordering
13+
* - placeHolder: if a placeholder is used during dragging, we need to consider it's height
14+
* - tolerance: (optional) number of pixels to overlap by instead of the default 50% of the element height
15+
*
16+
*/
17+
$.fn.simulateDragSortable = function(options) {
18+
// build main options before element iteration
19+
var opts = $.extend({}, $.fn.simulateDragSortable.defaults, options)
20+
21+
let applyDrag = function(options) {
22+
// allow for a drag handle if item is not draggable
23+
var that = this,
24+
options = options || opts, // default to plugin opts unless options explicitly provided
25+
handle = options.handle ? $(this).find(options.handle)[0] : $(this)[0],
26+
listItem = options.listItem,
27+
placeHolder = options.placeHolder,
28+
sibling = $(this),
29+
moveCounter = Math.floor(options.move),
30+
direction = moveCounter > 0 ? 'down' : 'up',
31+
moveVerticalAmount = 0,
32+
initialVerticalPosition = 0,
33+
extraDrag = !isNaN(parseInt(options.tolerance, 10))
34+
? function() {
35+
return Number(options.tolerance)
36+
}
37+
: function(obj) {
38+
return $(obj).outerHeight() / 2 + 5
39+
},
40+
dragPastBy = 0, // represents the additional amount one drags past an element and bounce back
41+
dropOn = options.dropOn ? $(options.dropOn) : false,
42+
center = findCenter(handle),
43+
x = Math.floor(center.x),
44+
y = Math.floor(center.y),
45+
mouseUpAfter = opts.debug ? 2500 : 10
46+
47+
if (dropOn) {
48+
if (dropOn.length === 0) {
49+
if (console && console.log) {
50+
console.log(
51+
'simulate.drag-sortable.js ERROR: Drop on target could not be found'
52+
)
53+
console.log(options.dropOn)
54+
}
55+
return
56+
}
57+
sibling = dropOn.find('>*:last')
58+
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
59+
if (dropOn.offset().top - $(this).offset().top < 0) {
60+
// 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)
61+
initialVerticalPosition =
62+
sibling.offset().top - $(this).offset().top - extraDrag(this)
63+
} else {
64+
// 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)
65+
initialVerticalPosition =
66+
sibling.offset().top - $(this).offset().top - $(this).height()
67+
}
68+
} else if (moveCounter === 0) {
69+
if (console && console.log) {
70+
console.log(
71+
'simulate.drag-sortable.js WARNING: Drag with move set to zero has no effect'
72+
)
73+
}
74+
return
75+
} else {
76+
while (moveCounter !== 0) {
77+
if (direction === 'down') {
78+
if (sibling.next(listItem).length) {
79+
sibling = sibling.next(listItem)
80+
moveVerticalAmount += sibling.outerHeight()
81+
}
82+
moveCounter -= 1
83+
} else {
84+
if (sibling.prev(listItem).length) {
85+
sibling = sibling.prev(listItem)
86+
moveVerticalAmount -= sibling.outerHeight()
87+
}
88+
moveCounter += 1
89+
}
90+
}
91+
}
92+
93+
dispatchEvent(
94+
handle,
95+
'mousedown',
96+
createEvent('mousedown', handle, { clientX: x, clientY: y })
97+
)
98+
// simulate drag start
99+
dispatchEvent(
100+
document,
101+
'mousemove',
102+
createEvent('mousemove', document, { clientX: x + 1, clientY: y + 1 })
103+
)
104+
105+
if (dropOn) {
106+
// jump to top or bottom of new list but do it in increments so that JQuery UI registers the drag events
107+
slideUpTo(x, y, initialVerticalPosition)
108+
109+
// reset y position to top or bottom of list and move from there
110+
y += initialVerticalPosition
111+
112+
// now call regular shift/down in a list
113+
options = jQuery.extend(options, { move: moveCounter })
114+
delete options.dropOn
115+
116+
// add some delays to allow JQuery UI to catch up
117+
setTimeout(function() {
118+
dispatchEvent(
119+
document,
120+
'mousemove',
121+
createEvent('mousemove', document, { clientX: x, clientY: y })
122+
)
123+
}, 5)
124+
setTimeout(function() {
125+
dispatchEvent(
126+
handle,
127+
'mouseup',
128+
createEvent('mouseup', handle, { clientX: x, clientY: y })
129+
)
130+
setTimeout(function() {
131+
if (options.move) {
132+
applyDrag.call(that, options)
133+
}
134+
}, 5)
135+
}, mouseUpAfter)
136+
137+
// stop execution as applyDrag has been called again
138+
return
139+
}
140+
141+
// Sortable is using a fixed height placeholder meaning items jump up and down as you drag variable height items into fixed height placeholder
142+
placeHolder =
143+
placeHolder &&
144+
$(this)
145+
.parent()
146+
.find(placeHolder)
147+
148+
if (!placeHolder && direction === 'down') {
149+
// need to move at least as far as this item and or the last sibling
150+
if ($(this).outerHeight() > $(sibling).outerHeight()) {
151+
moveVerticalAmount += $(this).outerHeight() - $(sibling).outerHeight()
152+
}
153+
moveVerticalAmount += extraDrag(sibling)
154+
dragPastBy += extraDrag(sibling)
155+
} else if (direction === 'up') {
156+
// move a little extra to ensure item clips into next position
157+
moveVerticalAmount -= Math.max(extraDrag(this), 5)
158+
} else if (direction === 'down') {
159+
// moving down with a place holder
160+
if (placeHolder.height() < $(this).height()) {
161+
moveVerticalAmount += Math.max(placeHolder.height(), 5)
162+
} else {
163+
moveVerticalAmount += extraDrag(sibling)
164+
}
165+
}
166+
167+
if (sibling[0] !== $(this)[0]) {
168+
// step through so that the UI controller can determine when to show the placeHolder
169+
slideUpTo(x, y, moveVerticalAmount, dragPastBy)
170+
} else {
171+
if (window.console) {
172+
console.log(
173+
'simulate.drag-sortable.js WARNING: Could not move as at top or bottom already'
174+
)
175+
}
176+
}
177+
178+
setTimeout(function() {
179+
dispatchEvent(
180+
document,
181+
'mousemove',
182+
createEvent('mousemove', document, {
183+
clientX: x,
184+
clientY: y + moveVerticalAmount
185+
})
186+
)
187+
}, 5)
188+
setTimeout(function() {
189+
dispatchEvent(
190+
handle,
191+
'mouseup',
192+
createEvent('mouseup', handle, {
193+
clientX: x,
194+
clientY: y + moveVerticalAmount
195+
})
196+
)
197+
}, mouseUpAfter)
198+
}
199+
200+
// iterate and move each matched element
201+
return this.each(applyDrag)
202+
}
203+
204+
// fire mouse events, go half way, then the next half, so small mouse movements near target and big at the start
205+
function slideUpTo(x, y, targetOffset, goPastBy) {
206+
var offset
207+
208+
if (!goPastBy) {
209+
goPastBy = 0
210+
}
211+
if (targetOffset < 0 && goPastBy > 0) {
212+
goPastBy = -goPastBy
213+
} // ensure go past is in the direction as often passed in from object height so always positive
214+
215+
// go forwards including goPastBy
216+
for (
217+
offset = 0;
218+
Math.abs(offset) + 1 < Math.abs(targetOffset + goPastBy);
219+
offset += (targetOffset + goPastBy - offset) / 2
220+
) {
221+
dispatchEvent(
222+
document,
223+
'mousemove',
224+
createEvent('mousemove', document, {
225+
clientX: x,
226+
clientY: y + Math.ceil(offset)
227+
})
228+
)
229+
}
230+
offset = targetOffset + goPastBy
231+
dispatchEvent(
232+
document,
233+
'mousemove',
234+
createEvent('mousemove', document, { clientX: x, clientY: y + offset })
235+
)
236+
237+
// now bounce back
238+
for (
239+
;
240+
Math.abs(offset) - 1 >= Math.abs(targetOffset);
241+
offset += (targetOffset - offset) / 2
242+
) {
243+
dispatchEvent(
244+
document,
245+
'mousemove',
246+
createEvent('mousemove', document, {
247+
clientX: x,
248+
clientY: y + Math.ceil(offset)
249+
})
250+
)
251+
}
252+
dispatchEvent(
253+
document,
254+
'mousemove',
255+
createEvent('mousemove', document, {
256+
clientX: x,
257+
clientY: y + targetOffset
258+
})
259+
)
260+
}
261+
262+
function createEvent(type, target, options) {
263+
var evt
264+
var e = $.extend(
265+
{
266+
target: target,
267+
preventDefault: function() {},
268+
stopImmediatePropagation: function() {},
269+
stopPropagation: function() {},
270+
isPropagationStopped: function() {
271+
return true
272+
},
273+
isImmediatePropagationStopped: function() {
274+
return true
275+
},
276+
isDefaultPrevented: function() {
277+
return true
278+
},
279+
bubbles: true,
280+
cancelable: type != 'mousemove',
281+
view: window,
282+
detail: 0,
283+
screenX: 0,
284+
screenY: 0,
285+
clientX: 0,
286+
clientY: 0,
287+
ctrlKey: false,
288+
altKey: false,
289+
shiftKey: false,
290+
metaKey: false,
291+
button: 0,
292+
relatedTarget: undefined
293+
},
294+
options || {}
295+
)
296+
297+
if ($.isFunction(document.createEvent)) {
298+
evt = document.createEvent('MouseEvents')
299+
evt.initMouseEvent(
300+
type,
301+
e.bubbles,
302+
e.cancelable,
303+
e.view,
304+
e.detail,
305+
e.screenX,
306+
e.screenY,
307+
e.clientX,
308+
e.clientY,
309+
e.ctrlKey,
310+
e.altKey,
311+
e.shiftKey,
312+
e.metaKey,
313+
e.button,
314+
e.relatedTarget || document.body.parentNode
315+
)
316+
} else if (document.createEventObject) {
317+
evt = document.createEventObject()
318+
$.extend(evt, e)
319+
evt.button = { 0: 1, 1: 4, 2: 2 }[evt.button] || evt.button
320+
}
321+
return evt
322+
}
323+
324+
function dispatchEvent(el, type, evt) {
325+
if (el.dispatchEvent) {
326+
el.dispatchEvent(evt)
327+
} else if (el.fireEvent) {
328+
el.fireEvent('on' + type, evt)
329+
}
330+
return evt
331+
}
332+
333+
function findCenter(el) {
334+
var elm = $(el),
335+
o = elm.offset()
336+
return {
337+
x: o.left + elm.outerWidth() / 2,
338+
y: o.top + elm.outerHeight() / 2
339+
}
340+
}
341+
342+
//
343+
// plugin defaults
344+
//
345+
$.fn.simulateDragSortable.defaults = {
346+
move: 0
347+
}
348+
})(jQuery)

0 commit comments

Comments
 (0)