-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduction to moveable mixin #209
Changes from all commits
52911bf
2259bff
5e73629
06bb87a
7689d2b
e4cc6a5
2aa9852
6a6dd8a
d718a78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
export const MOVE_END = 'moveend'; | ||
const PREVENT_SELECT_CLASS = 'noselect'; | ||
|
||
export function moveableComponent({ view }) { | ||
const offset = {}; | ||
const position = {}; | ||
|
||
function onMouseMove({ clientX, clientY }) { | ||
position.x = clientX - offset.x; | ||
position.y = clientY - offset.y; | ||
view.el.style.transform = | ||
`translate3d(${position.x}px, ${position.y}px, 0)`; | ||
} | ||
|
||
function onMouseUp() { | ||
document.removeEventListener('mousemove', onMouseMove); | ||
document.removeEventListener('mouseup', onMouseUp, false); | ||
view.trigger(MOVE_END, position); | ||
} | ||
|
||
function onMouseDown({ clientX, clientY }) { | ||
const { left, top } = view.el.getBoundingClientRect(); | ||
offset.x = clientX - left; | ||
offset.y = clientY - top; | ||
document.addEventListener('mousemove', onMouseMove, false); | ||
document.addEventListener('mouseup', onMouseUp, false); | ||
} | ||
|
||
view.delegate('mousedown', null, onMouseDown); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's |
||
view.el.classList.add(PREVENT_SELECT_CLASS); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,18 +2,15 @@ import lodash from 'lodash'; | |
import { View } from '../../core'; | ||
import PlantViewTools from '../plant/tools'; | ||
import Const from '../../const'; | ||
import { moveableComponent, MOVE_END } from '../components/moveable'; | ||
|
||
export default View.extend({ | ||
className: 'plantingjs-plantedobject-container ui-draggable ui-draggable-handle', | ||
className: 'plantingjs-plantedobject-container', | ||
template: require('./object.hbs'), | ||
|
||
events: { | ||
'dragstart': 'dragstart', | ||
'dragstop': 'saveCoords', | ||
'mouseover': 'setUserActivity', | ||
'mouseleave': 'unsetUserActivity', | ||
}, | ||
|
||
$img: null, | ||
|
||
initialize: function(options) { | ||
|
@@ -35,24 +32,28 @@ export default View.extend({ | |
options: this.app.data.options, | ||
}); | ||
|
||
this.$el.draggable({ | ||
cancel: '.icon-loop, .icon-trash, .icon-resize', | ||
}); | ||
this.model | ||
.on('change:currentProjection', this.updateProjection, this) | ||
.on('change:layerIndex', this.setLayer, this); | ||
|
||
if (this.app.getState() !== Const.State.VIEWER) { | ||
moveableComponent({ view: this }); | ||
this.on(MOVE_END, this.model.set, this.model); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually thinking here about movint this listener definition into mixin module. We have access to |
||
} | ||
}, | ||
|
||
render: function() { | ||
const x = this.overlay.width() * this.model.get('x'); | ||
const y = this.overlay.height() / 2 + this.model.get('y') * this.overlay.width(); | ||
|
||
this.$el | ||
.html(this.template({ | ||
projectionUrl: this.model.getProjection(), | ||
})) | ||
.attr('data-cid', this.model.cid) | ||
.css({ | ||
left: this.overlay.width() * this.model.get('x'), | ||
top: this.overlay.height() / 2 + this.model.get('y') * this.overlay.width(), | ||
zIndex: this.model.get('layerIndex'), | ||
transform: `translate3d(${x}px, ${y}px, 0)`, | ||
}); | ||
|
||
this.$img = this.$el.children('img'); | ||
|
@@ -74,26 +75,11 @@ export default View.extend({ | |
this.$img.attr('src', model.getProjection()); | ||
}, | ||
|
||
saveCoords: function(ev, ui) { | ||
this.model.set({ | ||
x: ui.position.left, | ||
y: ui.position.top, | ||
}); | ||
|
||
return this; | ||
}, | ||
|
||
setUserActivity: function() { | ||
this.model.set('userActivity', true); | ||
}, | ||
|
||
unsetUserActivity: function() { | ||
this.model.set('userActivity', false); | ||
}, | ||
|
||
dragstart: function(ev) { | ||
if (this.app.getState() === Const.State.VIEWER) { | ||
ev.preventDefault(); | ||
} | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* global describe,beforeEach,afterEach,before,it:false */ | ||
import environment from './env/client'; | ||
import { createMouseEvent } from './utils/events'; | ||
import { moveableComponent } from '../src/js/modules/components/moveable'; | ||
import Backbone from 'backbone'; | ||
import sinon from 'sinon'; | ||
import { equal } from 'assert'; | ||
|
||
describe('Moveable component', () => { | ||
let viewInstance; | ||
let document; | ||
|
||
function preparation() { | ||
viewInstance = new Backbone.View(); | ||
viewInstance.el.getBoundingClientRect = () => ({ | ||
left: 50, | ||
top: 50, | ||
}); | ||
} | ||
|
||
function cleanup() { | ||
viewInstance.remove(); | ||
} | ||
|
||
before(cb => environment.then((w) => { | ||
document = w.document; | ||
cb(); | ||
})); | ||
beforeEach(preparation); | ||
afterEach(cleanup); | ||
|
||
it('Should listen to mousedown event', () => { | ||
sinon.spy(viewInstance, 'delegate'); | ||
moveableComponent({ view: viewInstance }); | ||
equal( | ||
viewInstance.delegate | ||
.calledWith('mousedown', null, sinon.match.func), true); | ||
}); | ||
|
||
it('Should move element after sequence of mousedown and mousemove events', | ||
() => { | ||
const spy = sinon.spy(); | ||
|
||
moveableComponent({ view: viewInstance }); | ||
viewInstance.on('moveend', spy); | ||
equal(spy.called, false); | ||
viewInstance.el.dispatchEvent(createMouseEvent({ type: 'mousedown' })); | ||
document.dispatchEvent(createMouseEvent({ | ||
type: 'mousemove', | ||
clientX: 100, | ||
clientY: 100, | ||
})); | ||
document.dispatchEvent(createMouseEvent({ type: 'mouseup' })); | ||
equal(spy.calledWithExactly({ x: 150, y: 150 }), true); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const required = param => { | ||
throw new Error(`Parametr ${param} is required`); | ||
}; | ||
|
||
export function createMouseEvent({ | ||
type = required('type'), | ||
canBubble = false, | ||
cancelable = true, | ||
view = global.window, | ||
detail = 0, | ||
screenX = 0, | ||
screenY = 0, | ||
clientX = 0, | ||
clientY = 0, | ||
ctrlKey = false, | ||
altKey = false, | ||
shiftKey = false, | ||
metaKey = false, | ||
button = false, | ||
relatedTarget = null, | ||
}) { | ||
const event = document.createEvent('MouseEvent'); | ||
|
||
event.initMouseEvent( | ||
type, canBubble, cancelable, view, detail, | ||
screenX, screenY, clientX, clientY, ctrlKey, | ||
altKey, shiftKey, metaKey, button, relatedTarget); | ||
|
||
return event; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we are binding these events to
document
instead tothis.el
? Previous version have listen onel
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because they worked improperly in case of objects collision. E.g when you dragged object over another and the second object has bigger z-index the first one has stopped - because we lost mouse event. Having global event solves this problem, also this way we are sure that object will never move whenever user releases mouse button.