Skip to content

Commit

Permalink
#9 added slide dnd spec
Browse files Browse the repository at this point in the history
  • Loading branch information
abbr committed Oct 10, 2017
1 parent eba2fc4 commit 1a9dab6
Show file tree
Hide file tree
Showing 4 changed files with 365 additions and 9 deletions.
1 change: 0 additions & 1 deletion test/components/appTest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/*eslint-env node, mocha */
/*global expect */
/*eslint no-console: 0*/

Expand Down
23 changes: 16 additions & 7 deletions test/components/slides/well/indexTest.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { appWrapper } from 'helpers/fullRenderHelper'
import 'helpers/jquery.simulate.drag-sortable.js'

describe('slides > well', () => {
it('should add a slide when clicking top insert slide button', () => {
expect(appWrapper.state().deck.getActiveSlide().components.length).to.equal(
1
)
expect(appWrapper.state().deck.components.length).to.equal(4)
appWrapper
.find(
'.sp-well-slide-creator > .btn-success'
)
.first().simulate('click')
.find('.sp-well-slide-creator > .btn-success')
.first()
.simulate('click')
expect(appWrapper.state().deck.components.length).to.equal(5)
})
it('should move the 1st slide to 3rd when performing DnD', done => {
expect(appWrapper.state().deck.components[2].components.length).to.equal(0)
$('.sp-well-slide-wrapper')
.first()
.simulateDragSortable({ move: 3 })
setTimeout(() => {
expect(
appWrapper.state().deck.components[2].components[0].text
).to.contain('Welcome to ShowPreper!')
done()
}, 10)
})
})
2 changes: 1 addition & 1 deletion test/helpers/fullRenderHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import App from 'components/app'
import React from 'react'
export let appWrapper
beforeEach(() => {
appWrapper = mount(<App />, { attachTo: app })
appWrapper = mount(<App />, { attachTo: document.getElementById('app') })
})
348 changes: 348 additions & 0 deletions test/helpers/jquery.simulate.drag-sortable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
/*eslint no-console: 0*/
;(function($) {
/*
* Simulate drag of a JQuery UI sortable list
* Repository: https://github.com/mattheworiordan/jquery.simulate.drag-sortable.js
* Author: http://mattheworiordan.com
*
* options are:
* - move: move item up (positive) or down (negative) by Integer amount
* - dropOn: move item to a new linked list, move option now represents position in the new list (zero indexed)
* - handle: selector for the draggable handle element (optional)
* - listItem: selector to limit which sibling items can be used for reordering
* - placeHolder: if a placeholder is used during dragging, we need to consider it's height
* - tolerance: (optional) number of pixels to overlap by instead of the default 50% of the element height
*
*/
$.fn.simulateDragSortable = function(options) {
// build main options before element iteration
var opts = $.extend({}, $.fn.simulateDragSortable.defaults, options)

let applyDrag = function(options) {
// allow for a drag handle if item is not draggable
var that = this,
options = options || opts, // default to plugin opts unless options explicitly provided
handle = options.handle ? $(this).find(options.handle)[0] : $(this)[0],
listItem = options.listItem,
placeHolder = options.placeHolder,
sibling = $(this),
moveCounter = Math.floor(options.move),
direction = moveCounter > 0 ? 'down' : 'up',
moveVerticalAmount = 0,
initialVerticalPosition = 0,
extraDrag = !isNaN(parseInt(options.tolerance, 10))
? function() {
return Number(options.tolerance)
}
: function(obj) {
return $(obj).outerHeight() / 2 + 5
},
dragPastBy = 0, // represents the additional amount one drags past an element and bounce back
dropOn = options.dropOn ? $(options.dropOn) : false,
center = findCenter(handle),
x = Math.floor(center.x),
y = Math.floor(center.y),
mouseUpAfter = opts.debug ? 2500 : 10

if (dropOn) {
if (dropOn.length === 0) {
if (console && console.log) {
console.log(
'simulate.drag-sortable.js ERROR: Drop on target could not be found'
)
console.log(options.dropOn)
}
return
}
sibling = dropOn.find('>*:last')
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
if (dropOn.offset().top - $(this).offset().top < 0) {
// 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)
initialVerticalPosition =
sibling.offset().top - $(this).offset().top - extraDrag(this)
} else {
// 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)
initialVerticalPosition =
sibling.offset().top - $(this).offset().top - $(this).height()
}
} else if (moveCounter === 0) {
if (console && console.log) {
console.log(
'simulate.drag-sortable.js WARNING: Drag with move set to zero has no effect'
)
}
return
} else {
while (moveCounter !== 0) {
if (direction === 'down') {
if (sibling.next(listItem).length) {
sibling = sibling.next(listItem)
moveVerticalAmount += sibling.outerHeight()
}
moveCounter -= 1
} else {
if (sibling.prev(listItem).length) {
sibling = sibling.prev(listItem)
moveVerticalAmount -= sibling.outerHeight()
}
moveCounter += 1
}
}
}

dispatchEvent(
handle,
'mousedown',
createEvent('mousedown', handle, { clientX: x, clientY: y })
)
// simulate drag start
dispatchEvent(
document,
'mousemove',
createEvent('mousemove', document, { clientX: x + 1, clientY: y + 1 })
)

if (dropOn) {
// jump to top or bottom of new list but do it in increments so that JQuery UI registers the drag events
slideUpTo(x, y, initialVerticalPosition)

// reset y position to top or bottom of list and move from there
y += initialVerticalPosition

// now call regular shift/down in a list
options = jQuery.extend(options, { move: moveCounter })
delete options.dropOn

// add some delays to allow JQuery UI to catch up
setTimeout(function() {
dispatchEvent(
document,
'mousemove',
createEvent('mousemove', document, { clientX: x, clientY: y })
)
}, 5)
setTimeout(function() {
dispatchEvent(
handle,
'mouseup',
createEvent('mouseup', handle, { clientX: x, clientY: y })
)
setTimeout(function() {
if (options.move) {
applyDrag.call(that, options)
}
}, 5)
}, mouseUpAfter)

// stop execution as applyDrag has been called again
return
}

// Sortable is using a fixed height placeholder meaning items jump up and down as you drag variable height items into fixed height placeholder
placeHolder =
placeHolder &&
$(this)
.parent()
.find(placeHolder)

if (!placeHolder && direction === 'down') {
// need to move at least as far as this item and or the last sibling
if ($(this).outerHeight() > $(sibling).outerHeight()) {
moveVerticalAmount += $(this).outerHeight() - $(sibling).outerHeight()
}
moveVerticalAmount += extraDrag(sibling)
dragPastBy += extraDrag(sibling)
} else if (direction === 'up') {
// move a little extra to ensure item clips into next position
moveVerticalAmount -= Math.max(extraDrag(this), 5)
} else if (direction === 'down') {
// moving down with a place holder
if (placeHolder.height() < $(this).height()) {
moveVerticalAmount += Math.max(placeHolder.height(), 5)
} else {
moveVerticalAmount += extraDrag(sibling)
}
}

if (sibling[0] !== $(this)[0]) {
// step through so that the UI controller can determine when to show the placeHolder
slideUpTo(x, y, moveVerticalAmount, dragPastBy)
} else {
if (window.console) {
console.log(
'simulate.drag-sortable.js WARNING: Could not move as at top or bottom already'
)
}
}

setTimeout(function() {
dispatchEvent(
document,
'mousemove',
createEvent('mousemove', document, {
clientX: x,
clientY: y + moveVerticalAmount
})
)
}, 5)
setTimeout(function() {
dispatchEvent(
handle,
'mouseup',
createEvent('mouseup', handle, {
clientX: x,
clientY: y + moveVerticalAmount
})
)
}, mouseUpAfter)
}

// iterate and move each matched element
return this.each(applyDrag)
}

// fire mouse events, go half way, then the next half, so small mouse movements near target and big at the start
function slideUpTo(x, y, targetOffset, goPastBy) {
var offset

if (!goPastBy) {
goPastBy = 0
}
if (targetOffset < 0 && goPastBy > 0) {
goPastBy = -goPastBy
} // ensure go past is in the direction as often passed in from object height so always positive

// go forwards including goPastBy
for (
offset = 0;
Math.abs(offset) + 1 < Math.abs(targetOffset + goPastBy);
offset += (targetOffset + goPastBy - offset) / 2
) {
dispatchEvent(
document,
'mousemove',
createEvent('mousemove', document, {
clientX: x,
clientY: y + Math.ceil(offset)
})
)
}
offset = targetOffset + goPastBy
dispatchEvent(
document,
'mousemove',
createEvent('mousemove', document, { clientX: x, clientY: y + offset })
)

// now bounce back
for (
;
Math.abs(offset) - 1 >= Math.abs(targetOffset);
offset += (targetOffset - offset) / 2
) {
dispatchEvent(
document,
'mousemove',
createEvent('mousemove', document, {
clientX: x,
clientY: y + Math.ceil(offset)
})
)
}
dispatchEvent(
document,
'mousemove',
createEvent('mousemove', document, {
clientX: x,
clientY: y + targetOffset
})
)
}

function createEvent(type, target, options) {
var evt
var e = $.extend(
{
target: target,
preventDefault: function() {},
stopImmediatePropagation: function() {},
stopPropagation: function() {},
isPropagationStopped: function() {
return true
},
isImmediatePropagationStopped: function() {
return true
},
isDefaultPrevented: function() {
return true
},
bubbles: true,
cancelable: type != 'mousemove',
view: window,
detail: 0,
screenX: 0,
screenY: 0,
clientX: 0,
clientY: 0,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
button: 0,
relatedTarget: undefined
},
options || {}
)

if ($.isFunction(document.createEvent)) {
evt = document.createEvent('MouseEvents')
evt.initMouseEvent(
type,
e.bubbles,
e.cancelable,
e.view,
e.detail,
e.screenX,
e.screenY,
e.clientX,
e.clientY,
e.ctrlKey,
e.altKey,
e.shiftKey,
e.metaKey,
e.button,
e.relatedTarget || document.body.parentNode
)
} else if (document.createEventObject) {
evt = document.createEventObject()
$.extend(evt, e)
evt.button = { 0: 1, 1: 4, 2: 2 }[evt.button] || evt.button
}
return evt
}

function dispatchEvent(el, type, evt) {
if (el.dispatchEvent) {
el.dispatchEvent(evt)
} else if (el.fireEvent) {
el.fireEvent('on' + type, evt)
}
return evt
}

function findCenter(el) {
var elm = $(el),
o = elm.offset()
return {
x: o.left + elm.outerWidth() / 2,
y: o.top + elm.outerHeight() / 2
}
}

//
// plugin defaults
//
$.fn.simulateDragSortable.defaults = {
move: 0
}
})(jQuery)

0 comments on commit 1a9dab6

Please sign in to comment.