(function (svgext) {
'use strict';
/**
* Makes an element draggable
*
* @mixin svgext.SVGDraggable
*/
svgext.SVGDraggable = {
/**
* SVGDraggable mixin constructor proxy
*
* @override
* @ignore
*/
__constructor: function (opts) {
this.__base.apply(this, arguments);
if (opts.isDraggable || opts.isDraggable === undefined) {
this._dndOnMouseMove = this._dndOnMouseMove.bind(this);
this._dndOnMouseUp = this._dndOnMouseUp.bind(this);
this.addClass('svg_draggable');
this.on(svgext._isTouchDevice ? 'touchstart' : 'mousedown', this._dndOnMouseDown.bind(this));
}
},
/**
* Removes all dnd handlers
*
* @override {SVGElement}
*/
destroy: function () {
if (this.removeDndHandlers) {
this.removeDndHandlers();
}
this.__base();
},
/**
* Removes all handlers
*/
removeDndHandlers: function () {
if (svgext._isTouchDevice) {
window.removeEventListener('touchmove', this._dndOnMouseMove);
window.removeEventListener('touchcancel', this._dndOnMouseUp);
window.removeEventListener('touchend', this._dndOnMouseUp);
} else {
window.removeEventListener('mousemove', this._dndOnMouseMove);
window.removeEventListener('mouseup', this._dndOnMouseUp);
}
},
/**
* Mouse down event listener handler
*
* @param {Event} event
* @private
*/
_dndOnMouseDown: function (event) {
// Cross browser right mouse click check
if ((event.which && event.which === 3) || (event.button && event.button === 2)) {
return;
}
document.body.classList.add('unselectable');
this._saveClientCoords(event.changedTouches ? event.changedTouches[0] : event);
if (svgext._isTouchDevice) {
window.addEventListener('touchmove', this._dndOnMouseMove);
window.addEventListener('touchend', this._dndOnMouseUp);
window.addEventListener('touchcancel', this._dndOnMouseUp);
} else {
window.addEventListener('mousemove', this._dndOnMouseMove);
window.addEventListener('mouseup', this._dndOnMouseUp);
}
},
/**
* Mouse move handler
*
* @param {Event} event
* @private
*/
_dndOnMouseMove: function (event) {
if (event.movementX !== undefined) {
return this.drag(this.normalizeCoords({x: event.movementX, y: event.movementY}));
}
var data = event;
if (data.changedTouches) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
data = event.changedTouches[0];
}
if (!this._lastClientCoords) {
return this._saveClientCoords(data);
}
this.drag(this.normalizeCoords({
x: data.clientX - this._lastClientCoords.clientX,
y: data.clientY - this._lastClientCoords.clientY
}));
this._saveClientCoords(data);
},
/**
* Saves last event client coordinates
*
* @param {Object} data Mousemove event
* @param {Number} data.clientX
* @param {Number} data.clientY
* @private
*/
_saveClientCoords: function (data) {
this._lastClientCoords = {
clientX: data.clientX,
clientY: data.clientY
};
},
/**
* Mouse up handler
*
* @private
*/
_dndOnMouseUp: function () {
this._lastClientCoords = null;
this.removeDndHandlers();
document.body.classList.remove('unselectable');
},
/**
* Changes element position on mouse move
*
* @abstract
* @param {coordinates} delta
*/
drag: function () {
throw new Error('SVGDraggable.drag not implemented');
},
/**
* Checks if a new element position won't cross the border
* and changes delta object to avoid border crossing
*
* @abstract
* @param {coordinates} delta
* @returns {coordinates}
*/
normalizeCoords: function () {
throw new Error('SVGDraggable.normalizeCoords not implemented');
}
};
}(svgext));