123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- /**
- drag/drop functionality for use with jsPlumb but with
- no knowledge of jsPlumb. supports multiple scopes (separated by whitespace), dragging
- multiple elements, constrain to parent, drop filters, drag start filters, custom
- css classes.
- a lot of the functionality of this script is expected to be plugged in:
- addClass
- removeClass
- addEvent
- removeEvent
- getPosition
- setPosition
- getSize
- indexOf
- intersects
- the name came from here:
- http://mrsharpoblunto.github.io/foswig.js/
- copyright 2014 jsPlumb
- */
- ;(function() {
- "use strict";
- var getOffsetRect = function (elem) {
- // (1)
- var box = elem.getBoundingClientRect();
- var body = document.body;
- var docElem = document.documentElement;
- // (2)
- var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
- var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
- // (3)
- var clientTop = docElem.clientTop || body.clientTop || 0;
- var clientLeft = docElem.clientLeft || body.clientLeft || 0;
- // (4)
- var top = box.top + scrollTop - clientTop;
- var left = box.left + scrollLeft - clientLeft;
- return { top: Math.round(top), left: Math.round(left) };
- };
- var matchesSelector = function(el, selector, ctx) {
- ctx = ctx || el.parentNode;
- var possibles = ctx.querySelectorAll(selector);
- for (var i = 0; i < possibles.length; i++) {
- if (possibles[i] === el)
- return true;
- }
- return false;
- };
- var iev = (function() {
- var rv = -1;
- if (navigator.appName == 'Microsoft Internet Explorer') {
- var ua = navigator.userAgent,
- re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
- if (re.exec(ua) != null)
- rv = parseFloat(RegExp.$1);
- }
- return rv;
- })(),
- isIELT9 = iev > -1 && iev < 9,
- _pl = function(e) {
- if (isIELT9) {
- return [ e.clientX + document.documentElement.scrollLeft, e.clientY + document.documentElement.scrollTop ];
- }
- else {
- var ts = _touches(e), t = _getTouch(ts, 0);
- // this is for iPad. may not fly for Android.
- return [t.pageX, t.pageY];
- }
- },
- _getTouch = function(touches, idx) { return touches.item ? touches.item(idx) : touches[idx]; },
- _touches = function(e) {
- return e.touches && e.touches.length > 0 ? e.touches :
- e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches :
- e.targetTouches && e.targetTouches.length > 0 ? e.targetTouches :
- [ e ];
- },
- _classes = {
- draggable:"katavorio-draggable", // draggable elements
- droppable:"katavorio-droppable", // droppable elements
- drag : "katavorio-drag", // elements currently being dragged
- selected:"katavorio-drag-selected", // elements in current drag selection
- active : "katavorio-drag-active", // droppables that are targets of a currently dragged element
- hover : "katavorio-drag-hover", // droppables over which a matching drag element is hovering
- noSelect : "katavorio-drag-no-select" // added to the body to provide a hook to suppress text selection
- },
- _defaultScope = "katavorio-drag-scope",
- _events = [ "stop", "start", "drag", "drop", "over", "out" ],
- _devNull = function() {},
- _true = function() { return true; },
- _foreach = function(l, fn, from) {
- for (var i = 0; i < l.length; i++) {
- if (l[i] != from)
- fn(l[i]);
- }
- },
- _setDroppablesActive = function(dd, val, andHover, drag) {
- _foreach(dd, function(e) {
- e.setActive(val);
- if (val) e.updatePosition();
- if (andHover) e.setHover(drag, val);
- });
- },
- _each = function(obj, fn) {
- if (obj == null) return;
- obj = (typeof obj !== "string") && (obj.tagName == null && obj.length != null) ? obj : [ obj ];
- for (var i = 0; i < obj.length; i++)
- fn.apply(obj[i], [ obj[i] ]);
- },
- _consume = function(e) {
- if (e.stopPropagation) {
- e.stopPropagation();
- e.preventDefault();
- }
- else {
- e.returnValue = false;
- }
- },
- _defaultInputFilterSelector = "input,textarea,select,button",
- //
- // filters out events on all input elements, like textarea, checkbox, input, select.
- _inputFilter = function(e, el, _katavorio) {
- var t = e.srcElement || e.target;
- return !matchesSelector(t, _katavorio.getInputFilterSelector(), el);
- };
- var Super = function(el, params, css, scope) {
- this.params = params || {};
- this.el = el;
- this.params.addClass(this.el, this._class);
- var enabled = true;
- this.setEnabled = function(e) { enabled = e; };
- this.isEnabled = function() { return enabled; };
- this.toggleEnabled = function() { enabled = !enabled; };
- this.setScope = function(scopes) {
- this.scopes = scopes ? scopes.split(/\s+/) : [ scope ];
- };
- this.addScope = function(scopes) {
- var m = {};
- _each(this.scopes, function(s) { m[s] = true;});
- _each(scopes ? scopes.split(/\s+/) : [], function(s) { m[s] = true;});
- this.scopes = [];
- for (var i in m) this.scopes.push(i);
- };
- this.removeScope = function(scopes) {
- var m = {};
- _each(this.scopes, function(s) { m[s] = true;});
- _each(scopes ? scopes.split(/\s+/) : [], function(s) { delete m[s];});
- this.scopes = [];
- for (var i in m) this.scopes.push(i);
- };
- this.toggleScope = function(scopes) {
- var m = {};
- _each(this.scopes, function(s) { m[s] = true;});
- _each(scopes ? scopes.split(/\s+/) : [], function(s) {
- if (m[s]) delete m[s];
- else m[s] = true;
- });
- this.scopes = [];
- for (var i in m) this.scopes.push(i);
- };
- this.setScope(params.scope);
- this.k = params.katavorio;
- return params.katavorio;
- };
- var Drag = function(el, params, css, scope) {
- this._class = css.draggable;
- var k = Super.apply(this, arguments);
- this.rightButtonCanDrag = this.params.rightButtonCanDrag;
- var downAt = [0,0], posAtDown = null, moving = false,
- consumeStartEvent = this.params.consumeStartEvent !== false,
- dragEl = this.el,
- clone = this.params.clone;
- this.toGrid = function(pos) {
- return this.params.grid == null ? pos :
- [
- this.params.grid[0] * Math.floor(pos[0] / this.params.grid[0]),
- this.params.grid[1] * Math.floor(pos[1] / this.params.grid[1])
- ];
- };
- this.constrain = typeof this.params.constrain === "function" ? this.params.constrain : (this.params.constrain || this.params.containment) ? function(pos) {
- return [
- Math.max(0, Math.min(constrainRect.w - this.size[0], pos[0])),
- Math.max(0, Math.min(constrainRect.h - this.size[1], pos[1]))
- ];
- } : function(pos) { return pos; };
- var filter = _true,
- filterSpec = "",
- filterExclude = this.params.filterExclude !== false,
- _setFilter = this.setFilter = function(f, _exclude) {
- if (f) {
- filterSpec = f;
- filterExclude = _exclude !== false;
- filter = function(e) {
- var t = e.srcElement || e.target, ms = matchesSelector(t, f, el);
- return filterExclude ? !ms : ms;
- };
- }
- };
- this.canDrag = this.params.canDrag || _true;
- var constrainRect,
- matchingDroppables = [], intersectingDroppables = [];
- this.downListener = function(e) {
- var isNotRightClick = this.rightButtonCanDrag || (e.which !== 3 && e.button !== 2);
- if (isNotRightClick && this.isEnabled() && this.canDrag()) {
- var _f = filter(e) && _inputFilter(e, this.el, this.k);
- if (_f) {
- if (!clone)
- dragEl = this.el;
- else {
- dragEl = this.el.cloneNode(true);
- dragEl.setAttribute("id", null);
- dragEl.style.position = "absolute";
- // the clone node is added to the body; getOffsetRect gives us a value
- // relative to the body.
- var b = getOffsetRect(this.el);
- dragEl.style.left = b.left + "px";
- dragEl.style.top = b.top + "px";
- document.body.appendChild(dragEl);
- }
- consumeStartEvent && _consume(e);
- downAt = _pl(e);
- //
- this.params.bind(document, "mousemove", this.moveListener);
- this.params.bind(document, "mouseup", this.upListener);
- k.markSelection(this);
- this.params.addClass(document.body, css.noSelect);
- }
- else if (this.params.consumeFilteredEvents) {
- _consume(e);
- }
- }
- }.bind(this);
- this.moveListener = function(e) {
- if (downAt) {
- if (!moving) {
- this.params.events["start"]({el:this.el, pos:posAtDown, e:e, drag:this});
- this.mark();
- moving = true;
- }
- intersectingDroppables.length = 0;
- var pos = _pl(e), dx = pos[0] - downAt[0], dy = pos[1] - downAt[1],
- z = this.params.ignoreZoom ? 1 : k.getZoom();
- dx /= z;
- dy /= z;
- this.moveBy(dx, dy, e);
- k.updateSelection(dx, dy, this);
- }
- }.bind(this);
- this.upListener = function(e) {
- downAt = null;
- moving = false;
- this.params.unbind(document, "mousemove", this.moveListener);
- this.params.unbind(document, "mouseup", this.upListener);
- this.params.removeClass(document.body, css.noSelect);
- this.unmark(e);
- k.unmarkSelection(this, e);
- this.stop(e);
- k.notifySelectionDragStop(this, e);
- if (clone) {
- dragEl && dragEl.parentNode && dragEl.parentNode.removeChild(dragEl);
- dragEl = null;
- }
- }.bind(this);
- this.getFilter = function() { return filterSpec; };
- this.isFilterExclude = function() { return filterExclude; };
- this.abort = function() {
- if (downAt != null)
- this.upListener();
- };
- this.getDragElement = function() {
- return dragEl || this.el;
- };
- this.stop = function(e) {
- this.params.events["stop"]({el:dragEl, pos:this.params.getPosition(dragEl), e:e, drag:this});
- };
- this.mark = function() {
- posAtDown = this.params.getPosition(dragEl);
- this.size = this.params.getSize(dragEl);
- matchingDroppables = k.getMatchingDroppables(this);
- _setDroppablesActive(matchingDroppables, true, false, this);
- this.params.addClass(dragEl, this.params.dragClass || css.drag);
- if (this.params.constrain || this.params.containment) {
- var cs = this.params.getSize(dragEl.parentNode);
- constrainRect = { w:cs[0], h:cs[1] };
- }
- };
- this.unmark = function(e) {
- _setDroppablesActive(matchingDroppables, false, true, this);
- matchingDroppables.length = 0;
- for (var i = 0; i < intersectingDroppables.length; i++)
- intersectingDroppables[i].drop(this, e);
- };
- this.moveBy = function(dx, dy, e) {
- intersectingDroppables.length = 0;
- var cPos = this.constrain(this.toGrid(([posAtDown[0] + dx, posAtDown[1] + dy])), dragEl),
- rect = { x:cPos[0], y:cPos[1], w:this.size[0], h:this.size[1]};
- this.params.setPosition(dragEl, cPos);
- for (var i = 0; i < matchingDroppables.length; i++) {
- var r2 = { x:matchingDroppables[i].position[0], y:matchingDroppables[i].position[1], w:matchingDroppables[i].size[0], h:matchingDroppables[i].size[1]};
- if (this.params.intersects(rect, r2) && matchingDroppables[i].canDrop(this)) {
- intersectingDroppables.push(matchingDroppables[i]);
- matchingDroppables[i].setHover(this, true, e);
- }
- else if (matchingDroppables[i].el._katavorioDragHover) {
- matchingDroppables[i].setHover(this, false, e);
- }
- }
- this.params.events["drag"]({el:this.el, pos:cPos, e:e, drag:this});
- };
- this.destroy = function() {
- this.params.unbind(this.el, "mousedown", this.downListener);
- this.params.unbind(document, "mousemove", this.moveListener);
- this.params.unbind(document, "mouseup", this.upListener);
- this.downListener = null;
- this.upListener = null;
- this.moveListener = null;
- //this.params = null;
- //this.el = null;
- //dragEl = null;
- };
- // init:register mousedown, and perhaps set a filter
- this.params.bind(this.el, "mousedown", this.downListener);
- // if handle provded, use that. otherwise, try to set a filter.
- // note that a `handle` selector always results in filterExclude being set to false, ie.
- // the selector defines the handle element(s).
- if (this.params.handle)
- _setFilter(this.params.handle, false);
- else
- _setFilter(this.params.filter, this.params.filterExclude);
- };
- var Drop = function(el, params, css, scope) {
- this._class = css.droppable;
- this.params = params || {};
- this._activeClass = params.activeClass || css.active;
- this._hoverClass = params.hoverClass || css.hover;
- Super.apply(this, arguments)
- var hover = false;
- this.setActive = function(val) {
- this.params[val ? "addClass" : "removeClass"](this.el, this._activeClass);
- };
- this.updatePosition = function() {
- this.position = this.params.getPosition(this.el);
- this.size = this.params.getSize(this.el);
- };
- this.canDrop = this.params.canDrop || function(drag) {
- return true;
- };
- this.setHover = function(drag, val, e) {
- // if turning off hover but this was not the drag that caused the hover, ignore.
- if (val || this.el._katavorioDragHover == null || this.el._katavorioDragHover == drag.el._katavorio) {
- this.params[val ? "addClass" : "removeClass"](this.el, this._hoverClass);
- this.el._katavorioDragHover = val ? drag.el._katavorio : null;
- if (hover !== val)
- this.params.events[val ? "over" : "out"]({el:this.el, e:e, drag:drag, drop:this});
- hover = val;
- }
- };
- this.drop = function(drag, event) {
- this.params.events["drop"]({ drag:drag, e:event, drop:this });
- };
- this.destroy = function() {
- this._class = null;
- this._activeClass = null;
- this._hoverClass = null;
- //this.params = null;
- hover = null;
- //this.el = null;
- };
- };
- var _uuid = function() {
- return ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
- return v.toString(16);
- }));
- };
- var _gel = function(el) {
- if (el == null) return null;
- el = typeof el === "string" ? document.getElementById(el) : el;
- if (el == null) return null;
- el._katavorio = el._katavorio || _uuid();
- return el;
- };
- this.Katavorio = function(katavorioParams) {
- var _selection = [],
- _selectionMap = {};
- this._dragsByScope = {};
- this._dropsByScope = {};
- var _zoom = 1,
- _reg = function(obj, map) {
- for(var i = 0; i < obj.scopes.length; i++) {
- map[obj.scopes[i]] = map[obj.scopes[i]] || [];
- map[obj.scopes[i]].push(obj);
- }
- },
- _unreg = function(obj, map) {
- var c = 0;
- for(var i = 0; i < obj.scopes.length; i++) {
- if (map[obj.scopes[i]]) {
- var idx = katavorioParams.indexOf(map[obj.scopes[i]], obj);
- if (idx != -1) {
- map[obj.scopes[i]].splice(idx, 1);
- c++;
- }
- }
- }
- return c > 0 ;
- },
- _getMatchingDroppables = this.getMatchingDroppables = function(drag) {
- var dd = [], _m = {};
- for (var i = 0; i < drag.scopes.length; i++) {
- var _dd = this._dropsByScope[drag.scopes[i]];
- if (_dd) {
- for (var j = 0; j < _dd.length; j++) {
- if (_dd[j].canDrop(drag) && !_m[_dd[j].el._katavorio] && _dd[j].el !== drag.el) {
- _m[_dd[j].el._katavorio] = true;
- dd.push(_dd[j]);
- }
- }
- }
- }
- return dd;
- },
- _prepareParams = function(p) {
- p = p || {};
- var _p = {
- events:{}
- };
- for (var i in katavorioParams) _p[i] = katavorioParams[i];
- for (var i in p) _p[i] = p[i];
- // events
- for (var i = 0; i < _events.length; i++) {
- _p.events[_events[i]] = p[_events[i]] || _devNull;
- }
- _p.katavorio = this;
- return _p;
- }.bind(this),
- _css = {},
- overrideCss = katavorioParams.css || {},
- _scope = katavorioParams.scope || _defaultScope;
- // prepare map of css classes based on defaults frst, then optional overrides
- for (var i in _classes) _css[i] = _classes[i];
- for (var i in overrideCss) _css[i] = overrideCss[i];
- var inputFilterSelector = katavorioParams.inputFilterSelector || _defaultInputFilterSelector;
- /**
- * Gets the selector identifying which input elements to filter from drag events.
- * @method getInputFilterSelector
- * @return {String} Current input filter selector.
- */
- this.getInputFilterSelector = function() { return inputFilterSelector; };
- /**
- * Sets the selector identifying which input elements to filter from drag events.
- * @method setInputFilterSelector
- * @param {String} selector Input filter selector to set.
- * @return {Katavorio} Current instance; method may be chained.
- */
- this.setInputFilterSelector = function(selector) {
- inputFilterSelector = selector;
- return this;
- };
- this.draggable = function(el, params) {
- var o = [];
- _each(el, function(_el) {
- _el = _gel(_el);
- if (_el != null) {
- var p = _prepareParams(params);
- _el._katavorioDrag = new Drag(_el, p, _css, _scope);
- _reg(_el._katavorioDrag, this._dragsByScope);
- o.push(_el._katavorioDrag);
- katavorioParams.addClass(_el, _css.draggable);
- }
- }.bind(this));
- return o;
- };
- this.droppable = function(el, params) {
- var o = [];
- _each(el, function(_el) {
- _el = _gel(_el);
- if (_el != null) {
- _el._katavorioDrop = new Drop(_el, _prepareParams(params), _css, _scope);
- _reg(_el._katavorioDrop, this._dropsByScope);
- o.push(_el._katavorioDrop);
- katavorioParams.addClass(_el, _css.droppable);
- }
- }.bind(this));
- return o;
- };
- /**
- * @name Katavorio#select
- * @function
- * @desc Adds an element to the current selection (for multiple node drag)
- * @param {Element|String} DOM element - or id of the element - to add.
- */
- this.select = function(el) {
- _each(el, function() {
- var _el = _gel(this);
- if (_el && _el._katavorioDrag) {
- if (!_selectionMap[_el._katavorio]) {
- _selection.push(_el._katavorioDrag);
- _selectionMap[_el._katavorio] = [ _el, _selection.length - 1 ];
- katavorioParams.addClass(_el, _css.selected);
- }
- }
- });
- return this;
- };
- /**
- * @name Katavorio#deselect
- * @function
- * @desc Removes an element from the current selection (for multiple node drag)
- * @param {Element|String} DOM element - or id of the element - to remove.
- */
- this.deselect = function(el) {
- _each(el, function() {
- var _el = _gel(this);
- if (_el && _el._katavorio) {
- var e = _selectionMap[_el._katavorio];
- if (e) {
- var _s = [];
- for (var i = 0; i < _selection.length; i++)
- if (_selection[i].el !== _el) _s.push(_selection[i]);
- _selection = _s;
- delete _selectionMap[_el._katavorio];
- katavorioParams.removeClass(_el, _css.selected);
- }
- }
- });
- return this;
- };
- this.deselectAll = function() {
- for (var i in _selectionMap) {
- var d = _selectionMap[i];
- katavorioParams.removeClass(d[0], _css.selected);
- }
- _selection.length = 0;
- _selectionMap = {};
- };
- this.markSelection = function(drag) {
- _foreach(_selection, function(e) { e.mark(); }, drag);
- };
- this.unmarkSelection = function(drag, event) {
- _foreach(_selection, function(e) { e.unmark(event); }, drag);
- };
- this.getSelection = function() { return _selection.slice(0); };
- this.updateSelection = function(dx, dy, drag) {
- _foreach(_selection, function(e) { e.moveBy(dx, dy); }, drag);
- };
- this.notifySelectionDragStop = function(drag, evt) {
- _foreach(_selection, function(e) { e.stop(evt); }, drag);
- };
- this.setZoom = function(z) { _zoom = z; };
- this.getZoom = function() { return _zoom; };
- // does the work of changing scopes
- var _scopeManip = function(kObj, scopes, map, fn) {
- if (kObj != null) {
- _unreg(kObj, map); // deregister existing scopes
- kObj[fn](scopes); // set scopes
- _reg(kObj, map); // register new ones
- }
- };
- _each([ "set", "add", "remove", "toggle"], function(v) {
- this[v + "Scope"] = function(el, scopes) {
- _scopeManip(el._katavorioDrag, scopes, this._dragsByScope, v + "Scope");
- _scopeManip(el._katavorioDrop, scopes, this._dropsByScope, v + "Scope");
- }.bind(this);
- this[v + "DragScope"] = function(el, scopes) {
- _scopeManip(el._katavorioDrag, scopes, this._dragsByScope, v + "Scope");
- }.bind(this);
- this[v + "DropScope"] = function(el, scopes) {
- _scopeManip(el._katavorioDrop, scopes, this._dropsByScope, v + "Scope");
- }.bind(this);
- }.bind(this));
- this.getDragsForScope = function(s) { return this._dragsByScope[s]; };
- this.getDropsForScope = function(s) { return this._dropsByScope[s]; };
- var _destroy = function(el, type, map) {
- el = _gel(el);
- if (el[type]) {
- if (_unreg(el[type], map))
- el[type].destroy();
- el[type] = null;
- }
- };
- this.elementRemoved = function(el) {
- this.destroyDraggable(el);
- this.destroyDroppable(el);
- };
- this.destroyDraggable = function(el) {
- _destroy(el, "_katavorioDrag", this._dragsByScope);
- };
- this.destroyDroppable = function(el) {
- _destroy(el, "_katavorioDrop", this._dropsByScope);
- };
- };
- }).call(this);
|