3386 lines
104 KiB
JavaScript
3386 lines
104 KiB
JavaScript
;(function(angular, undefined, window, document){
|
||
|
||
|
||
/**
|
||
* @license AngularJS-DND v0.1.13
|
||
* (c) 2014-2015 Alexander Afonin (toafonin@gmail.com, http://github.com/Tuch)
|
||
* License: MIT
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
/*
|
||
|
||
=================
|
||
ANGULAR DND:
|
||
=================
|
||
|
||
*/
|
||
|
||
/* ENVIRONMENT VARIABLES */
|
||
|
||
var version = '0.1.13',
|
||
$ = angular.element, $window = $(window), $document = $(document), body = 'body', TRANSFORM, TRANSFORMORIGIN, MATCHES_SELECTOR,
|
||
debug = {
|
||
mode: false,
|
||
helpers: {}
|
||
},
|
||
forEach = angular.forEach,
|
||
extend = angular.extend;
|
||
|
||
(function () {
|
||
window.console = window.console || {
|
||
log: noop,
|
||
info: noop,
|
||
warn: noop,
|
||
error: noop
|
||
};
|
||
})();
|
||
|
||
(function() {
|
||
var agent = navigator.userAgent;
|
||
|
||
if ( /webkit\//i.test(agent) ) {
|
||
TRANSFORM = '-webkit-transform';
|
||
TRANSFORMORIGIN = '-webkit-transform-origin';
|
||
MATCHES_SELECTOR = 'webkitMatchesSelector';
|
||
} else if (/gecko\//i.test(agent)) {
|
||
TRANSFORM = '-moz-transform';
|
||
TRANSFORMORIGIN = '-moz-transform-origin';
|
||
MATCHES_SELECTOR = 'mozMatchesSelector';
|
||
} else if (/trident\//i.test(agent)) {
|
||
TRANSFORM = '-ms-transform';
|
||
TRANSFORMORIGIN = 'ms-transform-origin';
|
||
MATCHES_SELECTOR = 'msMatchesSelector';
|
||
} else if (/presto\//i.test(agent)) {
|
||
TRANSFORM = '-o-transform';
|
||
TRANSFORMORIGIN = '-o-transform-origin';
|
||
MATCHES_SELECTOR = 'oMatchesSelector';
|
||
} else {
|
||
TRANSFORM = 'transform';
|
||
TRANSFORMORIGIN = 'transform-origin';
|
||
MATCHES_SELECTOR = 'matches';
|
||
}
|
||
|
||
})();
|
||
|
||
|
||
|
||
/* SOME HELPERS */
|
||
|
||
function noop() {}
|
||
|
||
function doFalse() {
|
||
return false;
|
||
}
|
||
|
||
function doTrue() {
|
||
return true;
|
||
}
|
||
|
||
function proxy(context, fn) {
|
||
return function() {
|
||
fn.apply(context, arguments);
|
||
};
|
||
}
|
||
|
||
function degToRad(d) {
|
||
return (d * (Math.PI / 180));
|
||
}
|
||
|
||
function radToDeg(r) {
|
||
return (r * (180 / Math.PI));
|
||
}
|
||
|
||
function getNumFromSegment(min, curr, max) {
|
||
return curr<min ? min : curr > max ? max : curr;
|
||
}
|
||
|
||
function findEvents(element) {
|
||
|
||
var events = element.data('events');
|
||
if (events !== undefined) {
|
||
return events;
|
||
}
|
||
|
||
events = $.data(element, 'events');
|
||
if (events !== undefined) {
|
||
return events;
|
||
}
|
||
|
||
events = $._data(element, 'events');
|
||
if (events !== undefined) {
|
||
return events;
|
||
}
|
||
|
||
events = $._data(element[0], 'events');
|
||
if (events !== undefined) {
|
||
return events;
|
||
}
|
||
|
||
return undefined;
|
||
}
|
||
|
||
function debounce(fn, timeout, invokeAsap, context) {
|
||
if (arguments.length === 3 && typeof invokeAsap !== 'boolean') {
|
||
context = invokeAsap;
|
||
invokeAsap = false;
|
||
}
|
||
|
||
var timer;
|
||
|
||
return function() {
|
||
var args = arguments;
|
||
context = context || this;
|
||
|
||
if (invokeAsap && !timer) {
|
||
fn.apply(context, args);
|
||
}
|
||
|
||
clearTimeout(timer);
|
||
|
||
timer = setTimeout(function() {
|
||
if (!invokeAsap) {
|
||
fn.apply(context, args);
|
||
}
|
||
|
||
timer = null;
|
||
|
||
}, timeout);
|
||
|
||
};
|
||
}
|
||
|
||
function throttle(fn, timeout, context) {
|
||
var timer, args;
|
||
|
||
return function() {
|
||
if (timer) {
|
||
return;
|
||
}
|
||
|
||
args = arguments;
|
||
context = context || this;
|
||
fn.apply(context, args);
|
||
timer = setTimeout(function() { timer = null; }, timeout);
|
||
};
|
||
}
|
||
|
||
|
||
/* parsing like: ' a = fn1(), b = fn2()' into {a: 'fn1()', b: 'fn2()'} */
|
||
|
||
function parseStringAsVars(str) {
|
||
if (!str) {
|
||
return undefined;
|
||
}
|
||
|
||
var a = str.replace(/\s/g,'').split(','), ret = {};
|
||
|
||
for( var i = 0; i < a.length; i++ ) {
|
||
a[i] = a[i].split('=');
|
||
ret[a[i][0]] = a[i][1];
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
function avgPerf (fn1, timeout, context, callback) {
|
||
context = context || this;
|
||
timeout = timeout || 1000;
|
||
callback = callback || function(val) {
|
||
console.log(val);
|
||
};
|
||
|
||
var time = [];
|
||
|
||
var fn2 = debounce(function() {
|
||
var sum = 0;
|
||
|
||
for(var i=0; i < time.length; i++) {
|
||
sum += time[i];
|
||
}
|
||
|
||
callback( Math.round(sum / time.length) );
|
||
|
||
time = [];
|
||
|
||
}, timeout);
|
||
|
||
return function() {
|
||
var start = Date.now();
|
||
|
||
fn1.apply(context, arguments);
|
||
|
||
time.push(Date.now() - start);
|
||
|
||
fn2();
|
||
|
||
};
|
||
}
|
||
|
||
function roundNumber(number, n) {
|
||
if (isNaN(n)) {
|
||
n=0;
|
||
}
|
||
|
||
var m = Math.pow(10,n);
|
||
return Math.round(number * m) / m;
|
||
}
|
||
|
||
|
||
/* POINT OBJECT */
|
||
|
||
var Point = (function() {
|
||
function Point(x, y) {
|
||
if (typeof x === 'object') {
|
||
y = x.y || x.top;
|
||
x = x.x || x.left;
|
||
}
|
||
|
||
this.x = x || 0;
|
||
this.y = y || 0;
|
||
}
|
||
|
||
Point.prototype = {
|
||
equal: function(other) {
|
||
if (!(other instanceof Point)) {
|
||
other = new Point(other);
|
||
}
|
||
|
||
return this.x === other.x && this.y === other.y;
|
||
},
|
||
plus: function(other) {
|
||
if (!(other instanceof Point)) {
|
||
other = new Point(other);
|
||
}
|
||
|
||
return new Point(this.x + other.x, this.y + other.y);
|
||
},
|
||
minus: function(other) {
|
||
if (!(other instanceof Point)) {
|
||
other = new Point(other);
|
||
}
|
||
|
||
return new Point(this.x - other.x, this.y - other.y);
|
||
},
|
||
scale: function(scalar) {
|
||
return new Point(this.x * scalar, this.y * scalar);
|
||
},
|
||
magnitude: function() {
|
||
return this.distance(new Point(0, 0), this);
|
||
},
|
||
distance: function(other) {
|
||
if (!(other instanceof Point)) {
|
||
other = new Point(other);
|
||
}
|
||
|
||
return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
|
||
},
|
||
angle: function (other, isdegree) {
|
||
var ret = Math.atan2(
|
||
other.y - this.y,
|
||
other.x - this.x
|
||
);
|
||
|
||
if (isdegree===true) {
|
||
ret *= 180/Math.PI;
|
||
}
|
||
|
||
return ret;
|
||
},
|
||
deltaAngle: function(other, aboutPoint, isdegree) {
|
||
aboutPoint = aboutPoint === undefined ? {x:0,y:0} : aboutPoint;
|
||
|
||
var ret = this.angle(aboutPoint) - other.angle(aboutPoint);
|
||
|
||
if (ret < 0) {
|
||
ret = Math.PI*2 + ret;
|
||
}
|
||
|
||
if (isdegree===true) {
|
||
ret *= 180/Math.PI;
|
||
}
|
||
|
||
return ret;
|
||
},
|
||
transform: function(matrix) {
|
||
return matrix.transformPoint(this);
|
||
},
|
||
deltaTransform: function(matrix) {
|
||
return matrix.deltaTransformPoint(this);
|
||
},
|
||
rotate: function(rads, aboutPoint) {
|
||
var matrix = (new Matrix()).rotate(rads, aboutPoint);
|
||
|
||
return this.transform(matrix);
|
||
},
|
||
getAsCss: function() {
|
||
return {
|
||
top: this.y,
|
||
left: this.x
|
||
};
|
||
}
|
||
};
|
||
|
||
return function(x,y) {
|
||
return new Point(x,y);
|
||
};
|
||
})();
|
||
|
||
|
||
|
||
/* MATRIX OBJECT */
|
||
|
||
var Matrix = (function() {
|
||
function Matrix(a, b, c, d, tx, ty) {
|
||
this.a = a !== undefined ? a : 1;
|
||
this.b = b || 0;
|
||
this.c = c || 0;
|
||
this.d = d !== undefined ? d : 1;
|
||
this.tx = tx || 0;
|
||
this.ty = ty || 0;
|
||
}
|
||
|
||
Matrix.prototype = {
|
||
concat: function(other) {
|
||
return new Matrix(
|
||
this.a * other.a + this.c * other.b,
|
||
this.b * other.a + this.d * other.b,
|
||
this.a * other.c + this.c * other.d,
|
||
this.b * other.c + this.d * other.d,
|
||
this.a * other.tx + this.c * other.ty + this.tx,
|
||
this.b * other.tx + this.d * other.ty + this.ty
|
||
);
|
||
},
|
||
inverse: function() {
|
||
var determinant = this.a * this.d - this.b * this.c;
|
||
|
||
return new Matrix(
|
||
this.d / determinant,
|
||
-this.b / determinant,
|
||
-this.c / determinant,
|
||
this.a / determinant,
|
||
(this.c * this.ty - this.d * this.tx) / determinant,
|
||
(this.b * this.tx - this.a * this.ty) / determinant
|
||
);
|
||
},
|
||
rotate: function(theta, aboutPoint) {
|
||
var rotationMatrix = new Matrix(
|
||
Math.cos(theta),
|
||
Math.sin(theta),
|
||
-Math.sin(theta),
|
||
Math.cos(theta)
|
||
);
|
||
|
||
if (aboutPoint) {
|
||
rotationMatrix = this.translate(aboutPoint.x, aboutPoint.y).concat(rotationMatrix).translate(-aboutPoint.x, -aboutPoint.y);
|
||
}
|
||
|
||
return this.concat(rotationMatrix);
|
||
},
|
||
scale: function(sx, sy, aboutPoint) {
|
||
sy = sy || sx;
|
||
|
||
var scaleMatrix = new Matrix(sx, 0, 0, sy);
|
||
|
||
if (aboutPoint) {
|
||
scaleMatrix = scaleMatrix.translate(aboutPoint.x, aboutPoint.y).translate(-aboutPoint.x, -aboutPoint.y);
|
||
}
|
||
|
||
return scaleMatrix;
|
||
},
|
||
translate: function(tx, ty) {
|
||
var translateMatrix = new Matrix(1, 0, 0, 1, tx, ty);
|
||
|
||
return this.concat(translateMatrix);
|
||
},
|
||
|
||
transformPoint: function(point) {
|
||
return new Point(
|
||
this.a * point.x + this.c * point.y + this.tx,
|
||
this.b * point.x + this.d * point.y + this.ty
|
||
);
|
||
},
|
||
deltaTransformPoint: function(point) {
|
||
return new Point(
|
||
this.a * point.x + this.c * point.y,
|
||
this.b * point.x + this.d * point.y
|
||
);
|
||
},
|
||
toStyle: function() {
|
||
var a = roundNumber(this.a, 3),
|
||
b = roundNumber(this.b, 3),
|
||
c = roundNumber(this.c, 3),
|
||
d = roundNumber(this.d, 3),
|
||
tx = roundNumber(this.tx, 3),
|
||
ty = roundNumber(this.ty, 3);
|
||
|
||
return 'matrix(' + a + ', ' + b + ', ' + c + ', ' + d + ', ' + tx +', ' + ty + ')';
|
||
},
|
||
|
||
};
|
||
|
||
var fn = function(a, b, c, d, tx, ty) {
|
||
return new Matrix(a, b, c, d, tx, ty);
|
||
};
|
||
|
||
fn.IDENTITY = new Matrix();
|
||
fn.HORIZONTAL_FLIP = new Matrix(-1, 0, 0, 1);
|
||
fn.VERTICAL_FLIP = new Matrix(1, 0, 0, -1);
|
||
|
||
return fn;
|
||
}());
|
||
|
||
/* RECT OBJECT */
|
||
|
||
var Rect = (function() {
|
||
function Rect(tl, tr, bl, br) {
|
||
this.tl = tl;
|
||
this.tr = tr;
|
||
this.bl = bl;
|
||
this.br = br;
|
||
}
|
||
|
||
Rect.prototype = {
|
||
applyMatrix: function(matrix, aboutPoint) {
|
||
var tl, tr, bl, br,
|
||
translateIn = new Matrix(1,0,0,1,aboutPoint.x,aboutPoint.y),
|
||
translateOut = new Matrix(1,0,0,1,-aboutPoint.x,-aboutPoint.y);
|
||
|
||
if (aboutPoint !== undefined) {
|
||
tl = this.tl.transform(translateOut).transform(matrix).transform(translateIn);
|
||
tr = this.tr.transform(translateOut).transform(matrix).transform(translateIn);
|
||
bl = this.bl.transform(translateOut).transform(matrix).transform(translateIn);
|
||
br = this.br.transform(translateOut).transform(matrix).transform(translateIn);
|
||
} else {
|
||
tl = this.tl.transform(matrix);
|
||
tr = this.tr.transform(matrix);
|
||
bl = this.bl.transform(matrix);
|
||
br = this.br.transform(matrix);
|
||
}
|
||
|
||
return new Rect(tl, tr, bl, br);
|
||
},
|
||
width: function() {
|
||
var dx = this.tl.x - this.tr.x;
|
||
var dy = this.tl.y - this.tr.y;
|
||
return Math.sqrt(dx*dx+dy*dy);
|
||
},
|
||
height: function() {
|
||
var dx = this.tl.x - this.bl.x;
|
||
var dy = this.tl.y - this.bl.y;
|
||
return Math.sqrt(dx*dx+dy*dy);
|
||
},
|
||
client: function() {
|
||
var top = Math.min(this.tl.y, this.tr.y, this.bl.y, this.br.y);
|
||
var left = Math.min(this.tl.x, this.tr.x, this.bl.x, this.br.x);
|
||
var height = Math.max(this.tl.y, this.tr.y, this.bl.y, this.br.y)-top;
|
||
var width = Math.max(this.tl.x, this.tr.x, this.bl.x, this.br.x)-left;
|
||
|
||
return {
|
||
top: roundNumber(top,1),
|
||
left: roundNumber(left,1),
|
||
height: roundNumber(height,1),
|
||
width: roundNumber(width,1),
|
||
bottom: roundNumber(top+height, 1),
|
||
right: roundNumber(left+width, 1)
|
||
};
|
||
},
|
||
getAngle: function(degs) {
|
||
var y = this.tl.y-this.tr.y;
|
||
var x = this.tl.x-this.tr.x;
|
||
|
||
return Math.tan(x/y)*180/Math.PI;
|
||
}
|
||
};
|
||
|
||
var fn = function(left, top, width, height) {
|
||
var args = arguments;
|
||
|
||
if (typeof args[0] === 'object') {
|
||
top = args[0].top;
|
||
left = args[0].left;
|
||
width = args[0].width;
|
||
height = args[0].height;
|
||
}
|
||
|
||
return new Rect(
|
||
new Point(left,top),
|
||
new Point(left+width,top),
|
||
new Point(left,top+height),
|
||
new Point(left+width,top+height)
|
||
);
|
||
};
|
||
|
||
fn.fromPoints = function(tl,tr,bl,br) {
|
||
return new Rect( tl,tr,bl,br );
|
||
};
|
||
|
||
return fn;
|
||
})();
|
||
|
||
/* JQLITE EXTENDING */
|
||
|
||
extend($.prototype, {
|
||
|
||
dndDisableSelection: function() {
|
||
this.on('dragstart selectstart', doFalse ).dndCss({
|
||
'-moz-user-select': 'none',
|
||
'-khtml-user-select': 'none',
|
||
'-webkit-user-select': 'none',
|
||
'-o-user-select': 'none',
|
||
'-ms-user-select': 'none',
|
||
'user-select': 'none'
|
||
});
|
||
},
|
||
|
||
dndEnableSelection: function() {
|
||
this.off('dragstart selectstart', doFalse ).dndCss({
|
||
'-moz-user-select': 'auto',
|
||
'-khtml-user-select': 'auto',
|
||
'-webkit-user-select': 'auto',
|
||
'-o-user-select': 'auto',
|
||
'-ms-user-select': 'auto',
|
||
'user-select': 'auto'
|
||
});
|
||
},
|
||
|
||
dndClientRect: function() {
|
||
if (!this[0]) {
|
||
return;
|
||
}
|
||
|
||
var DOMRect = this[0] === window ? {top:0,bottom:0,left:0,right:0,width:0,height:0} : this[0].getBoundingClientRect();
|
||
|
||
return {
|
||
bottom: DOMRect.bottom,
|
||
height: DOMRect.height,
|
||
left: DOMRect.left,
|
||
right: DOMRect.right,
|
||
top: DOMRect.top,
|
||
width: DOMRect.width,
|
||
};
|
||
},
|
||
|
||
dndStyleRect: function() {
|
||
var styles = this.dndCss(['width','height','top','left']);
|
||
|
||
var width = parseFloat(styles.width);
|
||
var height = parseFloat(styles.height);
|
||
|
||
var top = styles.top === 'auto' ? 0 : parseFloat(styles.top);
|
||
var left = styles.left === 'auto' ? 0 : parseFloat(styles.left);
|
||
|
||
return {top: top, right: left+width, bottom: top+height, left: left, width: width, height: height};
|
||
},
|
||
|
||
dndGetParentScrollArea: function() {
|
||
var ret = [], parents = this.dndClosest(), scrollX, clientX, scrollY, clientY, paddingX, paddingY, paddings, htmlEl = document.documentElement;
|
||
|
||
forEach(parents, function(element) {
|
||
|
||
paddings = $(element).dndCss(['padding-top', 'padding-right', 'padding-bottom', 'padding-left']);
|
||
|
||
scrollX = element.scrollWidth;
|
||
clientX = element.clientWidth;
|
||
scrollY = element.scrollHeight;
|
||
clientY = element.clientHeight;
|
||
|
||
paddingY = parseFloat(paddings['padding-top']) + parseFloat(paddings['padding-bottom']);
|
||
paddingX = parseFloat(paddings['padding-left']) + parseFloat(paddings['padding-right']);
|
||
|
||
if ( scrollX - paddingX !== clientX || scrollY - paddingY !== clientY ) {
|
||
ret.push(element);
|
||
}
|
||
});
|
||
|
||
ret.push(window);
|
||
|
||
return $(ret);
|
||
},
|
||
|
||
dndGetFirstNotStaticParent: function() {
|
||
var ret, position, parents = this.dndClosest();
|
||
|
||
forEach(parents, function(element) {
|
||
|
||
position = $(element).dndCss('position');
|
||
|
||
if ( position === 'absolute' || position === 'relative' || position === 'fixed' ) {
|
||
ret = element;
|
||
return false;
|
||
}
|
||
});
|
||
|
||
if (!ret) {
|
||
ret = document.documentElement;
|
||
}
|
||
|
||
return $(ret);
|
||
},
|
||
|
||
dndClosest: function(selector) {
|
||
selector = selector || '*';
|
||
var parent = this[0], ret = [];
|
||
|
||
while(parent) {
|
||
parent[MATCHES_SELECTOR](selector) && ret.push(parent);
|
||
parent = parent.parentElement;
|
||
}
|
||
|
||
return $(ret);
|
||
},
|
||
dndGetAngle: function (degs) {
|
||
var matrix = this.dndCss(TRANSFORM);
|
||
|
||
if (matrix === 'none' || matrix === '') {
|
||
return 0;
|
||
}
|
||
|
||
var values = matrix.split('(')[1].split(')')[0].split(','),
|
||
a = values[0], b = values[1], rads = Math.atan2(b, a);
|
||
|
||
rads = rads < 0 ? rads +=Math.PI*2 : rads;
|
||
|
||
return degs ? Math.round(rads * 180/Math.PI) : rads;
|
||
},
|
||
|
||
dndCloneByStyles: function () {
|
||
var ret = [];
|
||
|
||
for (var i = 0, length = this.length; i < length; i++) {
|
||
var node = this[i].cloneNode();
|
||
|
||
angular.element(node).append(angular.element(this[0].childNodes).dndCloneByStyles());
|
||
|
||
if (this[i].nodeType === 1) {
|
||
node.style.cssText = window.getComputedStyle(this[i], "").cssText;
|
||
}
|
||
|
||
ret.push(node);
|
||
}
|
||
|
||
return angular.element(ret);
|
||
},
|
||
|
||
dndCss: (function() {
|
||
var SPECIAL_CHARS_REGEXP = /([\:\-\_\.]+(.))/g,
|
||
MOZ_HACK_REGEXP = /^moz([A-Z])/, hooks = {};
|
||
|
||
function toCamelCase(string) {
|
||
return string.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
|
||
return offset ? letter.toUpperCase() : letter;
|
||
}).replace(MOZ_HACK_REGEXP, 'Moz$1');
|
||
}
|
||
|
||
(function() {
|
||
var arr = {
|
||
width: ['paddingLeft','paddingRight','borderLeftWidth', 'borderRightWidth'],
|
||
height: ['paddingTop','paddingBottom','borderTopWidth', 'borderBottomWidth']
|
||
};
|
||
|
||
forEach(arr, function(styles, prop) {
|
||
|
||
hooks[prop] = {
|
||
get: function(element) {
|
||
var computed = window.getComputedStyle(element);
|
||
var ret = computed[prop];
|
||
|
||
if ( computed.boxSizing !== 'border-box' || ret[ret.length-1] === '%') {
|
||
return ret;
|
||
}
|
||
|
||
ret = parseFloat(ret);
|
||
|
||
for(var i = 0; i < styles.length; i++) {
|
||
ret -= parseFloat(computed[ styles[i] ]);
|
||
}
|
||
|
||
return ret + 'px';
|
||
}
|
||
};
|
||
|
||
});
|
||
|
||
})();
|
||
|
||
var cssNumber = {
|
||
'columnCount': true,
|
||
'fillOpacity': true,
|
||
'fontWeight': true,
|
||
'lineHeight': true,
|
||
'opacity': true,
|
||
'order': true,
|
||
'orphans': true,
|
||
'widows': true,
|
||
'zIndex': true,
|
||
'zoom': true
|
||
};
|
||
|
||
var setCss = function($element, obj) {
|
||
var styles = {};
|
||
|
||
for(var key in obj) {
|
||
var val = obj[key];
|
||
|
||
if ( typeof val === 'number' && !cssNumber[key] ) {
|
||
val += 'px';
|
||
}
|
||
|
||
styles[toCamelCase(key)] = val;
|
||
}
|
||
|
||
$element.css(styles);
|
||
|
||
return $element;
|
||
};
|
||
|
||
var getCss = function($element, arg) {
|
||
var ret = {};
|
||
|
||
if (!$element[0]) {
|
||
return undefined;
|
||
}
|
||
|
||
var style = $element[0].style;
|
||
var computed = window.getComputedStyle( $element[0], null );
|
||
|
||
if (typeof arg === 'string') {
|
||
if (style[arg]) {
|
||
return style[arg];
|
||
} else {
|
||
return hooks[arg] && 'get' in hooks[arg] ? hooks[arg].get($element[0]) : computed.getPropertyValue( arg );
|
||
}
|
||
}
|
||
|
||
for(var i=0; i < arg.length; i++) {
|
||
if (style[arg[i]]) {
|
||
ret[arg[i]] = style[ arg[i] ];
|
||
} else {
|
||
ret[arg[i]] = hooks[arg[i]] && 'get' in hooks[arg[i]] ? hooks[arg[i]].get($element[0]) : computed.getPropertyValue( arg[i] );
|
||
}
|
||
}
|
||
|
||
return ret;
|
||
};
|
||
|
||
function css() {
|
||
var a = arguments;
|
||
|
||
if ( (a.length === 1) && ((a[0] instanceof Array) || (typeof a[0] === 'string')) ) {
|
||
return getCss(this, a[0]);
|
||
} else if ( (a.length === 1) && (typeof a[0] === 'object') ) {
|
||
return setCss(this, a[0]);
|
||
} else if ( a.length === 2 ) {
|
||
var obj = {};
|
||
|
||
obj[a[0]] = a[1];
|
||
|
||
return setCss(this, obj);
|
||
}
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
return css;
|
||
})()
|
||
});
|
||
|
||
/* INIT ANGULAR MODULE */
|
||
|
||
var module = angular.module('dnd', []);
|
||
|
||
/* ANGULAR.ELEMENT DND PLUGIN - CORE OF ANGULAR-DND */
|
||
|
||
(function() {
|
||
|
||
var Regions = (function() {
|
||
var list = {};
|
||
|
||
function Regions(layer) {
|
||
if (!list[layer]) {
|
||
list[layer] = [];
|
||
}
|
||
|
||
this.layer = function() {
|
||
return layer;
|
||
};
|
||
}
|
||
|
||
Regions.prototype = {
|
||
get: function() {
|
||
return list[this.layer()];
|
||
},
|
||
|
||
remove: function(el) {
|
||
var index = this.get().indexOf(el);
|
||
|
||
if (index > -1) {
|
||
this.get().splice(index,1);
|
||
}
|
||
},
|
||
|
||
has: function(el) {
|
||
return this.get().indexOf(el) > -1;
|
||
},
|
||
|
||
add: function(el) {
|
||
if (this.has(el)) {
|
||
return;
|
||
}
|
||
|
||
this.get().push(el);
|
||
|
||
var self = this;
|
||
|
||
$(el).on('$destroy', function() { self.remove(el); });
|
||
},
|
||
|
||
};
|
||
|
||
return Regions;
|
||
})();
|
||
|
||
var Dnd = (function() {
|
||
|
||
var events = [ 'dragstart', 'drag', 'dragend', 'dragenter', 'dragover', 'dragleave', 'drop' ];
|
||
var draggables = [ 'dragstart', 'drag', 'dragend' ];
|
||
var droppables = [ 'dragenter', 'dragover', 'dragleave', 'drop' ];
|
||
var handled = false;
|
||
|
||
draggables.has = droppables.has = function(event) {
|
||
return this.indexOf(event) > -1;
|
||
};
|
||
|
||
var touchevents;
|
||
if ('ontouchstart' in document.documentElement) {
|
||
touchevents = {start: 'touchstart', move: 'touchmove', end: 'touchend', cancel: 'touchcancel'};
|
||
} else if ('pointerEnabled' in window.navigator) {
|
||
touchevents = {start: 'pointerdown', move: 'pointermove', end: 'pointerup', cancel: 'pointercancel'};
|
||
} else if ('msPointerEnabled' in window.navigator) {
|
||
touchevents = {start: 'MSPointerDown', move: 'MSPointerMove', end: 'MSPointerUp', cancel: 'MSPointerCancel'};
|
||
} else {
|
||
touchevents = {start: 'touchstart', move: 'touchmove', end: 'touchend', cancel: 'touchcancel'};
|
||
}
|
||
|
||
function Dnd(el, layer) {
|
||
this.el = el;
|
||
this.$el = $(el);
|
||
this.listeners = { 'dragstart':[], 'drag':[], 'dragend':[], 'dragenter':[], 'dragover':[], 'dragleave':[], 'drop':[] };
|
||
this.regions = new Regions(layer);
|
||
this.layer = function() {
|
||
return layer;
|
||
};
|
||
this.setCurrentManipulator(null);
|
||
}
|
||
|
||
Dnd.prototype = {
|
||
_isEmptyListeners: function(event) {
|
||
if (event instanceof Array) {
|
||
|
||
for(var i=0; i < event.length; i++ ) {
|
||
if (!this._isEmptyListeners(event[i])) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
} else if (this.listeners[event].length > 0) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
},
|
||
|
||
addListener: function(event, handler) {
|
||
if (events.indexOf(event) === -1) {
|
||
console.error('jquery.dnd: invalid event name - ', event);
|
||
return this;
|
||
}
|
||
this.listeners[event].push( handler);
|
||
|
||
if ( droppables.has(event) ) {
|
||
this.regions.add(this.el);
|
||
} else if (draggables.has(event) && !this.mouse && !this.touch) {
|
||
if ('onmousedown' in window) {
|
||
this.mouse = new Mouse(this);
|
||
}
|
||
if ( ('ontouchstart' in window) || ('onmsgesturechange' in window) ) {
|
||
this.touch = new Touch(this, touchevents);
|
||
}
|
||
|
||
}
|
||
|
||
return this;
|
||
},
|
||
|
||
removeListener: function(event, handler) {
|
||
var args = arguments;
|
||
|
||
if (args.length === 0) {
|
||
for( var key in this.listeners ) {
|
||
this.listeners[key].length = 0;
|
||
}
|
||
} else if (args.length === 1) {
|
||
this.listeners[event].length = 0;
|
||
} else {
|
||
var listeners = this.listeners[event];
|
||
|
||
for(var i=0; i < listeners.length; i++) {
|
||
if ( listeners[i] === handler ) listeners[event].splice(i,1);
|
||
}
|
||
|
||
}
|
||
|
||
if ( this._isEmptyListeners(droppables) ) this.regions.remove(this.el);
|
||
else if ( this._isEmptyListeners(draggables)) this.destroy();
|
||
|
||
return this;
|
||
},
|
||
|
||
trigger: function(event, api, el) {
|
||
for(var i=0; i < this.listeners[event].length; i++) {
|
||
this.listeners[event][i].call(this.$el, api, el);
|
||
}
|
||
|
||
return this;
|
||
},
|
||
|
||
destroy: function() {
|
||
if ( this.mouse ) {
|
||
this.mouse.destroy();
|
||
delete this.mouse;
|
||
}
|
||
|
||
if ( this.touch ) {
|
||
this.touch.destroy();
|
||
delete this.touch;
|
||
}
|
||
|
||
return this;
|
||
},
|
||
|
||
setCurrentManipulator: function (manipulator) {
|
||
this._manipulator = manipulator;
|
||
|
||
return this;
|
||
},
|
||
|
||
getCurrentManipulator: function () {
|
||
return this._manipulator;
|
||
}
|
||
};
|
||
|
||
|
||
return Dnd;
|
||
})();
|
||
|
||
var Api = (function() {
|
||
|
||
function Api(manipulator) {
|
||
this._manipulator = manipulator;
|
||
}
|
||
|
||
Api.prototype = {
|
||
getAxis: function() {
|
||
return this._manipulator.getClientAxis.apply(this._manipulator, arguments);
|
||
},
|
||
getBorderedAxis: function() {
|
||
return this._manipulator.getBorderedAxis.apply(this._manipulator, arguments);
|
||
},
|
||
getRelBorderedAxis: function() {
|
||
return this._manipulator.getRelBorderedAxis.apply(this._manipulator, arguments);
|
||
},
|
||
getDragTarget: function() {
|
||
return this._manipulator.dnd.el;
|
||
},
|
||
getDropTarget: function() {
|
||
return this._manipulator.target;
|
||
},
|
||
getEvent: function() {
|
||
return this._manipulator.event;
|
||
},
|
||
isTarget: function() {
|
||
return this._manipulator.isTarget.apply(this._manipulator, arguments);
|
||
},
|
||
unTarget: function() {
|
||
this._manipulator.removeFromTargets();
|
||
},
|
||
useAsPoint: function(value) {
|
||
return this._manipulator.asPoint = value === false ? false : true;
|
||
},
|
||
setBounderElement: function(node) {
|
||
this._manipulator.$bounder = angular.element(node);
|
||
this.clearCache();
|
||
},
|
||
setReferenceElement: function(node) {
|
||
this._manipulator.$reference = angular.element(node);
|
||
},
|
||
getBorders: function() {
|
||
return this._manipulator.getBorders.apply(this._manipulator, arguments);
|
||
},
|
||
getReferencePoint: function() {
|
||
return this._manipulator.getReferencePoint.apply(this._manipulator, arguments);
|
||
},
|
||
clearCache: function() {
|
||
this._manipulator.clearCache.apply(this._manipulator, arguments);
|
||
}
|
||
};
|
||
|
||
return Api;
|
||
|
||
})();
|
||
|
||
var Manipulator = (function() {
|
||
var targets = [];
|
||
|
||
function Manipulator(dnd) {
|
||
this.dnd = dnd;
|
||
this.onscroll = proxy(this, this.onscroll);
|
||
}
|
||
|
||
Manipulator.prototype = {
|
||
|
||
getBorders: function(offset) {
|
||
if (!this.$bounder) {
|
||
return;
|
||
}
|
||
|
||
var borders = this.getCache('borders');
|
||
|
||
if (!borders) {
|
||
var rect = this.$bounder.dndClientRect();
|
||
|
||
borders = this.setCache('borders', {
|
||
top: rect.top,
|
||
left: rect.left,
|
||
right: rect.right,
|
||
bottom: rect.bottom
|
||
});
|
||
}
|
||
|
||
return {
|
||
top: borders.top + (offset ? offset.top : 0),
|
||
left: borders.left + (offset ? offset.left : 0),
|
||
right: borders.right + (offset ? offset.right : 0),
|
||
bottom: borders.bottom + (offset ? offset.bottom : 0)
|
||
};
|
||
},
|
||
|
||
getReferencePoint: function() {
|
||
var referencePoint = this.getCache('referencePoint');
|
||
|
||
if (!referencePoint) {
|
||
var rect = this.$reference.dndClientRect();
|
||
|
||
referencePoint = this.setCache('referencePoint', new Point(rect.left, rect.top));
|
||
}
|
||
|
||
return referencePoint;
|
||
},
|
||
|
||
getBorderedAxis: function(borderOffset, axisOffset) {
|
||
var axis = this.getClientAxis(axisOffset);
|
||
var borders = this.getBorders(borderOffset);
|
||
|
||
var result = borders ? new Point(
|
||
getNumFromSegment(borders.left, axis.x, borders.right),
|
||
getNumFromSegment(borders.top, axis.y, borders.bottom)
|
||
) : axis;
|
||
|
||
return result;
|
||
},
|
||
|
||
getRelBorderedAxis: function(borderOffset, axisOffset) {
|
||
return this.getBorderedAxis(borderOffset, axisOffset).minus( this.getReferencePoint() );
|
||
},
|
||
|
||
addToTargets: function() {
|
||
targets.push(this);
|
||
},
|
||
|
||
removeFromTargets: function() {
|
||
var index;
|
||
|
||
while(index !== -1) {
|
||
index = targets.indexOf(this);
|
||
if (index > -1) {
|
||
targets.splice(index, 1);
|
||
}
|
||
}
|
||
},
|
||
|
||
getTarget: function() {
|
||
return targets[0];
|
||
},
|
||
|
||
isTarget: function() {
|
||
return this.getTarget() === this;
|
||
},
|
||
|
||
start: function() {
|
||
this.started = true;
|
||
this.targets = [];
|
||
this.asPoint = false;
|
||
this.api = new Api(this);
|
||
this.$scrollareas = this.dnd.$el.dndGetParentScrollArea();
|
||
this.$reference = this.dnd.$el.dndGetFirstNotStaticParent();
|
||
this.$scrollareas.on('scroll', this.onscroll);
|
||
this.dnd.trigger('dragstart', this.api);
|
||
},
|
||
|
||
onscroll: function() {
|
||
this.clearCache();
|
||
this.dnd.trigger('drag', this.api);
|
||
},
|
||
|
||
getCache: function(key) {
|
||
return this.cache[key];
|
||
},
|
||
|
||
setCache: function(key, value) {
|
||
return this.cache[key] = value;
|
||
},
|
||
|
||
clearCache: function() {
|
||
this.cache = {};
|
||
},
|
||
|
||
stop: function() {
|
||
this.$scrollareas.off ('scroll', this.onscroll);
|
||
|
||
if (this.targets.length) {
|
||
for(var i = 0, length = this.targets.length; i < length; i++) {
|
||
$(this.targets[i]).data('dnd')[this.dnd.layer()].trigger('drop', this.api, this.dnd.el);
|
||
}
|
||
}
|
||
|
||
this.dnd.trigger('dragend', this.api, this.targets);
|
||
},
|
||
|
||
|
||
prepareRegions: function() {
|
||
var regions = this.dnd.regions.get();
|
||
|
||
var ret = [];
|
||
|
||
for(var key in regions) {
|
||
var dnd = $( regions[key] ).data('dnd')[this.dnd.layer()];
|
||
var rect = dnd.$el.dndClientRect();
|
||
|
||
if (this.dnd === dnd) {
|
||
continue;
|
||
}
|
||
|
||
ret.push({
|
||
dnd: dnd,
|
||
rect: rect
|
||
});
|
||
}
|
||
|
||
return ret;
|
||
|
||
},
|
||
|
||
begin: function (event) {
|
||
if (this.dnd.getCurrentManipulator() ||
|
||
$(event.target).dndClosest('[dnd-pointer-none]').length) {
|
||
return false;
|
||
}
|
||
|
||
this.dnd.setCurrentManipulator(this);
|
||
|
||
this.addToTargets();
|
||
this.event = event;
|
||
this.started = false;
|
||
this.clearCache();
|
||
angular.element(document.body).dndDisableSelection();
|
||
|
||
return true;
|
||
},
|
||
|
||
progress: function (event) {
|
||
this.event = event;
|
||
|
||
if (!this.started) {
|
||
this.start();
|
||
}
|
||
|
||
var regions = this.getCache('regions');
|
||
|
||
if (!regions) {
|
||
regions = this.setCache('regions', this.prepareRegions());
|
||
if (debug.mode) {
|
||
this.showRegioins();
|
||
}
|
||
}
|
||
|
||
this.dnd.trigger('drag', this.api);
|
||
|
||
var axis = this.getBorderedAxis(), x = axis.x, y = axis.y, asPoint = this.asPoint;
|
||
var dragenter = [];
|
||
var dragover = [];
|
||
var dragleave = [];
|
||
|
||
for(var i = 0; i < regions.length; i++) {
|
||
var region = regions[i],
|
||
left = region.rect.left,
|
||
right = left + region.rect.width,
|
||
top = region.rect.top,
|
||
bottom = top + region.rect.height,
|
||
trigger = (x > left ) && (x < right) && (y > top) && (y < bottom),
|
||
targetIndex = this.targets.indexOf(region.dnd.el);
|
||
|
||
if ( trigger ) {
|
||
if (targetIndex === -1) {
|
||
this.targets.push(region.dnd.el);
|
||
dragenter.push(region.dnd);
|
||
} else {
|
||
dragover.push(region.dnd);
|
||
}
|
||
} else if (targetIndex !== -1) {
|
||
dragleave.push($(this.targets[targetIndex]).data('dnd')[this.dnd.layer()]);
|
||
this.targets.splice(targetIndex, 1);
|
||
}
|
||
}
|
||
|
||
this._triggerArray(dragleave, 'dragleave');
|
||
this._triggerArray(dragenter, 'dragenter');
|
||
this._triggerArray(dragover, 'dragover');
|
||
},
|
||
|
||
_triggerArray: function (arr, event) {
|
||
for (var i = 0; i < arr.length; i++) {
|
||
arr[i].trigger(event, this.api, this.dnd.el);
|
||
}
|
||
},
|
||
|
||
end: function (event) {
|
||
this.event = event;
|
||
|
||
if (this.started) {
|
||
this.stop();
|
||
}
|
||
|
||
angular.element(document.body).dndEnableSelection();
|
||
|
||
this.removeFromTargets();
|
||
|
||
debug.mode && this.hideRegions();
|
||
|
||
this.dnd.setCurrentManipulator(null);
|
||
},
|
||
|
||
showRegioins: function () {
|
||
this.hideRegions();
|
||
|
||
var regions = this.getCache('regions'),
|
||
bodyElement = angular.element(document.body),
|
||
bodyClientRect = bodyElement.dndClientRect();
|
||
|
||
for (var i = 0, length = regions.length; i < length; i++) {
|
||
var region = regions[i];
|
||
|
||
debug.helpers.renderRect(
|
||
region.rect.left - bodyClientRect.left,
|
||
region.rect.top - bodyClientRect.top,
|
||
region.rect.width,
|
||
region.rect.height
|
||
);
|
||
}
|
||
},
|
||
|
||
hideRegions: function () {
|
||
var nodes = document.querySelectorAll('.dnd-debug-rect');
|
||
|
||
for (var i = 0, length = nodes.length; i < length; i++) {
|
||
nodes[i].remove();
|
||
}
|
||
}
|
||
};
|
||
|
||
return Manipulator;
|
||
})();
|
||
|
||
function Mouse(dnd) {
|
||
this.dnd = dnd;
|
||
this.manipulator = new Manipulator(dnd);
|
||
this.mousedown = proxy(this, this.mousedown);
|
||
this.mousemove = proxy(this, this.mousemove);
|
||
this.mouseup = proxy(this, this.mouseup);
|
||
this.manipulator.getClientAxis = this.getClientAxis;
|
||
|
||
dnd.$el.on('mousedown', this.mousedown);
|
||
}
|
||
|
||
Mouse.prototype = {
|
||
|
||
getClientAxis: function(offset) {
|
||
return new Point(this.event.clientX + (offset ? offset.x : 0), this.event.clientY + (offset ? offset.y : 0));
|
||
},
|
||
|
||
mousedown: function (event) {
|
||
if (!this.manipulator.begin(event)) {
|
||
return;
|
||
}
|
||
|
||
$document.on('mousemove', this.mousemove );
|
||
$document.on('mouseup', this.mouseup );
|
||
},
|
||
|
||
mousemove: function(event) {
|
||
this.manipulator.progress(event);
|
||
},
|
||
|
||
mouseup: function(event) {
|
||
this.manipulator.end(event);
|
||
|
||
$document.off('mousemove', this.mousemove );
|
||
$document.off('mouseup', this.mouseup );
|
||
|
||
this.dnd.setCurrentManipulator(null);
|
||
},
|
||
|
||
destroy: function() {
|
||
this.dnd.$el.off('mousedown', this.mousedown);
|
||
},
|
||
};
|
||
|
||
|
||
function Touch(dnd, te) {
|
||
this.dnd = dnd;
|
||
this.te = te;
|
||
this.manipulator = new Manipulator(dnd);
|
||
this.touchstart = proxy(this, this.touchstart);
|
||
this.touchmove = proxy(this, this.touchmove);
|
||
this.touchend = proxy(this, this.touchend);
|
||
this.manipulator.getClientAxis = this.getClientAxis;
|
||
|
||
dnd.$el.on(this.te.start, this.touchstart);
|
||
}
|
||
|
||
Touch.prototype = {
|
||
|
||
getClientAxis: function(offset) {
|
||
var event = this.event.originalEvent || this.event;
|
||
|
||
return event.changedTouches ?
|
||
Point(event.changedTouches[0].clientX + (offset ? offset.x : 0), event.changedTouches[0].clientY + (offset ? offset.y : 0)) :
|
||
Point(event.clientX + (offset ? offset.x : 0), event.clientY + (offset ? offset.y : 0));
|
||
},
|
||
|
||
touchstart: function (event) {
|
||
if (!this.manipulator.begin(event)) {
|
||
return;
|
||
}
|
||
|
||
$document.on(this.te.move, this.touchmove );
|
||
$document.on(this.te.end + ' ' + this.te.cancel, this.touchend );
|
||
},
|
||
|
||
touchmove: function(event) {
|
||
event.preventDefault();
|
||
|
||
this.manipulator.progress(event);
|
||
},
|
||
|
||
touchend: function(event) {
|
||
this.manipulator.end(event);
|
||
|
||
$document.off(this.te.move, this.touchmove );
|
||
$document.off(this.te.end + ' ' + this.te.cancel, this.touchend );
|
||
this.dnd.setCurrentManipulator(null);
|
||
},
|
||
|
||
destroy: function() {
|
||
this.dnd.$el.off(this.te.start, this.touchstart);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @name angular.element.dndBind
|
||
*
|
||
* @description
|
||
* Аналог jQuery.fn.bind(), только для drag and drop событий
|
||
*
|
||
* События также могут быть в формате <layer.event>,
|
||
* но в отличие от jQuery.fn.bind() в нашем случае layer позволяет не только групировать обработчики,
|
||
* но также и отделять области для droppable и draggable элементов. Поясним.
|
||
* Дело в том, что при определении событий элемент не явным образом приписывается к определенной области видимости (layer),
|
||
* причем один элемент может одновременно находится в нескольких областях.
|
||
* Это означает, что для того, чтобы на элемент срабатывали droppable события, он должен находится в layer draggable элемента.
|
||
* По умолчанию, если layer не задан в наименовании обаботчика события, то эта область именуется 'common',
|
||
* т.е. события drop и common.drop идентичны и находятся в одной и той же области
|
||
*
|
||
* ! Элемент не явным образом считается draggable-элементом, если у него задано одно или несколько событий dragstart, drag или dragend
|
||
* ! Элемент не явным образом считается droppable-элементом, если у него задано одно или несколько событий dragenter, dragover, dragleave или drop
|
||
*
|
||
* @param {object|string} event
|
||
* Если object, то необходимо описать пары <event name>:<callback function>.
|
||
* Если string, то определяется только <event name> причем возможно задать несколько событий через пробел, например <dragstart drag leftside.dragend>
|
||
* @param {function} handler
|
||
* Если arg1 это string, то arg2 это callback, который будет вызван после наступления события.
|
||
* @returns {object} angular.element object.
|
||
*/
|
||
|
||
$.prototype.dndBind = function ( event, handler ) {
|
||
|
||
if (!this.length) {
|
||
return this;
|
||
}
|
||
|
||
var opts = [], events, obj, layer, self = this;
|
||
|
||
if (typeof event === 'object') {
|
||
obj = event;
|
||
|
||
for(var key in obj) {
|
||
events = key.replace(/\s+/g, ' ').split(' ');
|
||
|
||
for(var i=0; i < events.length; i++) {
|
||
opts.push({
|
||
event: events[i],
|
||
handler: obj[key]
|
||
});
|
||
}
|
||
}
|
||
|
||
} else if (typeof event === 'string' && typeof handler === 'function') {
|
||
events = event.trim().replace(/\s+/g, ' ').split(' ');
|
||
|
||
for(var i=0; i < events.length; i++) {
|
||
opts.push({
|
||
event: events[i],
|
||
handler: handler
|
||
});
|
||
}
|
||
|
||
} else {
|
||
return this;
|
||
}
|
||
|
||
if (!opts.length) {
|
||
return this;
|
||
}
|
||
|
||
forEach(this, function(element) {
|
||
var data = $(element).data();
|
||
|
||
if (!data.dnd) {
|
||
data.dnd = {};
|
||
}
|
||
|
||
for(var i=0; i < opts.length; i++) {
|
||
event = opts[i].event;
|
||
handler = opts[i].handler;
|
||
|
||
event = event.split('.');
|
||
|
||
if (event[1] === undefined) {
|
||
event[1] = event[0];
|
||
event[0] = 'common';
|
||
}
|
||
|
||
layer = event[0];
|
||
event = event[1];
|
||
|
||
if (!data.dnd[layer]) {
|
||
data.dnd[layer] = new Dnd(element, layer);
|
||
}
|
||
|
||
data.dnd[layer].addListener(event, handler);
|
||
}
|
||
});
|
||
|
||
return this;
|
||
};
|
||
|
||
|
||
/**
|
||
* @name angular.element.dndUnbind
|
||
*
|
||
* @description
|
||
* Аналог jQuery.fn.unbind(), только для drag and drop событий
|
||
*
|
||
* @param {(object=|string=)} arg1 Если не object и не string, то удаляются все обработчики с каждого слоя
|
||
* Если object, то будут удалены callbacks события которые заданы в виде ключа и
|
||
* @param {(function=|string=)} arg2
|
||
* Если arg1 это string, то arg2 это callback, который будет вызван после наступления события.
|
||
* Если arg1 это object, то arg2 это string которая определяет имя используемого слоя.
|
||
* @param {string=} arg3
|
||
* Если задан arg1 и arg2, то arg3 это string которая определяет имя используемого слоя
|
||
* @returns {object} angular.element object.
|
||
*/
|
||
|
||
$.prototype.dndUnbind = function() {
|
||
|
||
var args = arguments, events = [], default_layer = 'common';
|
||
|
||
if (!this.length) {
|
||
return this;
|
||
}
|
||
|
||
if (typeof args[0] === 'string') {
|
||
|
||
args[0] = args[0].trim().replace(/\s+/g, ' ').split(' ');
|
||
|
||
if (typeof args[1] === 'function') {
|
||
|
||
for(var i = 0; i < args[0].length; i++) {
|
||
events.push({
|
||
event: args[0][i],
|
||
handler: args[1]
|
||
});
|
||
}
|
||
|
||
} else {
|
||
for(var i = 0; i < args[0].length; i++) {
|
||
events.push({
|
||
event: args[0][i]
|
||
});
|
||
}
|
||
}
|
||
|
||
} else if ( typeof args[0] === 'object') {
|
||
|
||
for(var key in args[0]) {
|
||
if (args[0].hasOwnProperty(key)) {
|
||
|
||
events.push({
|
||
event: key.trim(),
|
||
handler: args[0][key]
|
||
});
|
||
|
||
}
|
||
}
|
||
|
||
} else if (args.length !== 0) {
|
||
return this;
|
||
}
|
||
|
||
forEach(this, function(element) {
|
||
var data = $(element).data();
|
||
|
||
if (!data.dnd) {
|
||
return;
|
||
}
|
||
|
||
if (args.length === 0) {
|
||
|
||
for(var key in data.dnd) {
|
||
data.dnd[key].removeListener();
|
||
}
|
||
|
||
} else {
|
||
|
||
for(var i = 0; i < events.length; i++) {
|
||
var obj = events[i];
|
||
|
||
obj.event = obj.event.split('.');
|
||
|
||
if (obj.event[1] === undefined) {
|
||
obj.event[1] = obj.event[0];
|
||
obj.event[0] = default_layer;
|
||
}
|
||
|
||
if (obj.event[0] === '*') {
|
||
for(var key in data.dnd) {
|
||
data.dnd[key].removeListener( obj.event[1] );
|
||
}
|
||
|
||
} else if (data.dnd[ obj.event[0] ]) {
|
||
obj.handler ? data.dnd[ obj.event[0] ].removeListener( obj.event[1], obj.handler ) : data.dnd[ obj.event[0] ].removeListener( obj.event[1] );
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
return this;
|
||
};
|
||
|
||
})();
|
||
|
||
/* DEBUG HELPERS */
|
||
|
||
debug.helpers.renderPoint = function (point) {
|
||
var element = angular.element(document.createElement('div'));
|
||
|
||
element.dndCss({
|
||
position: 'absolute',
|
||
left: point.x,
|
||
top: point.y,
|
||
height: 3,
|
||
width: 3,
|
||
background: 'rgba(0, 0, 0, 0.5)',
|
||
'pointer-events': 'none',
|
||
'z-index': 100000
|
||
});
|
||
|
||
element.addClass('dnd-debug-point');
|
||
|
||
angular.element(document.body).append(element);
|
||
};
|
||
|
||
debug.helpers.renderRect = function (left, top, width, height) {
|
||
var element = angular.element(document.createElement('div'));
|
||
|
||
element.dndCss({
|
||
position: 'absolute',
|
||
left: left,
|
||
top: top,
|
||
height: height,
|
||
width: width,
|
||
background: 'rgba(249, 255, 0, 0.1)',
|
||
'pointer-events': 'none',
|
||
'z-index': 100000,
|
||
'box-sizing': 'border-box',
|
||
'border': '2px dotted #000'
|
||
});
|
||
|
||
element.addClass('dnd-debug-rect');
|
||
|
||
angular.element(document.body).append(element);
|
||
};
|
||
|
||
angular.dnd = {
|
||
version: version,
|
||
noop: noop,
|
||
doTrue: doTrue,
|
||
doFalse: doFalse,
|
||
proxy: proxy,
|
||
radToDeg: radToDeg,
|
||
degToRad: degToRad,
|
||
getNumFromSegment: getNumFromSegment,
|
||
findEvents: findEvents,
|
||
throttle: throttle,
|
||
debounce: debounce,
|
||
debug: debug
|
||
};
|
||
;
|
||
|
||
module.directive('dndDraggable', ['$timeout', '$parse', '$http', '$compile', '$q', '$templateCache', 'EventEmitter',
|
||
function ($timeout, $parse, $http, $compile, $q, $templateCache, EventEmitter) {
|
||
|
||
var ElementTarget = (function () {
|
||
|
||
function ElementTarget(element, rect) {
|
||
|
||
var cssPosition = element.dndCss('position');
|
||
|
||
if (cssPosition !== 'fixed' && cssPosition !== 'absolute' && cssPosition !== 'relative') {
|
||
cssPosition = 'relative';
|
||
element.dndCss('position', cssPosition);
|
||
}
|
||
|
||
this.element = element;
|
||
|
||
this.rect = rect;
|
||
}
|
||
|
||
ElementTarget.prototype = {
|
||
initBorderOffset: function () {
|
||
var axis = this.api.getBorderedAxis();
|
||
var crect = this.element.dndClientRect();
|
||
|
||
this.borderOffset = {
|
||
top: axis.y - crect.top,
|
||
left: axis.x - crect.left,
|
||
bottom: axis.y - crect.top - crect.height,
|
||
right: axis.x - crect.left - crect.width
|
||
};
|
||
},
|
||
|
||
init: function (api) {
|
||
this.api = api;
|
||
delete this.start;
|
||
this.initBorderOffset();
|
||
},
|
||
|
||
updatePosition: function () {
|
||
var axis = this.api.getRelBorderedAxis(this.borderOffset);
|
||
|
||
if (!this.start) {
|
||
this.start = new Point(this.element.dndStyleRect()).minus(axis);
|
||
}
|
||
|
||
var position = new Point(this.start).plus(axis);
|
||
|
||
this.rect ? this.rect.update( position.getAsCss() ) : this.element.dndCss( position.getAsCss() );
|
||
},
|
||
|
||
destroy: function () {},
|
||
};
|
||
|
||
return ElementTarget;
|
||
})();
|
||
|
||
var HelperTarget = (function () {
|
||
var wrapper = $('<div class = "angular-dnd-draggable-helper"></div>').dndCss({position: 'absolute'});
|
||
|
||
function HelperTarget(mainNode, templateUrl, scope) {
|
||
this.mainElement = angular.element(mainNode);
|
||
this.scope = scope;
|
||
this.templateUrl = templateUrl;
|
||
|
||
if (templateUrl !== 'clone') {
|
||
this.createTemplateByUrl(templateUrl);
|
||
} else {
|
||
this.ready = true;
|
||
}
|
||
}
|
||
|
||
HelperTarget.prototype = {
|
||
|
||
init: function (api) {
|
||
delete this.start;
|
||
delete this.element;
|
||
this.api = api;
|
||
this.ready = false;
|
||
|
||
this.templateUrl === 'clone' ? this.createElementByClone() : this.createElementByTemplate();
|
||
|
||
this.wrap().appendTo($(document.body));
|
||
|
||
this.scope.$apply();
|
||
|
||
api.setReferenceElement(document.body);
|
||
this.initBorderOffset();
|
||
|
||
return this;
|
||
},
|
||
|
||
createTemplateByUrl: function (templateUrl) {
|
||
templateUrl = angular.isFunction (templateUrl) ? templateUrl() : templateUrl;
|
||
|
||
return $http.get(templateUrl, {cache: $templateCache}).then(function (result) {
|
||
this.template = result.data;
|
||
this._offset = Point();
|
||
this.ready = true;
|
||
}.bind(this));
|
||
},
|
||
|
||
createElementByClone: function () {
|
||
this.element = this.mainElement.dndCloneByStyles().dndCss('position', 'static');
|
||
this._offset = Point(this.mainElement.dndClientRect()).minus(this.api.getBorderedAxis());
|
||
this.ready = true;
|
||
|
||
return this;
|
||
},
|
||
|
||
createElementByTemplate: function () {
|
||
this.element = $compile(this.template)(this.scope);
|
||
|
||
return this;
|
||
},
|
||
|
||
wrap: function () {
|
||
wrapper.html('');
|
||
wrapper.append(this.element);
|
||
|
||
return this;
|
||
},
|
||
|
||
appendTo: function (element) {
|
||
element.append(wrapper);
|
||
|
||
return this;
|
||
},
|
||
|
||
initBorderOffset: function () {
|
||
var axis = this.api.getBorderedAxis();
|
||
|
||
if (this.templateUrl === 'clone') {
|
||
var crect = this.mainElement.dndClientRect();
|
||
|
||
this.borderOffset = {
|
||
top: axis.y - crect.top,
|
||
left: axis.x - crect.left,
|
||
bottom: axis.y - crect.top - crect.height,
|
||
right: axis.x - crect.left - crect.width
|
||
};
|
||
} else {
|
||
var crect = wrapper.dndClientRect();
|
||
|
||
this.borderOffset = {
|
||
top: 0,
|
||
left: 0,
|
||
bottom: -crect.height,
|
||
right: -crect.width
|
||
};
|
||
}
|
||
},
|
||
|
||
updatePosition: function () {
|
||
var position = this.api.getRelBorderedAxis(this.borderOffset).plus(this._offset);
|
||
if (debug.mode) {
|
||
console.log(this.api.getRelBorderedAxis());
|
||
}
|
||
|
||
wrapper.dndCss(position.getAsCss());
|
||
},
|
||
|
||
|
||
destroy: function () {
|
||
this.element.remove();
|
||
},
|
||
};
|
||
|
||
return HelperTarget;
|
||
})();
|
||
|
||
function link (scope, element, attrs, ctrls) {
|
||
var rect = ctrls[0],
|
||
model = ctrls[1],
|
||
containment = ctrls[2];
|
||
|
||
var defaults = {
|
||
layer: 'common',
|
||
useAsPoint: false,
|
||
helper: null,
|
||
handle: ''
|
||
};
|
||
|
||
var getterDraggable = $parse(attrs.dndDraggable);
|
||
var opts = extend({}, defaults, $parse(attrs.dndDraggableOpts)(scope) || {});
|
||
var dragstartCallback = $parse(attrs.dndOnDragstart);
|
||
var dragCallback = $parse(attrs.dndOnDrag);
|
||
var dragendCallback = $parse(attrs.dndOnDragend);
|
||
var draggable = opts.helper ? new HelperTarget(element, opts.helper, scope) : new ElementTarget(element, rect);
|
||
var started, handle = opts.handle ? element[0].querySelector(opts.handle) : '';
|
||
|
||
function dragstart(api) {
|
||
started = false;
|
||
|
||
// определяем включен ли draggable элемент
|
||
var enabled = getterDraggable(scope);
|
||
enabled = enabled === undefined || enabled;
|
||
|
||
// если draggable элемент выключен - отмечаем элемент как "не цель курсора"
|
||
if (!enabled || (handle && handle !== api.getEvent().target)) {
|
||
api.unTarget();
|
||
}
|
||
|
||
// если элемент не является целью курсора (возможно есть другие draggable элементы внутри) - никак не реагируем на событие
|
||
if (!api.isTarget()) {
|
||
return;
|
||
}
|
||
|
||
draggable.init(api);
|
||
|
||
// ставим флаг, что элемент начал двигаться
|
||
started = true;
|
||
|
||
// ставим флаг useAsPoint, что бы определить, является ли элемент полноразмерным или точкой.
|
||
// В зависимости от этого флага по разному реагируют droppable зоны на этот элемент
|
||
api.useAsPoint(opts.useAsPoint);
|
||
|
||
// задаем модель данному элементу
|
||
api.dragmodel = model ? model.get() : null;
|
||
|
||
api.setBounderElement( containment ? containment.get() : angular.element(document.body) );
|
||
|
||
// ставим флаг, что процесс перемещения элемента начался
|
||
scope.$dragged = true;
|
||
|
||
// применяем пользовательский callback
|
||
dragstartCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api});
|
||
|
||
// запускаем dirty-checking цикл
|
||
scope.$apply();
|
||
}
|
||
|
||
function drag(api) {
|
||
if (!started) {
|
||
return;
|
||
}
|
||
|
||
draggable.updatePosition();
|
||
dragCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api});
|
||
|
||
scope.$apply();
|
||
}
|
||
|
||
function dragend(api) {
|
||
if (!started) {
|
||
return;
|
||
}
|
||
|
||
draggable.destroy();
|
||
|
||
dragendCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api});
|
||
|
||
$timeout(function () {
|
||
scope.$dragged = false;
|
||
});
|
||
}
|
||
|
||
var bindings = {};
|
||
|
||
opts.layer = opts.layer || defaults.layer;
|
||
|
||
bindings[opts.layer+'.dragstart'] = dragstart;
|
||
bindings[opts.layer+'.drag'] = drag;
|
||
bindings[opts.layer+'.dragend'] = dragend;
|
||
|
||
element.dndBind(bindings);
|
||
|
||
scope.$dragged = false;
|
||
}
|
||
|
||
return {
|
||
require: ['?dndRect', '?dndModel', '?dndContainment'],
|
||
scope: true,
|
||
link: link
|
||
};
|
||
}]);
|
||
;
|
||
|
||
module.directive('dndDroppable', ['$parse', '$timeout', function( $parse, $timeout ){
|
||
return {
|
||
require: '?dndModel',
|
||
scope: true,
|
||
link: function(scope, $el, attrs, model){
|
||
|
||
var defaults = {
|
||
layer: 'common'
|
||
};
|
||
|
||
var getterDroppable = $parse(attrs.dndDroppable);
|
||
var opts = extend({}, defaults, $parse(attrs.dndDroppableOpts)(scope) || {});
|
||
var dragenterCallback = $parse(attrs.dndOnDragenter);
|
||
var dragoverCallback = $parse(attrs.dndOnDragover);
|
||
var dragleaveCallback = $parse(attrs.dndOnDragleave);
|
||
var dropCallback = $parse(attrs.dndOnDrop);
|
||
|
||
function dragenter(api){
|
||
var local = api.droplocal = {};
|
||
|
||
api.dropmodel = model ? model.get() : model;
|
||
|
||
local.droppable = getterDroppable(scope, {'$dragmodel': api.dragmodel, '$dropmodel': api.dropmodel, '$api': api});
|
||
local.droppable = local.droppable === undefined ? true : local.droppable;
|
||
|
||
if(!local.droppable) {
|
||
return;
|
||
}
|
||
|
||
dragenterCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api});
|
||
scope.$apply();
|
||
}
|
||
|
||
function dragover(api){
|
||
var local = api.droplocal;
|
||
|
||
if(!local.droppable) {
|
||
return;
|
||
}
|
||
|
||
dragoverCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api});
|
||
scope.$apply();
|
||
}
|
||
|
||
function dragleave(api){
|
||
var local = api.droplocal;
|
||
|
||
if(!local.droppable) {
|
||
return;
|
||
}
|
||
|
||
dragleaveCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api});
|
||
api.dropmodel = undefined;
|
||
scope.$apply();
|
||
}
|
||
|
||
function drop(api){
|
||
dropCallback(scope, {'$dragmodel':api.dragmodel, '$dropmodel': api.dropmodel, '$api': api});
|
||
}
|
||
|
||
|
||
var bindings = {};
|
||
|
||
opts.layer = opts.layer || defaults.layer;
|
||
|
||
bindings[opts.layer+'.dragenter'] = dragenter;
|
||
bindings[opts.layer+'.dragover'] = dragover;
|
||
bindings[opts.layer+'.dragleave'] = dragleave;
|
||
bindings[opts.layer+'.drop'] = drop;
|
||
|
||
$el.dndBind( bindings );
|
||
|
||
}
|
||
};
|
||
}]);
|
||
;
|
||
|
||
module.directive('dndRotatable', ['$parse', '$timeout', function($parse, $timeout){
|
||
|
||
function link (scope, element, attrs, ctrls) {
|
||
var rect = ctrls[0],
|
||
containment = ctrls[1];
|
||
|
||
var defaults = {
|
||
step: 5
|
||
};
|
||
|
||
var getterRotatable = $parse(attrs.dndRotatable);
|
||
var opts = extend({}, defaults, $parse(attrs.dndRotatableOpts)(scope) || {});
|
||
var dragstartCallback = $parse(attrs.dndOnRotatestart);
|
||
var dragCallback = $parse(attrs.dndOnRotate);
|
||
var dragendCallback = $parse(attrs.dndOnRotateend);
|
||
|
||
var cssPosition = element.dndCss('position');
|
||
|
||
if(cssPosition != 'fixed' && cssPosition != 'absolute' && cssPosition != 'relative') {
|
||
cssPosition = 'relative';
|
||
element.dndCss('position', cssPosition);
|
||
}
|
||
|
||
var handle = angular.element('<div class = "angular-dnd-rotatable-handle"></div>');
|
||
|
||
element.append(handle);
|
||
|
||
function dragstart(api) {
|
||
var local = api.local = {};
|
||
|
||
local.rotatable = getterRotatable(scope);
|
||
local.rotatable = local.rotatable === undefined ? true : local.rotatable;
|
||
|
||
if( !local.rotatable ) {
|
||
api.unTarget();
|
||
}
|
||
|
||
if(!api.isTarget()) {
|
||
return;
|
||
}
|
||
|
||
local.started = true;
|
||
|
||
api.setBounderElement( containment ? containment.get() : angular.element(document.body) );
|
||
|
||
var axis = api.getRelBorderedAxis();
|
||
|
||
local.srect = element.dndStyleRect();
|
||
|
||
local.currAngle = element.dndGetAngle();
|
||
|
||
local.startPoint = Point(axis);
|
||
|
||
local.borders = api.getBorders();
|
||
|
||
local.center = Point(local.srect).plus(Point(local.srect.width / 2, local.srect.height / 2));
|
||
|
||
scope.$rotated = true;
|
||
|
||
dragstartCallback(scope);
|
||
|
||
scope.$apply();
|
||
}
|
||
|
||
function drag(api){
|
||
var local = api.local;
|
||
|
||
if(!local.started) {
|
||
return;
|
||
}
|
||
|
||
var axis = api.getRelBorderedAxis();
|
||
var angle = Point(axis).deltaAngle(local.startPoint, local.center);
|
||
var degs = radToDeg(local.currAngle+angle);
|
||
|
||
degs = Math.round(degs/opts.step)*opts.step;
|
||
var rads = degToRad(degs);
|
||
var matrix = Matrix().rotate(rads);
|
||
|
||
var compute = Rect( local.center.x - local.srect.width/2, local.center.y - local.srect.height/2, local.srect.width, local.srect.height).applyMatrix( matrix, local.center ).client();
|
||
var rPoint = api.getReferencePoint();
|
||
|
||
if(local.borders && (compute.left + rPoint.x < local.borders.left-1 || compute.top + rPoint.y < local.borders.top-1 || (compute.left + rPoint.x + compute.width) > local.borders.right+1 || (compute.top + rPoint.y + compute.height) > local.borders.bottom+1)) {
|
||
return;
|
||
}
|
||
|
||
if(rect) {
|
||
rect.update('transform', matrix.toStyle());
|
||
} else {
|
||
element.dndCss('transform', matrix.toStyle());
|
||
}
|
||
|
||
dragCallback(scope);
|
||
|
||
scope.$apply();
|
||
}
|
||
|
||
function dragend(api){
|
||
var local = api.local;
|
||
|
||
if(!local.started) {
|
||
return;
|
||
}
|
||
|
||
dragendCallback(scope);
|
||
|
||
$timeout(function(){
|
||
scope.$rotated = false;
|
||
});
|
||
}
|
||
|
||
scope.$rotated = false;
|
||
|
||
var bindings = {
|
||
'$$rotatable.dragstart': dragstart,
|
||
'$$rotatable.drag': drag,
|
||
'$$rotatable.dragend': dragend
|
||
};
|
||
|
||
handle.dndBind( bindings );
|
||
|
||
}
|
||
|
||
return {
|
||
require: ['?dndRect', '?dndContainment'],
|
||
scope: true,
|
||
link: link
|
||
};
|
||
}])
|
||
;
|
||
|
||
module.directive('dndResizable', ['$parse', '$timeout', function($parse, $timeout) {
|
||
|
||
function createHandleElement(side) {
|
||
return angular.element('<div></div>').addClass('angular-dnd-resizable-handle angular-dnd-resizable-handle-' + side);
|
||
}
|
||
|
||
function getCenterPoint(rect, scale) {
|
||
scale = typeof scale === 'object' ? scale : {x:1,y:1};
|
||
|
||
return new Point(rect.left + rect.width*scale.x/2, rect.top + rect.height*scale.y/2);
|
||
}
|
||
|
||
function link (scope, $el, attrs, ctrls) {
|
||
var rect = ctrls[0],
|
||
containment = ctrls[1];
|
||
|
||
var defaults = {
|
||
handles: 'ne, se, sw, nw, n, e, s, w',
|
||
minWidth: 20,
|
||
minHeight: 20,
|
||
maxWidth: 10000,
|
||
maxHeight: 10000
|
||
};
|
||
|
||
var getterResizable = $parse(attrs.dndResizable);
|
||
var opts = extend({}, defaults, $parse(attrs.dndResizableOpts)(scope) || {});
|
||
var dragstartCallback = $parse(attrs.dndOnResizestart);
|
||
var dragCallback = $parse(attrs.dndOnResize);
|
||
var dragendCallback = $parse(attrs.dndOnResizeend);
|
||
|
||
function getBindings(side) {
|
||
|
||
function dragstart(api) {
|
||
var local = api.local = {};
|
||
|
||
local.resizable = getterResizable(scope);
|
||
local.resizable = local.resizable === undefined ? true : local.resizable;
|
||
|
||
if ( !local.resizable ) {
|
||
api.unTarget();
|
||
}
|
||
|
||
if ( !api.isTarget() ) {
|
||
return;
|
||
}
|
||
|
||
api.setBounderElement( containment ? containment.get() : angular.element(document.body) );
|
||
local.started = true;
|
||
local.$parent = $el.parent();
|
||
local.rads = $el.dndGetAngle();
|
||
local.rotateMatrix = Matrix.IDENTITY.rotate(local.rads);
|
||
local.inverseRotateMatrix = local.rotateMatrix.inverse();
|
||
local.parentRect = local.$parent.dndClientRect();
|
||
|
||
var axis = api.getBorderedAxis(), crect = $el.dndClientRect(), srect = local.rect = $el.dndStyleRect();
|
||
|
||
local.borders = api.getBorders();
|
||
local.startAxis = axis;
|
||
|
||
local.minScaleX = opts.minWidth / srect.width;
|
||
local.minScaleY = opts.minHeight / srect.height;
|
||
local.maxScaleX = opts.maxWidth / srect.width;
|
||
local.maxScaleY = opts.maxHeight / srect.height;
|
||
|
||
local.deltaX = crect.left - srect.left + crect.width / 2 - srect.width / 2;
|
||
local.deltaY = crect.top - srect.top + crect.height / 2 - srect.height / 2;
|
||
|
||
scope.$resized = true;
|
||
|
||
dragstartCallback(scope);
|
||
|
||
scope.$apply();
|
||
}
|
||
|
||
function drag(api) {
|
||
var local = api.local;
|
||
|
||
if (!local.started) return;
|
||
|
||
var axis = api.getBorderedAxis();
|
||
var vector = Point(axis).minus(local.startAxis).transform(local.inverseRotateMatrix);
|
||
|
||
var scale = {x:1,y:1};
|
||
|
||
var width = local.rect.width,
|
||
height = local.rect.height,
|
||
top = local.rect.top,
|
||
left = local.rect.left;
|
||
|
||
switch(side) {
|
||
case 'n': scale.y = (height - vector.y) / height; break;
|
||
case 'e': scale.x = (width + vector.x) / width; break;
|
||
case 's': scale.y = (height + vector.y) / height; break;
|
||
case 'w': scale.x = (width - vector.x) / width; break;
|
||
case 'ne': scale.x = (width + vector.x) / width; scale.y = (height - vector.y) / height; break;
|
||
case 'se': scale.x = (width + vector.x) / width; scale.y = (height + vector.y) / height; break;
|
||
case 'sw': scale.x = (width - vector.x) / width; scale.y = (height + vector.y) / height; break;
|
||
case 'nw': scale.x = (width - vector.x) / width; scale.y = (height - vector.y) / height; break;
|
||
}
|
||
|
||
scale.x = getNumFromSegment(local.minScaleX, scale.x, local.maxScaleX);
|
||
scale.y = getNumFromSegment(local.minScaleY, scale.y, local.maxScaleY);
|
||
|
||
var offset;
|
||
var center = getCenterPoint(local.rect);
|
||
var scaledCenter = getCenterPoint(local.rect, scale);
|
||
|
||
switch(side) {
|
||
case 'n': offset = Point(left,top+height*scale.y).rotate(local.rads, scaledCenter).minus( Point(left,top+height).rotate(local.rads, center) ); break;
|
||
case 'e': offset = Point(left,top).rotate(local.rads, scaledCenter).minus( Point(left,top).rotate(local.rads, center) ); break;
|
||
case 's': offset = Point(left,top).rotate(local.rads, scaledCenter).minus( Point(left,top).rotate(local.rads, center) ); break;
|
||
case 'w': offset = Point(left+width*scale.x,top).rotate(local.rads, scaledCenter).minus( Point(left+width,top).rotate(local.rads, center) ); break;
|
||
case 'ne': offset = Point(left,top+height*scale.y).rotate(local.rads, scaledCenter).minus( Point(left,top+height).rotate(local.rads, center) ); break;
|
||
case 'se': offset = Point(left,top).rotate(local.rads, scaledCenter).minus( Point(left,top).rotate(local.rads, center) ); break;
|
||
case 'sw': offset = Point(left+width*scale.x,top).rotate(local.rads, scaledCenter).minus( Point(left+width,top).rotate(local.rads, center) ); break;
|
||
case 'nw': offset = Point(left+width*scale.x,top+height*scale.y).rotate(local.rads, scaledCenter).minus( Point(left+width,top+height).rotate(local.rads, center) ); break;
|
||
};
|
||
|
||
var styles = {};
|
||
styles.width = width * scale.x;
|
||
styles.height = height * scale.y;
|
||
styles.left = left - offset.x;
|
||
styles.top = top - offset.y;
|
||
|
||
var realCenter = Point(styles.left+local.deltaX+styles.width/2, styles.top+local.deltaY+styles.height/2);
|
||
var boundedRect = Rect(styles.left+local.deltaX, styles.top+local.deltaY, styles.width, styles.height).applyMatrix( local.rotateMatrix, realCenter ).client();
|
||
|
||
if (local.borders && (boundedRect.left+1 < local.borders.left || boundedRect.top+1 < local.borders.top || boundedRect.right-1 > local.borders.right || boundedRect.bottom-1 > local.borders.bottom)) {
|
||
return;
|
||
}
|
||
|
||
if (rect) {
|
||
rect.update(styles);
|
||
} else {
|
||
$el.dndCss(styles);
|
||
}
|
||
|
||
dragCallback(scope);
|
||
|
||
scope.$apply();
|
||
}
|
||
|
||
function dragend(api) {
|
||
var local = api.local;
|
||
|
||
if (!local.started) {
|
||
return;
|
||
}
|
||
|
||
dragendCallback(scope);
|
||
|
||
|
||
$timeout(function() { scope.$resized = false; });
|
||
}
|
||
|
||
return {
|
||
'$$resizable.dragstart': dragstart,
|
||
'$$resizable.drag': drag,
|
||
'$$resizable.dragend': dragend
|
||
};
|
||
|
||
}
|
||
|
||
var cssPosition = $el.dndCss('position');
|
||
|
||
if (cssPosition !== 'fixed' && cssPosition !== 'absolute' && cssPosition !== 'relative') {
|
||
cssPosition = 'relative';
|
||
$el.dndCss('position', cssPosition);
|
||
}
|
||
|
||
var sides = opts.handles.replace(/\s/g,'').split(',');
|
||
|
||
for(var i=0; i < sides.length; i++) {
|
||
$el.append( createHandleElement( sides[i] ).dndBind( getBindings( sides[i] ) ) );
|
||
}
|
||
|
||
scope.$resized = false;
|
||
}
|
||
|
||
return {
|
||
require: ['?dndRect', '?dndContainment'],
|
||
scope: true,
|
||
link: link
|
||
};
|
||
}]);
|
||
;
|
||
|
||
module.directive('dndSortable', ['$parse', '$compile', function($parse, $compile) {
|
||
var placeholder, ngRepeatRegExp = /^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/;
|
||
|
||
function join(obj, sep1, sep2) {
|
||
return Object.getOwnPropertyNames(obj).map(function(key) {
|
||
return [key, obj[key]].join(sep1);
|
||
}).join(sep2);
|
||
}
|
||
|
||
function joinObj (obj) {
|
||
return '{' + join(obj, ':', ',') + '}';
|
||
}
|
||
|
||
function joinAttrs (attrs) {
|
||
return join(attrs, '="', '" ') + '"';
|
||
}
|
||
|
||
function template(element, tAttrs) {
|
||
var tag = element[0].nodeName.toLowerCase();
|
||
var ngRepeat = tAttrs.ngRepeat || '';
|
||
var match = ngRepeat.match(ngRepeatRegExp);
|
||
|
||
if(!match) {
|
||
throw 'dnd-sortable-item requires ng-repeat as dependence';
|
||
}
|
||
|
||
var opts = angular.extend({
|
||
layer: "'common'",
|
||
}, $parse(tAttrs.dndSortableOpts)());
|
||
|
||
var attrs = {
|
||
'ng-transclude': '',
|
||
'dnd-draggable': '',
|
||
'dnd-draggable-opts': joinObj({
|
||
helper: "'clone'",
|
||
useAsPoint: true,
|
||
layer: opts.layer
|
||
}),
|
||
'dnd-droppable': '',
|
||
'dnd-droppable-opts': joinObj({
|
||
layer: opts.layer,
|
||
}),
|
||
'dnd-on-dragstart': '$$onDragStart($api, $dropmodel, $dragmodel)',
|
||
'dnd-on-dragend': '$$onDragEnd($api, $dropmodel, $dragmodel)',
|
||
'dnd-on-dragover': '$$onDragOver($api, $dropmodel, $dragmodel)',
|
||
'dnd-on-drag': '$$onDrag($api, $dropmodel, $dragmodel)',
|
||
'dnd-model': '{item: ' + match[1] + ', list: ' + match[2] + ', index: $index}',
|
||
};
|
||
|
||
return '<' + tag + ' ' + joinAttrs(attrs) + '></' + tag + '>';
|
||
}
|
||
|
||
function link(scope, element, attrs) {
|
||
var defaults = {
|
||
layer: 'common'
|
||
};
|
||
|
||
var parentNode = element[0].parentNode;
|
||
var parentElement = angular.element(parentNode);
|
||
var parentData = parentElement.data('dnd-sortable');
|
||
var getter = $parse(attrs.dndModel) || noop;
|
||
var css = element.dndCss(['float', 'display']);
|
||
var floating = /left|right|inline/.test(css.float + css.display);
|
||
var opts = extend({}, defaults, $parse(attrs.dndSortableOpts)(scope) || {});
|
||
var sortstartCallback = $parse(attrs.dndOnSortstart);
|
||
var sortCallback = $parse(attrs.dndOnSort);
|
||
var sortchangeCallback = $parse(attrs.dndOnSortchange);
|
||
var sortendCallback = $parse(attrs.dndOnSortend);
|
||
var sortenterCallback = $parse(attrs.dndOnSortenter);
|
||
var sortleaveCallback = $parse(attrs.dndOnSortleave);
|
||
|
||
if(!parentData || !parentData[opts.layer]) {
|
||
parentData = parentData || {};
|
||
parentData[opts.layer] = true;
|
||
|
||
var bindings = {};
|
||
|
||
bindings[opts.layer+'.dragover'] = function(api) {
|
||
if(api.getEvent().target !== parentNode || getter(scope).list.length > 1) {
|
||
return;
|
||
}
|
||
|
||
api.$sortable.model = getter(scope);
|
||
api.$sortable.insertBefore = true;
|
||
parentElement.append(placeholder[0]);
|
||
};
|
||
|
||
parentElement.dndBind(bindings).data('dnd-sortable', parentData);
|
||
}
|
||
|
||
function isHalfway(dragTarget, axis, dropmodel) {
|
||
var rect = element.dndClientRect();
|
||
|
||
return (floating ? (axis.x - rect.left) / rect.width : (axis.y - rect.top) / rect.height) > 0.5;
|
||
}
|
||
|
||
function moveValue(fromIndex, fromList, toIndex, toList) {
|
||
toList = toList || fromList;
|
||
toList.splice(toIndex, 0, fromList.splice(fromIndex, 1)[0]);
|
||
}
|
||
|
||
scope.$$onDragStart = function(api) {
|
||
sortstartCallback(scope);
|
||
|
||
placeholder = element.clone();
|
||
element.addClass('ng-hide');
|
||
placeholder.addClass('angular-dnd-placeholder');
|
||
parentNode.insertBefore(placeholder[0], element[0]);
|
||
api.$sortable = {};
|
||
api.clearCache();
|
||
|
||
scope.$apply();
|
||
};
|
||
|
||
scope.$$onDragOver = function(api, dropmodel, dragmodel) {
|
||
var halfway = isHalfway(api.getDragTarget(), api.getBorderedAxis());
|
||
|
||
halfway ? parentNode.insertBefore(placeholder[0], element[0].nextSibling) : parentNode.insertBefore(placeholder[0], element[0]);
|
||
|
||
var model = getter(scope);
|
||
|
||
if (sortchangeCallback !== angular.noop && (!api.$sortable.model || api.$sortable.model.index !== model.index)) {
|
||
sortchangeCallback(scope);
|
||
scope.$apply();
|
||
}
|
||
|
||
api.$sortable.model = model;
|
||
api.$sortable.insertBefore = !halfway;
|
||
|
||
api.clearCache();
|
||
};
|
||
|
||
scope.$$onDragEnd = function(api) {
|
||
element.removeClass('ng-hide');
|
||
placeholder.addClass('ng-hide');
|
||
|
||
if(!api.$sortable.model) {
|
||
return;
|
||
}
|
||
|
||
var fromIndex = scope.$index,
|
||
toIndex = api.$sortable.model.index,
|
||
fromList = getter(scope).list,
|
||
toList = api.$sortable.model.list;
|
||
|
||
if(toList === fromList) {
|
||
if(toIndex < fromIndex) {
|
||
if(!api.$sortable.insertBefore) toIndex++;
|
||
} else {
|
||
if(api.$sortable.insertBefore) toIndex--;
|
||
}
|
||
} else if(!api.$sortable.insertBefore) toIndex++;
|
||
|
||
moveValue(fromIndex, fromList, toIndex, toList);
|
||
|
||
api.clearCache();
|
||
|
||
sortendCallback(scope);
|
||
scope.$apply();
|
||
};
|
||
|
||
(sortCallback !== angular.noop) && (scope.$$onDrag = function(api) {
|
||
sortCallback(scope);
|
||
scope.$apply();
|
||
});
|
||
}
|
||
|
||
return {
|
||
scope: true,
|
||
transclude: true,
|
||
template: template,
|
||
replace: true,
|
||
link: link
|
||
};
|
||
}]);
|
||
//TODO:
|
||
//create - вызывается при создании списка
|
||
|
||
//activate - начат процесс сортировки (вызывается у всех связанных списков)
|
||
//start - начат процесс сортировки (вызывается только у списка инициатора)
|
||
|
||
//sort - вызывается при любом движении манипулятора при сортировке
|
||
//change - сортировка списка изменилась
|
||
//out - манипулятор с элементом вынесен за пределы списка (а также, если было событие over и небыло out то и при окончании сортировки)
|
||
//over - манипулятор с элементом внесен в пределы списка
|
||
|
||
//beforeStop - будет вызвано у списка инициатора
|
||
//update - будет вызвано если список изменился
|
||
//deactivate - вызывается у всех связанных списков
|
||
//stop - вызывается самым последним у списка инициатора
|
||
|
||
//receive - элемент дропнулся ИЗ другого списка
|
||
//remove - элмент дропнулся В другой список
|
||
|
||
//example: http://jsfiddle.net/UAcC7/1441/
|
||
;
|
||
|
||
module.directive('dndSelectable', ['$parse', function($parse){
|
||
|
||
var defaults = {};
|
||
|
||
Controller.$inject = ['$scope', '$attrs', '$element'];
|
||
function Controller($scope, $attrs, $element) {
|
||
var getterSelecting = $parse($attrs.dndModelSelecting), setterSelecting = getterSelecting.assign || noop;
|
||
var getterSelected = $parse($attrs.dndModelSelected), setterSelected = getterSelected.assign || noop;
|
||
var getterSelectable = $parse($attrs.dndSelectable), setterSelectable = getterSelectable.assign || noop;
|
||
var onSelected = $parse($attrs.dndOnSelected);
|
||
var onUnselected = $parse($attrs.dndOnUnselected);
|
||
var onSelecting = $parse($attrs.dndOnSelecting);
|
||
var onUnselecting = $parse($attrs.dndOnUnselecting);
|
||
|
||
setterSelected($scope, false);
|
||
setterSelecting($scope, false);
|
||
|
||
this.getElement = function(){
|
||
return $element;
|
||
};
|
||
|
||
this.isSelected = function(){
|
||
return getterSelected($scope);
|
||
};
|
||
|
||
this.isSelecting = function(){
|
||
return getterSelecting($scope);
|
||
};
|
||
|
||
this.isSelectable = function(){
|
||
var selectable = getterSelectable($scope);
|
||
return selectable === undefined || selectable;
|
||
};
|
||
|
||
this.toggleSelected = function(val){
|
||
val = val === undefined ? !this.isSelected() : val;
|
||
|
||
return val ? this.selected() : this.unselected();
|
||
};
|
||
|
||
this.selecting = function(){
|
||
if(this.isSelectable() && onSelecting($scope) !== false) setterSelecting($scope, true);
|
||
|
||
return this;
|
||
};
|
||
|
||
this.unselecting = function(){
|
||
if(onUnselecting($scope) !== false) setterSelecting($scope, false);
|
||
|
||
return this;
|
||
};
|
||
|
||
this.selected = function(){
|
||
if(this.isSelectable() && onSelected($scope) !== false) setterSelected($scope, true);
|
||
|
||
return this;
|
||
};
|
||
|
||
this.unselected = function(){
|
||
if(onUnselected($scope) !== false) setterSelected($scope, false);
|
||
|
||
return this;
|
||
};
|
||
|
||
this.hit = function(a){
|
||
var b = this.rectCtrl.getClient();
|
||
|
||
for(var key in b) {
|
||
b[key] = parseFloat(b[key]);
|
||
}
|
||
|
||
b.bottom = b.bottom == undefined ? b.top + b.height : b.bottom;
|
||
b.right = b.right == undefined ? b.left + b.width : b.right;
|
||
a.bottom = a.bottom == undefined ? a.top + a.height : a.bottom;
|
||
a.right = a.right == undefined ? a.left + a.width : a.right;
|
||
|
||
return (
|
||
a.top <= b.top && b.top <= a.bottom && ( a.left <= b.left && b.left <= a.right || a.left <= b.right && b.right <= a.right ) ||
|
||
a.top <= b.bottom && b.bottom <= a.bottom && ( a.left <= b.left && b.left <= a.right || a.left <= b.right && b.right <= a.right ) ||
|
||
a.left >= b.left && a.right <= b.right && ( b.top <= a.bottom && a.bottom <= b.bottom || b.bottom >= a.top && a.top >= b.top || a.top <= b.top && a.bottom >= b.bottom) ||
|
||
a.top >= b.top && a.bottom <= b.bottom && ( b.left <= a.right && a.right <= b.right || b.right >= a.left && a.left >= b.left || a.left <= b.left && a.right >= b.right) ||
|
||
a.top >= b.top && a.right <= b.right && a.bottom <= b.bottom && a.left >= b.left
|
||
);
|
||
};
|
||
|
||
}
|
||
|
||
function LikeRectCtrl($element){
|
||
this.$element = $element;
|
||
}
|
||
|
||
LikeRectCtrl.prototype = {
|
||
getClient: function(){
|
||
return this.$element.dndClientRect();
|
||
}
|
||
};
|
||
|
||
return {
|
||
restrict: 'A',
|
||
require: ['dndSelectable', '^dndLassoArea', '?dndRect'],
|
||
controller: Controller,
|
||
scope: true,
|
||
link: function(scope, $el, attrs, ctrls) {
|
||
scope.$dndSelectable = ctrls[0];
|
||
|
||
var rectCtrl = ctrls[2];
|
||
|
||
ctrls[0].rectCtrl = rectCtrl ? rectCtrl : new LikeRectCtrl($el);
|
||
|
||
ctrls[1].add(ctrls[0]);
|
||
|
||
function ondestroy() {
|
||
ctrls[1].remove(ctrls[0]);
|
||
|
||
if(!scope.$$phase) scope.$apply();
|
||
}
|
||
|
||
$el.on('$destroy', ondestroy);
|
||
|
||
var selected = $parse(attrs.dndOnSelected);
|
||
var unselected = $parse(attrs.dndOnUnselected);
|
||
var selecting = $parse(attrs.dndOnSelecting);
|
||
var unselecting = $parse(attrs.dndOnUnselecting);
|
||
|
||
if(selected || unselected) {
|
||
selected = selected || noop;
|
||
unselected = unselected || noop;
|
||
|
||
scope.$watch(attrs.dndModelSelected, function(n, o){
|
||
if(n === undefined || o === undefined || n === o) return;
|
||
|
||
n ? selected(scope) : unselected(scope);
|
||
});
|
||
}
|
||
|
||
|
||
if(selecting || unselecting) {
|
||
selecting = selecting || noop;
|
||
unselecting = unselecting || noop;
|
||
|
||
scope.$watch(attrs.dndModelSelecting, function(n, o){
|
||
if(n === undefined || o === undefined || n === o) return;
|
||
|
||
n ? selecting(scope) : unselecting(scope);
|
||
});
|
||
}
|
||
}
|
||
};
|
||
}])
|
||
;
|
||
|
||
module.directive('dndRect', ['$parse', function($parse){
|
||
var setStyles = ['top','left','width','height', 'transform'];
|
||
var getStyles = ['top','left','width','height', TRANSFORM];
|
||
|
||
setStyles.has = function(val){
|
||
return this.indexOf(val) > -1;
|
||
}
|
||
|
||
Controller.$inject = ['$scope', '$attrs', '$element'];
|
||
function Controller( $scope, $attrs, $element ){
|
||
var getter = $parse($attrs.dndRect), setter = getter.assign, lastRect;
|
||
|
||
this.update = function(prop, value) {
|
||
var values, rect = getter($scope) || {};
|
||
|
||
if(typeof prop != 'object') {
|
||
values = {};
|
||
values[prop] = value;
|
||
} else values = prop;
|
||
|
||
for(var i = 0; i < setStyles.length; i++){
|
||
var style = setStyles[i];
|
||
|
||
if(values[style] !== undefined) rect[style] = values[style];
|
||
}
|
||
|
||
setter($scope, rect);
|
||
};
|
||
|
||
this.get = function(){
|
||
return getter($scope);
|
||
};
|
||
|
||
this.getClient = function(){
|
||
return $element.dndClientRect();
|
||
};
|
||
|
||
function sanitize(rect){
|
||
var css;
|
||
|
||
rect = typeof rect == 'object' ? rect : {};
|
||
|
||
for(var i = 0; i < setStyles.length; i++){
|
||
|
||
var style = setStyles[i];
|
||
|
||
if(rect[style] !== undefined) continue;
|
||
|
||
if(!css) css = $element.dndCss(getStyles);
|
||
|
||
rect[style] = style == 'transform' ? (css[TRANSFORM] == 'none' ? 'matrix(1, 0, 0, 1, 0, 0)' : css[TRANSFORM]) : css[style];
|
||
}
|
||
|
||
for(var key in rect){
|
||
rect[key.toLowerCase()] = rect[key];
|
||
}
|
||
|
||
for(var key in rect) {
|
||
if(setStyles.has(key)) {
|
||
if(typeof rect[key] === 'number') rect[key] = rect[key]+'px';
|
||
} else delete rect[key];
|
||
}
|
||
|
||
return rect;
|
||
};
|
||
|
||
$scope.$parent.$watch(function(){
|
||
var rect = getter($scope.$parent);
|
||
|
||
if(rect !== lastRect) setter($scope, rect);
|
||
});
|
||
|
||
$scope.$watch($attrs.dndRect, function(n, o){
|
||
if(!n || typeof n != 'object') return;
|
||
if(o == undefined) o = {};
|
||
|
||
var lastRect = n = sanitize(n);
|
||
|
||
var css = {};
|
||
|
||
for(var val, i=0; i < setStyles.length; i++ ){
|
||
val = setStyles[i];
|
||
|
||
if(n[val] == undefined && o[val] != undefined) css[val] = '';
|
||
else if(n[val] != undefined) css[val] = n[val];
|
||
}
|
||
|
||
if(css.transform) css[TRANSFORM] = css.transform;
|
||
$element.dndCss(css);
|
||
|
||
}, true);
|
||
}
|
||
|
||
|
||
|
||
return {
|
||
restrict: 'A',
|
||
controller: Controller
|
||
};
|
||
}])
|
||
;
|
||
|
||
module.directive('dndModel', ['$parse', function($parse){
|
||
|
||
Controller.$inject = ['$scope', '$attrs'];
|
||
function Controller( $scope, $attrs ){
|
||
var getter = $parse($attrs.dndModel), setter = getter.assign
|
||
|
||
this.set = function(value){
|
||
setter($scope, value);
|
||
};
|
||
|
||
this.get = function(){
|
||
return getter($scope);
|
||
};
|
||
}
|
||
|
||
return {
|
||
restrict: 'A',
|
||
controller: Controller
|
||
}
|
||
}])
|
||
;
|
||
|
||
module.directive('dndLassoArea', ['DndLasso', '$parse', '$timeout', 'dndKey', function(DndLasso, $parse, $timeout, dndKey){
|
||
|
||
Controller.$inject = [];
|
||
function Controller(){
|
||
var ctrls = [], data = {};
|
||
|
||
this.data = function(){
|
||
return data;
|
||
};
|
||
|
||
this.add = function(ctrl){
|
||
ctrls.push(ctrl);
|
||
};
|
||
|
||
this.remove = function(ctrl){
|
||
for(var i = 0; i < ctrls.length; i++){
|
||
if(ctrls[i] === ctrl) {
|
||
ctrls.splice(i,1);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
};
|
||
|
||
this.getSelectable = function(element){
|
||
for(var i = 0; i < ctrls.length; i++){
|
||
if(ctrls[i].getElement()[0] == element) return ctrls[i];
|
||
}
|
||
|
||
return undefined;
|
||
};
|
||
|
||
this.empty = function(){
|
||
return !ctrls.length;
|
||
};
|
||
|
||
this.get = function(i){
|
||
return i === undefined ? ctrls : ctrls[i];
|
||
}
|
||
}
|
||
|
||
var ctrls = [];
|
||
|
||
ctrls.remove = function (ctrl){
|
||
for(var i = 0; i < this.length; i++){
|
||
if(this[i] === ctrl) {
|
||
this.splice(i,1);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
return {
|
||
restrict: 'A',
|
||
controller: Controller,
|
||
require: 'dndLassoArea',
|
||
/* отрицательный приоритет необходим для того, что бы post link function dnd-lasso-area запускался раньше post link function ng-click */
|
||
priority: -1,
|
||
link: function(scope, $el, attrs, ctrl){
|
||
var defaults = {
|
||
selectAdditionals: true
|
||
};
|
||
|
||
var getterLassoArea = $parse(attrs.dndLassoArea);
|
||
var opts = extend({}, defaults, $parse(attrs.dndLassoAreaOpts)(scope) || {});
|
||
var dragstartCallback = $parse(attrs.dndOnLassostart);
|
||
var dragCallback = $parse(attrs.dndOnLasso);
|
||
var dragendCallback = $parse(attrs.dndOnLassoend);
|
||
var clickCallback = $parse(attrs.dndLassoOnclick);
|
||
var lasso = new DndLasso({ $el:$el }), selectable, keyPressed;
|
||
|
||
ctrls.push(ctrl);
|
||
|
||
function onClick(event){
|
||
if(!ctrl.empty()) {
|
||
|
||
if(keyPressed) {
|
||
selectable.toggleSelected();
|
||
return
|
||
}
|
||
|
||
var s = ctrl.get();
|
||
|
||
for(var i = 0; i < s.length; i++){
|
||
s[i].unselected().unselecting();
|
||
}
|
||
|
||
if(selectable) selectable.selected();
|
||
|
||
}
|
||
|
||
clickCallback( scope, {$event: event});
|
||
|
||
scope.$apply();
|
||
}
|
||
|
||
function onStart(handler) {
|
||
scope.$dragged = true;
|
||
|
||
if(!handler.isActive()) return;
|
||
|
||
dragstartCallback( scope );
|
||
|
||
if(!ctrl.empty() && !keyPressed) {
|
||
|
||
var s = ctrl.get();
|
||
|
||
for(var i = 0; i < s.length; i++){
|
||
s[i].unselected().unselecting();
|
||
}
|
||
|
||
}
|
||
|
||
|
||
scope.$apply();
|
||
}
|
||
|
||
function onDrag(handler) {
|
||
|
||
scope.$dragged = true;
|
||
|
||
if(!handler.isActive()) return;
|
||
|
||
if(!ctrl.empty()) {
|
||
var s = ctrl.get(), rect = handler.getClientRect();
|
||
|
||
for(var i = 0; i < s.length; i++) {
|
||
s[i].hit(rect) ? s[i].selecting() : s[i].unselecting();
|
||
}
|
||
}
|
||
|
||
dragCallback(scope, { $rect: handler.getRect() });
|
||
|
||
scope.$apply();
|
||
|
||
}
|
||
|
||
function onEnd(handler) {
|
||
|
||
if(!handler.isActive()) return;
|
||
|
||
var s = ctrl.get();
|
||
|
||
if(!ctrl.empty()) {
|
||
|
||
for(var i = 0; i < s.length; i++){
|
||
if(s[i].isSelecting()) s[i].toggleSelected();
|
||
}
|
||
|
||
scope.$apply();
|
||
}
|
||
|
||
dragendCallback(scope, { $rect: handler.getRect() });
|
||
|
||
if(!ctrl.empty()) {
|
||
|
||
for(var i = 0; i < s.length; i++){
|
||
s[i].unselecting();
|
||
}
|
||
|
||
scope.$apply();
|
||
}
|
||
|
||
/* что бы события click/dblclick получили флаг $dragged === true, переключение флага происходит после их выполнения */
|
||
$timeout(function(){ scope.$dragged = false; });
|
||
}
|
||
|
||
$el.on('mousedown touchstart', throttle(function (event){
|
||
|
||
scope.$dragged = false;
|
||
|
||
//scope.$keypressed = keyPressed = ( dndKey.isset(16) || dndKey.isset(17) || dndKey.isset(18) );
|
||
scope.$keypressed = keyPressed = opts.selectAdditionals ? ( event.shiftKey || event.ctrlKey || event.metaKey ) : false;
|
||
|
||
if(!ctrl.empty()) {
|
||
selectable = ctrl.getSelectable(event.target);
|
||
}
|
||
|
||
scope.$apply();
|
||
|
||
}, 300) );
|
||
|
||
$el.on('click', function(event){
|
||
|
||
if(!scope.$dragged) onClick(event);
|
||
|
||
/* что бы события dnd-on-* получили флаг $keypressed, переключение флага происходит после их выполнения */
|
||
if(scope.$keypressed) $timeout(function(){ scope.$keypressed = false; });
|
||
} );
|
||
|
||
lasso.on('start', onStart);
|
||
lasso.on('drag', onDrag);
|
||
lasso.on('end', onEnd);
|
||
|
||
$el.on('$destroy', function(){
|
||
ctrls.remove(ctrl);
|
||
|
||
scope.$apply();
|
||
});
|
||
|
||
scope.$dragged = false;
|
||
}
|
||
};
|
||
}])
|
||
;
|
||
|
||
/**
|
||
* @name dnd.fittext
|
||
*
|
||
* @description
|
||
* Отличная функция для подгонки текста под размер блока, в котором этот текст находится.
|
||
* за единственный аргумент функция принимает объект rect, содержащий в себе ширину (width) и высоту (height) элемента.
|
||
* На основе этих параметров идет расчет высоты шрифта.
|
||
* Также у директвы есть дополнительные атрибуты-настройки: dnd-fittext-max и dnd-fittext-min,
|
||
* которые позволяют задать максимальное и минимальное соответственно значение шрифта.
|
||
*
|
||
*/
|
||
|
||
module.directive('dndFittext', ['$timeout', '$window', function( $timeout, $window ){
|
||
var $span = $('<span></span>').dndCss({'position':'absolute','left':-99999, 'top':-99999, 'opacity':0, 'z-index': -9999});
|
||
|
||
$(document.body).append( $span );
|
||
|
||
function encodeStr(val) {
|
||
var val = $span.text(val).html().replace(/\s+/g, ' ')
|
||
if($span[0].tagName == 'INPUT' || $span[0].tagName == 'TEXTAREA') val = val.replace(/\s/g,' ');
|
||
|
||
return val;
|
||
}
|
||
|
||
function getRealSize(text, font) {
|
||
$span.html( encodeStr(text) ).dndCss(font);
|
||
var rect = $span[0].getBoundingClientRect();
|
||
|
||
return {
|
||
width: parseFloat(rect.width),
|
||
height: parseFloat(rect.height)
|
||
}
|
||
}
|
||
|
||
function getCurrSize($el, offsetWidthPrct, offsetHeightPrct){
|
||
var rect = $el[0].getBoundingClientRect();
|
||
|
||
return {
|
||
width: parseFloat(rect.width)*(100-offsetWidthPrct)/100,
|
||
height: parseFloat(rect.height)*(100-offsetHeightPrct)/100
|
||
}
|
||
}
|
||
|
||
return {
|
||
restrict: 'A',
|
||
link: function(scope, $el, attrs) {
|
||
|
||
function updateSize(opts) {
|
||
opts = opts === undefined ? {} : opts;
|
||
var font = $el.dndCss(
|
||
['font-size','font-family','font-weight','text-transform','border-top','border-right','border-bottom','border-left','padding-top','padding-right','padding-bottom','padding-left']
|
||
), text = opts.text == undefined ? $el.text() || $el.val() : opts.text;
|
||
|
||
var sizes = [];
|
||
if(opts.width === undefined) sizes.push('width');
|
||
if(opts.height === undefined) sizes.push('height');
|
||
|
||
if(sizes.length) sizes = $el.dndCss(sizes);
|
||
|
||
for(var key in sizes){
|
||
var val = sizes[key];
|
||
|
||
if(val[val.length-1] == '%') return;
|
||
opts[key] = sizes[key];
|
||
}
|
||
|
||
var realSize = getRealSize(text, font), currSize = getCurrSize($el,0,0);
|
||
if(!realSize.width || !realSize.height) {
|
||
$el.dndCss('font-size', '');
|
||
return
|
||
}
|
||
|
||
currSize.width = parseFloat(opts.width);
|
||
currSize.height = parseFloat(opts.height);
|
||
|
||
var kof1 = currSize.height / realSize.height;
|
||
var kof2 = currSize.width / realSize.width;
|
||
|
||
var max = scope.$eval(attrs.dndFittextMax);
|
||
var min = scope.$eval(attrs.dndFittextMin);
|
||
|
||
if(min == undefined) min = 0;
|
||
if(max == undefined) max = Number.POSITIVE_INFINITY;
|
||
|
||
var kof = (kof1 < kof2 ? kof1 : kof2);
|
||
|
||
//Корректировка плавности
|
||
kof *= 0.85;
|
||
if((kof > 0.95 && kof <= 1) || (kof >= 1 && kof < 1.05) ) return;
|
||
|
||
var n = kof * parseFloat(font['font-size']);
|
||
|
||
n = getNumFromSegment(min, n, max);
|
||
|
||
$el.dndCss('font-size', n+'px');
|
||
}
|
||
|
||
scope.$watch( attrs.dndFittext, throttle(function(opts){
|
||
updateSize(opts);
|
||
}), true);
|
||
|
||
$($window).on('resize', function(){
|
||
updateSize();
|
||
});
|
||
}
|
||
};
|
||
}])
|
||
;
|
||
|
||
module.directive('dndKeyModel', ['$parse', 'dndKey', function($parse, dndKey){
|
||
return {
|
||
restrict: 'A',
|
||
link: function(scope, $el, attrs) {
|
||
var getter = $parse(attrs.dndKeyModel), setter = getter.assign;
|
||
|
||
scope.$watch(function(){ return dndKey.get() }, function(n,o){
|
||
if(n === undefined) return;
|
||
setter(scope, n);
|
||
});
|
||
}
|
||
}
|
||
}])
|
||
;
|
||
|
||
module.directive('dndContainment', ['$parse', function($parse){
|
||
|
||
Controller.$inject = ['$element', '$attrs', '$scope'];
|
||
function Controller( $element, $attrs, $scope){
|
||
var getterSelector = $parse($attrs.dndContainment);
|
||
|
||
this.get = function () {
|
||
var selector = getterSelector($scope);
|
||
|
||
return selector ? $element.dndClosest(selector).eq(0) : $element.parent();
|
||
}
|
||
}
|
||
|
||
return {
|
||
restrict: 'EAC',
|
||
controller: Controller,
|
||
}
|
||
}])
|
||
;
|
||
|
||
module.factory('dndKey', ['$rootScope', function ($rootScope) {
|
||
var keys = [];
|
||
|
||
function DndKey(){
|
||
|
||
};
|
||
|
||
DndKey.prototype = {
|
||
get: function(){
|
||
return keys;
|
||
},
|
||
isset: function(code){
|
||
var index = keys.indexOf(code);
|
||
return (index !== -1);
|
||
}
|
||
};
|
||
|
||
function keydown(event){
|
||
var code = event.keyCode;
|
||
debounceKeyup(event);
|
||
if(keys.indexOf(code) > -1) return;
|
||
|
||
keys.push(code);
|
||
$rootScope.$digest();
|
||
}
|
||
|
||
function keyup(event){
|
||
var code = event.keyCode, index = keys.indexOf(code);
|
||
if(index === -1) return;
|
||
|
||
keys.splice(index,1);
|
||
$rootScope.$digest();
|
||
};
|
||
|
||
|
||
|
||
var debounceKeyup = debounce(keyup, 1000);
|
||
$document.on('keydown', keydown);
|
||
$document.on('keyup', keyup);
|
||
|
||
return new DndKey;
|
||
}])
|
||
;
|
||
|
||
module.factory('DndLasso', [function () {
|
||
var $div = $('<div></div>').dndCss({position: 'absolute'});
|
||
|
||
var defaults = {
|
||
className: 'angular-dnd-lasso',
|
||
offsetX: 0,
|
||
offsetY: 0
|
||
};
|
||
|
||
function Handler(local){
|
||
|
||
this.getRect = function(){
|
||
return this.isActive ? local.rect : undefined;
|
||
}
|
||
|
||
this.getClientRect = function(){
|
||
return this.isActive ? $div.dndClientRect() : undefined;
|
||
}
|
||
|
||
this.isActive = function(){
|
||
return local.active;
|
||
}
|
||
}
|
||
|
||
function Local(api){
|
||
var isTarget = api.isTarget(), handler = new Handler(this);
|
||
|
||
this.isTarget = function(){
|
||
return isTarget;
|
||
}
|
||
|
||
this.handler = function(){
|
||
return handler;
|
||
}
|
||
|
||
this.getEvent = function(){
|
||
return api.getEvent();
|
||
}
|
||
}
|
||
|
||
function Lasso(opts){
|
||
|
||
var self = this;
|
||
|
||
opts = extend( {}, defaults, opts );
|
||
|
||
function dragstart(api) {
|
||
var local = api.local = new Local(api);
|
||
|
||
if( !local.isTarget() ) {
|
||
self.trigger('start', local.handler() );
|
||
return;
|
||
}
|
||
|
||
local.active = true;
|
||
|
||
self.trigger('start', local.handler() );
|
||
|
||
api.setReferenceElement(opts.$el);
|
||
api.setBounderElement(opts.$el);
|
||
|
||
local.startAxis = api.getRelBorderedAxis();
|
||
|
||
$div.removeAttr('class style').removeClass('ng-hide').addClass(opts.className);
|
||
|
||
opts.$el.append( $div );
|
||
|
||
};
|
||
|
||
function drag(api) {
|
||
var local = api.local;
|
||
|
||
if( !local.active ) {
|
||
self.trigger('drag', local.handler());
|
||
return;
|
||
}
|
||
|
||
var change = api.getRelBorderedAxis().minus(local.startAxis);
|
||
|
||
var rect = {
|
||
top: local.startAxis.y,
|
||
left: local.startAxis.x,
|
||
width: change.x,
|
||
height: change.y
|
||
};
|
||
|
||
if(rect.width < 0) {
|
||
rect.width = - rect.width;
|
||
rect.left = rect.left - rect.width;
|
||
}
|
||
|
||
if(rect.height < 0) {
|
||
rect.height = - rect.height;
|
||
rect.top = rect.top - rect.height;
|
||
}
|
||
|
||
local.rect = rect;
|
||
|
||
rect.top += opts.offsetY;
|
||
rect.left += opts.offsetX;
|
||
|
||
$div.dndCss(rect);
|
||
|
||
self.trigger('drag', local.handler() );
|
||
};
|
||
|
||
function dragend(api) {
|
||
var local = api.local;
|
||
|
||
if( !local.active ) {
|
||
self.trigger('end', local.handler());
|
||
return;
|
||
}
|
||
|
||
$div.addClass('ng-hide');
|
||
|
||
$(document.body).append( $div );
|
||
|
||
self.trigger('end', local.handler() );
|
||
};
|
||
|
||
var bindings = {
|
||
'$$lasso.dragstart': dragstart,
|
||
'$$lasso.drag': drag,
|
||
'$$lasso.dragend': dragend
|
||
};
|
||
|
||
opts.$el.dndBind(bindings);
|
||
|
||
this.destroy = function(){
|
||
opts.$el.dndUnbind();
|
||
};
|
||
|
||
var events = {};
|
||
|
||
this.on = function(name, fn) {
|
||
events[name] = events[name] || [];
|
||
events[name].push(fn);
|
||
};
|
||
|
||
this.trigger = function(name, args) {
|
||
events[name] = events[name] || [];
|
||
args = args || typeof args === 'string' ? [args] : [];
|
||
events[name].forEach(function(fn) {
|
||
fn.apply(this, args);
|
||
});
|
||
}
|
||
}
|
||
|
||
return Lasso;
|
||
}])
|
||
;
|
||
|
||
module.factory('EventEmitter', [function () {
|
||
|
||
function EventEmitter() {
|
||
var events = {};
|
||
|
||
this.on = function(name, fn) {
|
||
events[name] = events[name] || [];
|
||
events[name].push(fn);
|
||
};
|
||
|
||
this.off = function(name, fn) {
|
||
if(!events[name]) return;
|
||
|
||
for(var i = 0, length = events[name].length; i < length; i++){
|
||
if(events[name][i] === fn) events[name].splice(i, 1);
|
||
}
|
||
};
|
||
|
||
this.trigger = function(name, args) {
|
||
events[name] = events[name] || [];
|
||
args = args || typeof args === 'string' ? [args] : [];
|
||
events[name].forEach(function(fn) {
|
||
fn.apply(this, args);
|
||
});
|
||
}
|
||
}
|
||
|
||
return EventEmitter;
|
||
}])
|
||
|
||
|
||
})(angular, undefined, window, document); |