grafana/public/vendor/angular-native-dragdrop/draganddrop.js
2016-09-13 21:26:28 +02:00

460 lines
16 KiB
JavaScript
Executable File

(function(angular) {
'use strict';
function isDnDsSupported() {
return 'ondrag' in document.createElement('a');
}
function determineEffectAllowed(e) {
if(e.originalEvent) {
e.dataTransfer = e.originalEvent.dataTransfer;
}
// Chrome doesn't set dropEffect, so we have to work it out ourselves
if (typeof e.dataTransfer !== 'undefined' && e.dataTransfer.dropEffect === 'none') {
if (e.dataTransfer.effectAllowed === 'copy' ||
e.dataTransfer.effectAllowed === 'move') {
e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed;
} else if (e.dataTransfer.effectAllowed === 'copyMove' || e.dataTransfer.effectAllowed === 'copymove') {
e.dataTransfer.dropEffect = e.ctrlKey ? 'copy' : 'move';
}
}
}
if (!isDnDsSupported()) {
angular.module('ang-drag-drop', []);
return;
}
var module = angular.module('ang-drag-drop', []);
module.directive('uiDraggable', ['$parse', '$rootScope', '$dragImage', function($parse, $rootScope, $dragImage) {
return function(scope, element, attrs) {
var isDragHandleUsed = false,
dragHandleClass,
draggingClass = attrs.draggingClass || 'on-dragging',
dragTarget;
element.attr('draggable', false);
scope.$watch(attrs.uiDraggable, function(newValue) {
if (newValue) {
element.attr('draggable', newValue);
element.bind('dragend', dragendHandler);
element.bind('dragstart', dragstartHandler);
}
else {
element.removeAttr('draggable');
element.unbind('dragend', dragendHandler);
element.unbind('dragstart', dragstartHandler);
}
});
if (angular.isString(attrs.dragHandleClass)) {
isDragHandleUsed = true;
dragHandleClass = attrs.dragHandleClass.trim() || 'drag-handle';
element.bind('mousedown', function(e) {
dragTarget = e.target;
});
}
function dragendHandler(e) {
if(e.originalEvent) {
e.dataTransfer = e.originalEvent.dataTransfer;
}
setTimeout(function() {
element.unbind('$destroy', dragendHandler);
}, 0);
var sendChannel = attrs.dragChannel || 'defaultchannel';
$rootScope.$broadcast('ANGULAR_DRAG_END', e, sendChannel);
determineEffectAllowed(e);
if (e.dataTransfer && e.dataTransfer.dropEffect !== 'none') {
if (attrs.onDropSuccess) {
var onDropSuccessFn = $parse(attrs.onDropSuccess);
scope.$evalAsync(function() {
onDropSuccessFn(scope, {$event: e});
});
}
}else if (e.dataTransfer && e.dataTransfer.dropEffect === 'none'){
if (attrs.onDropFailure) {
var onDropFailureFn = $parse(attrs.onDropFailure);
scope.$evalAsync(function() {
onDropFailureFn(scope, {$event: e});
});
}
}
element.removeClass(draggingClass);
}
function setDragElement(e, dragImageElementId) {
var dragImageElementFn;
if(e.originalEvent) {
e.dataTransfer = e.originalEvent.dataTransfer;
}
dragImageElementFn = $parse(dragImageElementId);
scope.$apply(function() {
var elementId = dragImageElementFn(scope, {$event: e}),
dragElement;
if (!(elementId && angular.isString(elementId))) {
return;
}
dragElement = document.getElementById(elementId);
if (!dragElement) {
return;
}
e.dataTransfer.setDragImage(dragElement, 0, 0);
});
}
function dragstartHandler(e) {
if(e.originalEvent) {
e.dataTransfer = e.originalEvent.dataTransfer;
}
var isDragAllowed = !isDragHandleUsed || dragTarget.classList.contains(dragHandleClass);
if (isDragAllowed) {
var sendChannel = attrs.dragChannel || 'defaultchannel';
var dragData = '';
if (attrs.drag) {
dragData = scope.$eval(attrs.drag);
}
var dragImage = attrs.dragImage || null;
element.addClass(draggingClass);
element.bind('$destroy', dragendHandler);
//Code to make sure that the setDragImage is available. IE 10, 11, and Opera do not support setDragImage.
var hasNativeDraggable = !(document.uniqueID || window.opera);
//If there is a draggable image passed in, then set the image to be dragged.
if (dragImage && hasNativeDraggable) {
var dragImageFn = $parse(attrs.dragImage);
scope.$apply(function() {
var dragImageParameters = dragImageFn(scope, {$event: e});
if (dragImageParameters) {
if (angular.isString(dragImageParameters)) {
dragImageParameters = $dragImage.generate(dragImageParameters);
}
if (dragImageParameters.image) {
var xOffset = dragImageParameters.xOffset || 0,
yOffset = dragImageParameters.yOffset || 0;
e.dataTransfer.setDragImage(dragImageParameters.image, xOffset, yOffset);
}
}
});
} else if (attrs.dragImageElementId) {
setDragElement(e, attrs.dragImageElementId);
}
var offset = {x: e.offsetX, y: e.offsetY};
var transferDataObject = {data: dragData, channel: sendChannel, offset: offset};
var transferDataText = angular.toJson(transferDataObject);
e.dataTransfer.setData('text', transferDataText);
e.dataTransfer.effectAllowed = 'copyMove';
$rootScope.$broadcast('ANGULAR_DRAG_START', e, sendChannel, transferDataObject);
}
else {
e.preventDefault();
}
}
};
}
]);
module.directive('uiOnDrop', ['$parse', '$rootScope', function($parse, $rootScope) {
return function(scope, element, attr) {
var dragging = 0; //Ref. http://stackoverflow.com/a/10906204
var dropChannel = attr.dropChannel || 'defaultchannel';
var dragChannel = '';
var dragEnterClass = attr.dragEnterClass || 'on-drag-enter';
var dragHoverClass = attr.dragHoverClass || 'on-drag-hover';
var customDragEnterEvent = $parse(attr.onDragEnter);
var customDragLeaveEvent = $parse(attr.onDragLeave);
function calculateDropOffset(e) {
var offset = {
x: e.offsetX,
y: e.offsetY
};
var target = e.target;
while (target !== element[0]) {
offset.x = offset.x + target.offsetLeft;
offset.y = offset.y + target.offsetTop;
target = target.offsetParent;
if (!target) {
return null;
}
}
return offset;
}
function onDragOver(e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
if (e.stopPropagation) {
e.stopPropagation();
}
var uiOnDragOverFn = $parse(attr.uiOnDragOver);
scope.$evalAsync(function() {
uiOnDragOverFn(scope, {$event: e, $channel: dropChannel});
});
return false;
}
function onDragLeave(e) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
dragging--;
if (dragging === 0) {
scope.$evalAsync(function() {
customDragLeaveEvent(scope, {$event: e, $channel: dropChannel});
});
element.addClass(dragEnterClass);
element.removeClass(dragHoverClass);
}
var uiOnDragLeaveFn = $parse(attr.uiOnDragLeave);
scope.$evalAsync(function() {
uiOnDragLeaveFn(scope, {$event: e, $channel: dropChannel});
});
}
function onDragEnter(e) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
if (dragging === 0) {
scope.$evalAsync(function() {
customDragEnterEvent(scope, {$event: e, $channel: dropChannel});
});
element.removeClass(dragEnterClass);
element.addClass(dragHoverClass);
}
dragging++;
var uiOnDragEnterFn = $parse(attr.uiOnDragEnter);
scope.$evalAsync(function() {
uiOnDragEnterFn(scope, {$event: e, $channel: dropChannel});
});
$rootScope.$broadcast('ANGULAR_HOVER', dragChannel);
}
function onDrop(e) {
if(e.originalEvent) {
e.dataTransfer = e.originalEvent.dataTransfer;
}
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
if (e.stopPropagation) {
e.stopPropagation(); // Necessary. Allows us to drop.
}
var sendData = e.dataTransfer.getData('text');
sendData = angular.fromJson(sendData);
var dropOffset = calculateDropOffset(e);
var position = dropOffset ? {
x: dropOffset.x - sendData.offset.x,
y: dropOffset.y - sendData.offset.y
} : null;
determineEffectAllowed(e);
var uiOnDropFn = $parse(attr.uiOnDrop);
scope.$evalAsync(function() {
uiOnDropFn(scope, {$data: sendData.data, $event: e, $channel: sendData.channel, $position: position});
});
element.removeClass(dragEnterClass);
dragging = 0;
}
function isDragChannelAccepted(dragChannel, dropChannel) {
if (dropChannel === '*') {
return true;
}
var channelMatchPattern = new RegExp('(\\s|[,])+(' + dragChannel + ')(\\s|[,])+', 'i');
return channelMatchPattern.test(',' + dropChannel + ',');
}
function preventNativeDnD(e) {
if(e.originalEvent) {
e.dataTransfer = e.originalEvent.dataTransfer;
}
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
e.dataTransfer.dropEffect = 'none';
return false;
}
var deregisterDragStart = $rootScope.$on('ANGULAR_DRAG_START', function(_, e, channel, transferDataObject) {
dragChannel = channel;
var valid = true;
if (!isDragChannelAccepted(channel, dropChannel)) {
valid = false;
}
if (valid && attr.dropValidate) {
var validateFn = $parse(attr.dropValidate);
valid = validateFn(scope, {
$drop: {scope: scope, element: element},
$event: e,
$data: transferDataObject.data,
$channel: transferDataObject.channel
});
}
if (valid) {
element.bind('dragover', onDragOver);
element.bind('dragenter', onDragEnter);
element.bind('dragleave', onDragLeave);
element.bind('drop', onDrop);
element.addClass(dragEnterClass);
} else {
element.bind('dragover', preventNativeDnD);
element.bind('dragenter', preventNativeDnD);
element.bind('dragleave', preventNativeDnD);
element.bind('drop', preventNativeDnD);
element.removeClass(dragEnterClass);
}
});
var deregisterDragEnd = $rootScope.$on('ANGULAR_DRAG_END', function() {
element.unbind('dragover', onDragOver);
element.unbind('dragenter', onDragEnter);
element.unbind('dragleave', onDragLeave);
element.unbind('drop', onDrop);
element.removeClass(dragHoverClass);
element.removeClass(dragEnterClass);
element.unbind('dragover', preventNativeDnD);
element.unbind('dragenter', preventNativeDnD);
element.unbind('dragleave', preventNativeDnD);
element.unbind('drop', preventNativeDnD);
});
scope.$on('$destroy', function() {
deregisterDragStart();
deregisterDragEnd();
});
attr.$observe('dropChannel', function(value) {
if (value) {
dropChannel = value;
}
});
};
}
]);
module.constant('$dragImageConfig', {
height: 20,
width: 200,
padding: 10,
font: 'bold 11px Arial',
fontColor: '#eee8d5',
backgroundColor: '#93a1a1',
xOffset: 0,
yOffset: 0
});
module.service('$dragImage', ['$dragImageConfig', function(defaultConfig) {
var ELLIPSIS = '…';
function fitString(canvas, text, config) {
var width = canvas.measureText(text).width;
if (width < config.width) {
return text;
}
while (width + config.padding > config.width) {
text = text.substring(0, text.length - 1);
width = canvas.measureText(text + ELLIPSIS).width;
}
return text + ELLIPSIS;
}
this.generate = function(text, options) {
var config = angular.extend({}, defaultConfig, options || {});
var el = document.createElement('canvas');
el.height = config.height;
el.width = config.width;
var canvas = el.getContext('2d');
canvas.fillStyle = config.backgroundColor;
canvas.fillRect(0, 0, config.width, config.height);
canvas.font = config.font;
canvas.fillStyle = config.fontColor;
var title = fitString(canvas, text, config);
canvas.fillText(title, 4, config.padding + 4);
var image = new Image();
image.src = el.toDataURL();
return {
image: image,
xOffset: config.xOffset,
yOffset: config.yOffset
};
};
}
]);
}(angular));