(js) sgDraggable and sgDroppable directives
parent
fea6978b63
commit
1e26b3e61d
|
@ -0,0 +1,155 @@
|
||||||
|
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
/* jshint validthis: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sgDraggable - Make an element (usually a folder of elements) draggable.
|
||||||
|
* @memberof SOGo.Common
|
||||||
|
* @restrict attribute
|
||||||
|
* @param {Object=} sgDraggable - the object to be exposed to the droppable target.
|
||||||
|
* @param {expression} sgDragStart - dragging will only start if this expression returns true.
|
||||||
|
* @param {expression} sgDragCount - the number of items being dragged; this number appears inside
|
||||||
|
* the sg-draggable-helper element that follows the mouse cursor.
|
||||||
|
*
|
||||||
|
* @example:
|
||||||
|
|
||||||
|
<sg-draggable-helper>
|
||||||
|
<md-icon>email</md-icon>
|
||||||
|
<sg-draggable-helper-counter></sg-draggable-helper-counter>
|
||||||
|
</sg-draggable-helper>
|
||||||
|
|
||||||
|
<md-list sg-draggable="mailbox.service.selectedFolder"
|
||||||
|
sg-drag-start="mailbox.selectedFolder.$selectedCount()"
|
||||||
|
sg-drag-count="mailbox.selectedFolder.$selectedCount()">
|
||||||
|
*/
|
||||||
|
sgDraggable.$inject = ['$parse', '$rootScope', '$document', '$timeout', '$log'];
|
||||||
|
function sgDraggable($parse, $rootScope, $document, $timeout, $log) {
|
||||||
|
return {
|
||||||
|
restrict: 'A',
|
||||||
|
link: link
|
||||||
|
};
|
||||||
|
|
||||||
|
function link(scope, element, attrs) {
|
||||||
|
var o;
|
||||||
|
|
||||||
|
$timeout(function() {
|
||||||
|
var folder, dragStart, count;
|
||||||
|
|
||||||
|
folder = $parse(attrs.sgDraggable)(scope);
|
||||||
|
dragStart = attrs.sgDragStart? $parse(attrs.sgDragStart) : null;
|
||||||
|
count = attrs.sgDragCount? $parse(attrs.sgDragCount) : null;
|
||||||
|
o = new sgDraggableObject(element, folder, dragStart, count);
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.$on('$destroy', function() {
|
||||||
|
o.$destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
function sgDraggableObject($element, folder, dragStart, count) {
|
||||||
|
this.$element = $element;
|
||||||
|
this.folder = folder;
|
||||||
|
this.dragStart = dragStart;
|
||||||
|
this.count = count;
|
||||||
|
this.helper = $document.find('sg-draggable-helper');
|
||||||
|
|
||||||
|
if (!this.helper) {
|
||||||
|
throw Error('sg-draggable requires a sg-draggable-helper element.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bindedOnDragDetect = angular.bind(this, this.onDragDetect);
|
||||||
|
this.bindedOnDrag = angular.bind(this, this.onDrag);
|
||||||
|
|
||||||
|
// Register the mousedown event that can trigger the dragging action
|
||||||
|
this.$element.on('mousedown', this.bindedOnDragDetect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sgDraggableObject is an object that wraps the logic to emit the folder:dragstart and
|
||||||
|
* folder:dragend custom events.
|
||||||
|
*/
|
||||||
|
sgDraggableObject.prototype = {
|
||||||
|
|
||||||
|
dragHasStarted: false,
|
||||||
|
|
||||||
|
$destroy: function() {
|
||||||
|
this.$element.off('mousedown', this.bindedOnDragDetect);
|
||||||
|
},
|
||||||
|
|
||||||
|
getDistanceFromStart: function(event) {
|
||||||
|
var delta = {
|
||||||
|
x: this.startPosition.clientX - event.clientX,
|
||||||
|
y: this.startPosition.clientY - event.clientY
|
||||||
|
};
|
||||||
|
|
||||||
|
return Math.sqrt(delta.x * delta.x + delta.y * delta.y);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// Start dragging on mousedown
|
||||||
|
onDragDetect: function(ev) {
|
||||||
|
var dragMode, pointerHandler;
|
||||||
|
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
if (!this.dragStart || this.dragStart(scope)) {
|
||||||
|
// Listen to mousemove and start dragging when mouse has moved from at least 3 pixels
|
||||||
|
$document.on('mousemove', this.bindedOnDrag);
|
||||||
|
// Stop dragging on the next "mouseup"
|
||||||
|
$document.one('mouseup', angular.bind(this, this.onDragEnd));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
onDrag: function(ev) {
|
||||||
|
var counter;
|
||||||
|
|
||||||
|
if (!this.startPosition) {
|
||||||
|
this.startPosition = { clientX: ev.clientX, clientY: ev.clientY };
|
||||||
|
}
|
||||||
|
else if (!this.dragHasStarted && this.getDistanceFromStart(ev) > 10) {
|
||||||
|
counter = this.helper.find('sg-draggable-helper-counter');
|
||||||
|
this.dragHasStarted = true;
|
||||||
|
|
||||||
|
this.helper.removeClass('ng-hide');
|
||||||
|
if (this.count && this.count(scope) > 1)
|
||||||
|
counter.text(this.count(scope)).removeClass('ng-hide');
|
||||||
|
else
|
||||||
|
counter.addClass('ng-hide');
|
||||||
|
|
||||||
|
$log.debug('emit folder:dragstart');
|
||||||
|
$rootScope.$emit('folder:dragstart', this.folder);
|
||||||
|
}
|
||||||
|
if (this.dragHasStarted) {
|
||||||
|
if (ev.shiftKey)
|
||||||
|
this.helper.addClass('sg-draggable-helper--copy');
|
||||||
|
else
|
||||||
|
this.helper.removeClass('sg-draggable-helper--copy');
|
||||||
|
this.helper.css({ top: (ev.pageY + 5) + 'px', left: (ev.pageX + 5) + 'px' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
onDragEnd: function(ev) {
|
||||||
|
this.startPosition = null;
|
||||||
|
$document.off('mousemove', this.bindedOnDrag);
|
||||||
|
|
||||||
|
if (this.dragHasStarted) {
|
||||||
|
$log.debug('emit folder:dragend');
|
||||||
|
$rootScope.$emit('folder:dragend', this.folder, ev.shiftKey?'copy':'move');
|
||||||
|
this.dragHasStarted = false;
|
||||||
|
this.helper.addClass('ng-hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('SOGo.Common')
|
||||||
|
.directive('sgDraggable', sgDraggable);
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
/* jshint validthis: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sgDroppable - Make an element a possible destination while dragging
|
||||||
|
* @memberof SOGo.Common
|
||||||
|
* @restrict attribute
|
||||||
|
* @param {expression} sgDroppable - dropping is accepted only if this expression returs true.
|
||||||
|
* One variables is exposed: dragFolder.
|
||||||
|
* @param {expression} sgDrop - called when dropping ends on the element.
|
||||||
|
* Two variables are exposed: dragFolder and dragMode.
|
||||||
|
*
|
||||||
|
* @example:
|
||||||
|
|
||||||
|
<md-list-item sg-droppable="folder.id != dragFolder.id"
|
||||||
|
sg-drop="app.dragSelectedMessages(dragFolder, folder, dragMode)">
|
||||||
|
*/
|
||||||
|
sgDroppable.$inject = ['$parse', '$rootScope', '$document', '$timeout', '$log'];
|
||||||
|
function sgDroppable($parse, $rootScope, $document, $timeout, $log) {
|
||||||
|
return {
|
||||||
|
restrict: 'A',
|
||||||
|
link: link
|
||||||
|
};
|
||||||
|
|
||||||
|
function link(scope, element, attrs) {
|
||||||
|
var overElement = false, dropAction, droppable,
|
||||||
|
deregisterFolderDragStart, deregisterFolderDragEnd;
|
||||||
|
|
||||||
|
if (!attrs.sgDrop) {
|
||||||
|
throw Error('sg-droppable requires a sg-drop action.');
|
||||||
|
}
|
||||||
|
|
||||||
|
overElement = false;
|
||||||
|
droppable = $parse(attrs.sgDroppable);
|
||||||
|
dropAction = $parse(attrs.sgDrop);
|
||||||
|
|
||||||
|
// Register listeners of custom events on root scope
|
||||||
|
deregisterFolderDragStart = $rootScope.$on('folder:dragstart', function(event, folder) {
|
||||||
|
if (droppable(scope, { dragFolder: folder })) {
|
||||||
|
element.on('mouseenter', onEnter);
|
||||||
|
element.on('mouseleave', onLeave);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
deregisterFolderDragEnd = $rootScope.$on('folder:dragend', function(event, folder, mode) {
|
||||||
|
element.off('mouseenter');
|
||||||
|
element.off('mouseleave');
|
||||||
|
if (overElement) {
|
||||||
|
angular.bind(element[0], onLeave)(event);
|
||||||
|
dropAction(scope, { dragFolder: folder, dragMode: mode });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.$on('destroy', function() {
|
||||||
|
deregisterFolderDragStart();
|
||||||
|
deregisterFolderDragEnd();
|
||||||
|
});
|
||||||
|
|
||||||
|
function onEnter(event) {
|
||||||
|
overElement = true;
|
||||||
|
element.addClass('sg-droppable-over');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLeave(event) {
|
||||||
|
overElement = false;
|
||||||
|
this.classList.remove('sg-droppable-over');
|
||||||
|
element.off('mousemove');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('SOGo.Common')
|
||||||
|
.directive('sgDroppable', sgDroppable);
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/// draggable.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*-
|
||||||
|
|
||||||
|
sg-draggable-helper {
|
||||||
|
min-width: $icon-button-width;
|
||||||
|
min-height: $icon-button-width;
|
||||||
|
position: absolute;
|
||||||
|
top: -200px;
|
||||||
|
left: -200px;
|
||||||
|
z-index: $z-index-toast + 1;
|
||||||
|
&.sg-draggable-helper--copy md-icon {
|
||||||
|
color: rgba(0,0,0,0.34);
|
||||||
|
text-shadow: 3px 3px 0px rgba(0, 0, 0, 0.54);
|
||||||
|
}
|
||||||
|
md-icon {
|
||||||
|
margin: $icon-button-margin;
|
||||||
|
}
|
||||||
|
sg-draggable-helper-counter {
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: $icon-badge-size;
|
||||||
|
min-height: $icon-badge-size;
|
||||||
|
min-width: $icon-badge-size;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
right: ($icon-size - $icon-badge-size) / 2;
|
||||||
|
top: ($icon-size - $icon-badge-size) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
/// droppable.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*-
|
||||||
|
|
||||||
|
.sg-droppable-over {
|
||||||
|
@extend .md-whiteframe-3dp;
|
||||||
|
background-color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -66,6 +66,8 @@
|
||||||
@import 'components/whiteframe/whiteframe';
|
@import 'components/whiteframe/whiteframe';
|
||||||
|
|
||||||
// Inverse components
|
// Inverse components
|
||||||
|
@import 'components/draggable-droppable/draggable';
|
||||||
|
@import 'components/draggable-droppable/droppable';
|
||||||
@import 'components/ripple/ripple';
|
@import 'components/ripple/ripple';
|
||||||
@import 'components/timepicker/timepicker';
|
@import 'components/timepicker/timepicker';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue