|
|
Line 6: |
Line 6: |
| * Provides a way to visually connect elements on an HTML page, using SVG or VML. | | * Provides a way to visually connect elements on an HTML page, using SVG or VML. |
| * | | * |
- | * This file contains the core code. | + | * This file contains the 'vanilla' adapter - having no external dependencies other than bundled libs. |
| * | | * |
| * Copyright (c) 2010 - 2014 Simon Porritt (simon@jsplumbtoolkit.com) | | * Copyright (c) 2010 - 2014 Simon Porritt (simon@jsplumbtoolkit.com) |
Line 16: |
Line 16: |
| */ | | */ |
| ;(function() { | | ;(function() { |
- |
| + | |
| "use strict"; | | "use strict"; |
- |
| |
- | var _ju = jsPlumbUtil,
| |
- | _getOffset = function(el, _instance, relativeToRoot) {
| |
- | return jsPlumbAdapter.getOffset(el, _instance, relativeToRoot);
| |
- | },
| |
- |
| |
- | /**
| |
- | * creates a timestamp, using milliseconds since 1970, but as a string.
| |
- | */
| |
- | _timestamp = function() { return "" + (new Date()).getTime(); },
| |
| | | |
- | // helper method to update the hover style whenever it, or paintStyle, changes.
| + | var _getDragManager = function(instance, isPlumbedComponent) { |
- | // we use paintStyle as the foundation and merge hoverPaintStyle over the
| + | var k = instance[isPlumbedComponent ? "_internalKatavorio" : "_katavorio"], |
- | // top.
| + | e = _getEventManager(instance); |
- | _updateHoverStyle = function(component) {
| + | |
- | if (component._jsPlumb.paintStyle && component._jsPlumb.hoverPaintStyle) {
| + | |
- | var mergedHoverStyle = {};
| + | |
- | jsPlumb.extend(mergedHoverStyle, component._jsPlumb.paintStyle);
| + | |
- | jsPlumb.extend(mergedHoverStyle, component._jsPlumb.hoverPaintStyle);
| + | |
- | delete component._jsPlumb.hoverPaintStyle;
| + | |
- | // we want the fillStyle of paintStyle to override a gradient, if possible.
| + | |
- | if (mergedHoverStyle.gradient && component._jsPlumb.paintStyle.fillStyle)
| + | |
- | delete mergedHoverStyle.gradient;
| + | |
- | component._jsPlumb.hoverPaintStyle = mergedHoverStyle;
| + | |
- | }
| + | |
- | },
| + | |
- | events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ],
| + | |
- | eventFilters = { "mouseout":"mouseleave", "mouseexit":"mouseleave" },
| + | |
- | _updateAttachedElements = function(component, state, timestamp, sourceElement) {
| + | |
- | var affectedElements = component.getAttachedElements();
| + | |
- | if (affectedElements) {
| + | |
- | for (var i = 0, j = affectedElements.length; i < j; i++) {
| + | |
- | if (!sourceElement || sourceElement != affectedElements[i])
| + | |
- | affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements.
| + | |
- | }
| + | |
- | } | + | |
- | },
| + | |
- | _splitType = function(t) { return t == null ? null : t.split(" "); },
| + | |
- | _applyTypes = function(component, params, doNotRepaint) {
| + | |
- | if (component.getDefaultType) {
| + | |
- | var td = component.getTypeDescriptor();
| + | |
- |
| + | |
- | var o = _ju.merge({}, component.getDefaultType());
| + | |
- | for (var i = 0, j = component._jsPlumb.types.length; i < j; i++)
| + | |
- | o = _ju.merge(o, component._jsPlumb.instance.getType(component._jsPlumb.types[i], td), [ "cssClass" ]);
| + | |
- |
| + | |
- | if (params) {
| + | |
- | o = _ju.populate(o, params);
| + | |
- | }
| + | |
| | | |
- | component.applyType(o, doNotRepaint);
| + | if (!k) { |
- | if (!doNotRepaint) component.repaint();
| + | k = new Katavorio( { |
- | }
| + | bind:e.on, |
- | },
| + | unbind:e.off, |
- | | + | getSize:jsPlumb.getSize, |
- | // ------------------------------ BEGIN jsPlumbUIComponent --------------------------------------------
| + | getPosition:function(el) { |
- | | + | var o = jsPlumbAdapter.getOffset(el, instance); |
- | jsPlumbUIComponent = window.jsPlumbUIComponent = function(params) {
| + | return [o.left, o.top]; |
- | | + | |
- | jsPlumbUtil.EventGenerator.apply(this, arguments); | + | |
- | | + | |
- | var self = this,
| + | |
- | a = arguments,
| + | |
- | idPrefix = self.idPrefix,
| + | |
- | id = idPrefix + (new Date()).getTime();
| + | |
- | | + | |
- | this._jsPlumb = {
| + | |
- | instance: params._jsPlumb, | + | |
- | parameters:params.parameters || {}, | + | |
- | paintStyle:null, | + | |
- | hoverPaintStyle:null,
| + | |
- | paintStyleInUse:null,
| + | |
- | hover:false,
| + | |
- | beforeDetach:params.beforeDetach,
| + | |
- | beforeDrop:params.beforeDrop, | + | |
- | overlayPlacements : [],
| + | |
- | hoverClass: params.hoverClass || params._jsPlumb.Defaults.HoverClass,
| + | |
- | types:[]
| + | |
- | };
| + | |
- | | + | |
- | this.getId = function() { return id; };
| + | |
- |
| + | |
- | // all components can generate events
| + | |
- |
| + | |
- | if (params.events) {
| + | |
- | for (var i in params.events)
| + | |
- | self.bind(i, params.events[i]); | + | |
- | }
| + | |
- | | + | |
- | // all components get this clone function.
| + | |
- | // TODO issue 116 showed a problem with this - it seems 'a' that is in
| + | |
- | // the clone function's scope is shared by all invocations of it, the classic
| + | |
- | // JS closure problem. for now, jsPlumb does a version of this inline where
| + | |
- | // it used to call clone. but it would be nice to find some time to look
| + | |
- | // further at this.
| + | |
- | this.clone = function() {
| + | |
- | var o = {};//new Object();
| + | |
- | this.constructor.apply(o, a);
| + | |
- | return o;
| + | |
- | }.bind(this);
| + | |
- |
| + | |
- | // user can supply a beforeDetach callback, which will be executed before a detach
| + | |
- | // is performed; returning false prevents the detach.
| + | |
- | this.isDetachAllowed = function(connection) {
| + | |
- | var r = true;
| + | |
- | if (this._jsPlumb.beforeDetach) {
| + | |
- | try {
| + | |
- | r = this._jsPlumb.beforeDetach(connection);
| + | |
- | }
| + | |
- | catch (e) { _ju.log("jsPlumb: beforeDetach callback failed", e); }
| + | |
- | }
| + | |
- | return r;
| + | |
- | };
| + | |
- |
| + | |
- | // user can supply a beforeDrop callback, which will be executed before a dropped
| + | |
- | // connection is confirmed. user can return false to reject connection.
| + | |
- | this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint, source, target) {
| + | |
- | var r = this._jsPlumb.instance.checkCondition("beforeDrop", {
| + | |
- | sourceId:sourceId,
| + | |
- | targetId:targetId,
| + | |
- | scope:scope,
| + | |
- | connection:connection,
| + | |
- | dropEndpoint:dropEndpoint,
| + | |
- | source:source, target:target
| + | |
- | });
| + | |
- | if (this._jsPlumb.beforeDrop) {
| + | |
- | try {
| + | |
- | r = this._jsPlumb.beforeDrop({
| + | |
- | sourceId:sourceId,
| + | |
- | targetId:targetId,
| + | |
- | scope:scope,
| + | |
- | connection:connection,
| + | |
- | dropEndpoint:dropEndpoint,
| + | |
- | source:source, target:target
| + | |
- | });
| + | |
- | }
| + | |
- | catch (e) { _ju.log("jsPlumb: beforeDrop callback failed", e); }
| + | |
- | }
| + | |
- | return r; | + | |
- | };
| + | |
- | | + | |
- | var boundListeners = [],
| + | |
- | bindAListener = function(obj, type, fn) {
| + | |
- | boundListeners.push([obj, type, fn]);
| + | |
- | obj.bind(type, fn);
| + | |
- | },
| + | |
- | domListeners = [],
| + | |
- | bindOne = function(o, c, evt, override) {
| + | |
- | var filteredEvent = eventFilters[evt] || evt,
| + | |
- | fn = function(ee) {
| + | |
- | if (override && override(ee) === false) return;
| + | |
- | c.fire(filteredEvent, c, ee);
| + | |
- | };
| + | |
- | domListeners.push([o, evt, fn, c]);
| + | |
- | c._jsPlumb.instance.on(o, evt, fn);
| + | |
| }, | | }, |
- | unbindOne = function(o, evt, fn, c) { | + | setPosition:function(el, xy) { |
- | var filteredEvent = eventFilters[evt] || evt; | + | el.style.left = xy[0] + "px"; |
- | c._jsPlumb.instance.off(o, evt, fn);
| + | el.style.top = xy[1] + "px"; |
- | };
| + | |
- | | + | |
- | // sets the component associated with listener events. for instance, an overlay delegates
| + | |
- | // its events back to a connector. but if the connector is swapped on the underlying connection,
| + | |
- | // then this component must be changed. This is called by setConnector in the Connection class.
| + | |
- | this.setListenerComponent = function(c) {
| + | |
- | for (var i = 0; i < domListeners.length; i++)
| + | |
- | domListeners[i][3] = c;
| + | |
- | };
| + | |
- | | + | |
- | this.bindListeners = function(obj, _self, _hoverFunction) {
| + | |
- | bindAListener(obj, "click", function(ep, e) { _self.fire("click", _self, e); });
| + | |
- | bindAListener(obj, "dblclick", function(ep, e) { _self.fire("dblclick", _self, e); });
| + | |
- | bindAListener(obj, "contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); });
| + | |
- | bindAListener(obj, "mouseleave", function(ep, e) {
| + | |
- | if (_self.isHover()) {
| + | |
- | _hoverFunction(false);
| + | |
- | _self.fire("mouseleave", _self, e);
| + | |
- | }
| + | |
- | });
| + | |
- | bindAListener(obj, "mouseenter", function(ep, e) {
| + | |
- | if (!_self.isHover()) {
| + | |
- | _hoverFunction(true);
| + | |
- | _self.fire("mouseenter", _self, e);
| + | |
- | }
| + | |
- | });
| + | |
- | bindAListener(obj, "mousedown", function(ep, e) { _self.fire("mousedown", _self, e); });
| + | |
- | bindAListener(obj, "mouseup", function(ep, e) { _self.fire("mouseup", _self, e); });
| + | |
- | };
| + | |
- | | + | |
- | this.unbindListeners = function() {
| + | |
- | for (var i = 0; i < boundListeners.length; i++) {
| + | |
- | var o = boundListeners[i];
| + | |
- | o[0].unbind(o[1], o[2]);
| + | |
- | }
| + | |
- | boundListeners = null;
| + | |
- | };
| + | |
- |
| + | |
- | this.attachListeners = function(o, c, overrides) {
| + | |
- | overrides = overrides || {};
| + | |
- | for (var i = 0, j = events.length; i < j; i++) {
| + | |
- | bindOne(o, c, events[i], overrides[events[i]]); | + | |
- | }
| + | |
- | };
| + | |
- | this.detachListeners = function() {
| + | |
- | for (var i = 0; i < domListeners.length; i++) {
| + | |
- | unbindOne(domListeners[i][0], domListeners[i][1], domListeners[i][2], domListeners[i][3]);
| + | |
- | }
| + | |
- | domListeners = null;
| + | |
- | };
| + | |
- |
| + | |
- | this.reattachListenersForElement = function(o) {
| + | |
- | if (arguments.length > 1) {
| + | |
- | for (var i = 0, j = events.length; i < j; i++)
| + | |
- | unbindOne(o, events[i]);
| + | |
- | for (i = 1, j = arguments.length; i < j; i++)
| + | |
- | this.attachListeners(o, arguments[i]);
| + | |
- | }
| + | |
- | };
| + | |
- | };
| + | |
- | | + | |
- | var _removeTypeCssHelper = function(component, typeIndex) {
| + | |
- | var typeId = component._jsPlumb.types[typeIndex],
| + | |
- | type = component._jsPlumb.instance.getType(typeId, component.getTypeDescriptor());
| + | |
- | | + | |
- | if (type != null) {
| + | |
- | | + | |
- | if (type.cssClass && component.canvas)
| + | |
- | component._jsPlumb.instance.removeClass(component.canvas, type.cssClass);
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | jsPlumbUtil.extend(jsPlumbUIComponent, jsPlumbUtil.EventGenerator, {
| + | |
- |
| + | |
- | getParameter : function(name) {
| + | |
- | return this._jsPlumb.parameters[name];
| + | |
- | },
| + | |
- |
| + | |
- | setParameter : function(name, value) {
| + | |
- | this._jsPlumb.parameters[name] = value;
| + | |
- | },
| + | |
- |
| + | |
- | getParameters : function() {
| + | |
- | return this._jsPlumb.parameters;
| + | |
- | },
| + | |
- |
| + | |
- | setParameters : function(p) {
| + | |
- | this._jsPlumb.parameters = p;
| + | |
- | },
| + | |
- |
| + | |
- | addClass : function(clazz) {
| + | |
- | jsPlumbAdapter.addClass(this.canvas, clazz);
| + | |
- | },
| + | |
- |
| + | |
- | removeClass : function(clazz) {
| + | |
- | jsPlumbAdapter.removeClass(this.canvas, clazz);
| + | |
- | },
| + | |
- |
| + | |
- | setType : function(typeId, params, doNotRepaint) {
| + | |
- | this.clearTypes();
| + | |
- | this._jsPlumb.types = _splitType(typeId) || [];
| + | |
- | _applyTypes(this, params, doNotRepaint);
| + | |
- | },
| + | |
- |
| + | |
- | getType : function() {
| + | |
- | return this._jsPlumb.types;
| + | |
- | },
| + | |
- |
| + | |
- | reapplyTypes : function(params, doNotRepaint) {
| + | |
- | _applyTypes(this, params, doNotRepaint);
| + | |
- | },
| + | |
- |
| + | |
- | hasType : function(typeId) {
| + | |
- | return jsPlumbUtil.indexOf(this._jsPlumb.types, typeId) != -1;
| + | |
- | },
| + | |
- |
| + | |
- | addType : function(typeId, params, doNotRepaint) {
| + | |
- | var t = _splitType(typeId), _cont = false;
| + | |
- | if (t != null) {
| + | |
- | for (var i = 0, j = t.length; i < j; i++) {
| + | |
- | if (!this.hasType(t[i])) {
| + | |
- | this._jsPlumb.types.push(t[i]);
| + | |
- | _cont = true;
| + | |
- | }
| + | |
- | }
| + | |
- | if (_cont) _applyTypes(this, params, doNotRepaint);
| + | |
- | }
| + | |
- | },
| + | |
- |
| + | |
- | removeType : function(typeId, doNotRepaint) {
| + | |
- | var t = _splitType(typeId), _cont = false, _one = function(tt) {
| + | |
- | var idx = _ju.indexOf(this._jsPlumb.types, tt);
| + | |
- | if (idx != -1) {
| + | |
- | // remove css class if necessary
| + | |
- | _removeTypeCssHelper(this, idx);
| + | |
- | this._jsPlumb.types.splice(idx, 1);
| + | |
- | return true;
| + | |
- | }
| + | |
- | return false;
| + | |
- | }.bind(this);
| + | |
- |
| + | |
- | if (t != null) {
| + | |
- | for (var i = 0,j = t.length; i < j; i++) {
| + | |
- | _cont = _one(t[i]) || _cont;
| + | |
- | }
| + | |
- | if (_cont) _applyTypes(this, null, doNotRepaint);
| + | |
- | }
| + | |
- | },
| + | |
- | clearTypes : function(doNotRepaint) {
| + | |
- | var i = this._jsPlumb.types.length;
| + | |
- | for (var j = 0; j < i; j++) {
| + | |
- | _removeTypeCssHelper(this, 0);
| + | |
- | this._jsPlumb.types.splice(0, 1);
| + | |
- | }
| + | |
- | _applyTypes(this, {}, doNotRepaint);
| + | |
- | },
| + | |
- |
| + | |
- | toggleType : function(typeId, params, doNotRepaint) {
| + | |
- | var t = _splitType(typeId);
| + | |
- | if (t != null) {
| + | |
- | for (var i = 0, j = t.length; i < j; i++) {
| + | |
- | var idx = jsPlumbUtil.indexOf(this._jsPlumb.types, t[i]);
| + | |
- | if (idx != -1) {
| + | |
- | _removeTypeCssHelper(this, idx);
| + | |
- | this._jsPlumb.types.splice(idx, 1);
| + | |
- | }
| + | |
- | else
| + | |
- | this._jsPlumb.types.push(t[i]);
| + | |
- | }
| + | |
- |
| + | |
- | _applyTypes(this, params, doNotRepaint);
| + | |
- | }
| + | |
- | },
| + | |
- | applyType : function(t, doNotRepaint) {
| + | |
- | this.setPaintStyle(t.paintStyle, doNotRepaint);
| + | |
- | this.setHoverPaintStyle(t.hoverPaintStyle, doNotRepaint);
| + | |
- | if (t.parameters){
| + | |
- | for (var i in t.parameters)
| + | |
- | this.setParameter(i, t.parameters[i]);
| + | |
- | }
| + | |
- | },
| + | |
- | setPaintStyle : function(style, doNotRepaint) {
| + | |
- | // this._jsPlumb.paintStyle = jsPlumb.extend({}, style);
| + | |
- | // TODO figure out if we want components to clone paintStyle so as not to share it.
| + | |
- | this._jsPlumb.paintStyle = style;
| + | |
- | this._jsPlumb.paintStyleInUse = this._jsPlumb.paintStyle;
| + | |
- | _updateHoverStyle(this);
| + | |
- | if (!doNotRepaint) this.repaint();
| + | |
- | },
| + | |
- | getPaintStyle : function() {
| + | |
- | return this._jsPlumb.paintStyle;
| + | |
- | },
| + | |
- | setHoverPaintStyle : function(style, doNotRepaint) {
| + | |
- | //this._jsPlumb.hoverPaintStyle = jsPlumb.extend({}, style);
| + | |
- | // TODO figure out if we want components to clone paintStyle so as not to share it.
| + | |
- | this._jsPlumb.hoverPaintStyle = style;
| + | |
- | _updateHoverStyle(this);
| + | |
- | if (!doNotRepaint) this.repaint();
| + | |
- | },
| + | |
- | getHoverPaintStyle : function() {
| + | |
- | return this._jsPlumb.hoverPaintStyle;
| + | |
- | },
| + | |
- | cleanup:function() {
| + | |
- | this.unbindListeners();
| + | |
- | this.detachListeners();
| + | |
- | },
| + | |
- | destroy:function() {
| + | |
- | this.cleanupListeners();
| + | |
- | this.clone = null;
| + | |
- | this._jsPlumb = null;
| + | |
- | },
| + | |
- |
| + | |
- | isHover : function() { return this._jsPlumb.hover; },
| + | |
- |
| + | |
- | setHover : function(hover, ignoreAttachedElements, timestamp) {
| + | |
- | // while dragging, we ignore these events. this keeps the UI from flashing and
| + | |
- | // swishing and whatevering.
| + | |
- | if (this._jsPlumb && !this._jsPlumb.instance.currentlyDragging && !this._jsPlumb.instance.isHoverSuspended()) {
| + | |
- |
| + | |
- | this._jsPlumb.hover = hover;
| + | |
- |
| + | |
- | if (this.canvas != null) {
| + | |
- | if (this._jsPlumb.instance.hoverClass != null) {
| + | |
- | var method = hover ? "addClass" : "removeClass";
| + | |
- | this._jsPlumb.instance[method](this.canvas, this._jsPlumb.instance.hoverClass);
| + | |
- | }
| + | |
- | if (this._jsPlumb.hoverClass != null) {
| + | |
- | this._jsPlumb.instance[method](this.canvas, this._jsPlumb.hoverClass);
| + | |
- | }
| + | |
- | }
| + | |
- | if (this._jsPlumb.hoverPaintStyle != null) {
| + | |
- | this._jsPlumb.paintStyleInUse = hover ? this._jsPlumb.hoverPaintStyle : this._jsPlumb.paintStyle;
| + | |
- | if (!this._jsPlumb.instance.isSuspendDrawing()) {
| + | |
- | timestamp = timestamp || _timestamp();
| + | |
- | this.repaint({timestamp:timestamp, recalc:false});
| + | |
- | }
| + | |
- | }
| + | |
- | // get the list of other affected elements, if supported by this component.
| + | |
- | // for a connection, its the endpoints. for an endpoint, its the connections! surprise.
| + | |
- | if (this.getAttachedElements && !ignoreAttachedElements)
| + | |
- | _updateAttachedElements(this, hover, _timestamp(), this);
| + | |
- | }
| + | |
- | }
| + | |
- | });
| + | |
- | | + | |
- | // ------------------------------ END jsPlumbUIComponent --------------------------------------------
| + | |
- | | + | |
- | // ------------------------------ BEGIN OverlayCapablejsPlumbUIComponent --------------------------------------------
| + | |
- | | + | |
- | var _internalLabelOverlayId = "__label",
| + | |
- | // helper to get the index of some overlay
| + | |
- | _getOverlayIndex = function(component, id) {
| + | |
- | var idx = -1;
| + | |
- | for (var i = 0, j = component._jsPlumb.overlays.length; i < j; i++) {
| + | |
- | if (id === component._jsPlumb.overlays[i].id) {
| + | |
- | idx = i;
| + | |
- | break;
| + | |
- | }
| + | |
- | }
| + | |
- | return idx;
| + | |
- | },
| + | |
- | // this is a shortcut helper method to let people add a label as
| + | |
- | // overlay.
| + | |
- | _makeLabelOverlay = function(component, params) {
| + | |
- | | + | |
- | var _params = {
| + | |
- | cssClass:params.cssClass,
| + | |
- | labelStyle : component.labelStyle,
| + | |
- | id:_internalLabelOverlayId,
| + | |
- | component:component,
| + | |
- | _jsPlumb:component._jsPlumb.instance // TODO not necessary, since the instance can be accessed through the component.
| + | |
| }, | | }, |
- | mergedParams = jsPlumb.extend(_params, params); | + | addClass:jsPlumbAdapter.addClass, |
- | | + | removeClass:jsPlumbAdapter.removeClass, |
- | return new jsPlumb.Overlays[component._jsPlumb.instance.getRenderMode()].Label( mergedParams ); | + | intersects:Biltong.intersects, |
- | },
| + | indexOf:jsPlumbUtil.indexOf, |
- | _processOverlay = function(component, o) {
| + | css:{ |
- | var _newOverlay = null; | + | noSelect : instance.dragSelectClass, |
- | if (_ju.isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax
| + | droppable:"jsplumb-droppable", |
- | // there's also a three arg version:
| + | draggable:"jsplumb-draggable", |
- | // ["Arrow", { width:50 }, {location:0.7}]
| + | drag:"jsplumb-drag", |
- | // which merges the 3rd arg into the 2nd. | + | selected:"jsplumb-drag-selected", |
- | var type = o[0],
| + | active:"jsplumb-drag-active", |
- | // make a copy of the object so as not to mess up anyone else's reference...
| + | hover:"jsplumb-drag-hover" |
- | p = jsPlumb.extend({component:component, _jsPlumb:component._jsPlumb.instance}, o[1]);
| + | |
- | if (o.length == 3) jsPlumb.extend(p, o[2]);
| + | |
- | _newOverlay = new jsPlumb.Overlays[component._jsPlumb.instance.getRenderMode()][type](p); | + | |
- | } else if (o.constructor == String) {
| + | |
- | _newOverlay = new jsPlumb.Overlays[component._jsPlumb.instance.getRenderMode()][o]({component:component, _jsPlumb:component._jsPlumb.instance});
| + | |
- | } else {
| + | |
- | _newOverlay = o; | + | |
- | }
| + | |
- |
| + | |
- | component._jsPlumb.overlays.push(_newOverlay);
| + | |
- | },
| + | |
- | _calculateOverlaysToAdd = function(component, params) {
| + | |
- | var defaultKeys = component.defaultOverlayKeys || [], o = params.overlays,
| + | |
- | checkKey = function(k) { | + | |
- | return component._jsPlumb.instance.Defaults[k] || jsPlumb.Defaults[k] || [];
| + | |
- | };
| + | |
- |
| + | |
- | if (!o) o = [];
| + | |
- | | + | |
- | for (var i = 0, j = defaultKeys.length; i < j; i++)
| + | |
- | o.unshift.apply(o, checkKey(defaultKeys[i])); | + | |
- |
| + | |
- | return o;
| + | |
- | },
| + | |
- | OverlayCapableJsPlumbUIComponent = window.OverlayCapableJsPlumbUIComponent = function(params) {
| + | |
- | | + | |
- | jsPlumbUIComponent.apply(this, arguments);
| + | |
- | this._jsPlumb.overlays = [];
| + | |
- | | + | |
- | var _overlays = _calculateOverlaysToAdd(this, params);
| + | |
- | if (_overlays) {
| + | |
- | for (var i = 0, j = _overlays.length; i < j; i++) { | + | |
- | _processOverlay(this, _overlays[i]);
| + | |
- | } | + | |
| } | | } |
- |
| + | }); |
- | if (params.label) {
| + | instance[isPlumbedComponent ? "_internalKatavorio" : "_katavorio"] = k; |
- | var loc = params.labelLocation || this.defaultLabelLocation || 0.5,
| + | instance.bind("zoom", k.setZoom); |
- | labelStyle = params.labelStyle || this._jsPlumb.instance.Defaults.LabelStyle;
| + | } |
| + | return k; |
| + | }; |
| | | |
- | this._jsPlumb.overlays.push(_makeLabelOverlay(this, {
| + | var _getEventManager = function(instance) { |
- | label:params.label,
| + | var e = instance._mottle; |
- | location:loc,
| + | if (!e) { |
- | labelStyle:labelStyle
| + | e = instance._mottle = new Mottle(); |
- | }));
| + | } |
- | }
| + | return e; |
- | | + | }; |
- | this.setListenerComponent = function(c) {
| + | |
- | if (this._jsPlumb) {
| + | var _animProps = function(o, p) { |
- | for (var i = 0; i < this._jsPlumb.overlays.length; i++)
| + | var _one = function(pName) { |
- | this._jsPlumb.overlays[i].setListenerComponent(c);
| + | if (p[pName]) { |
- | }
| + | if (jsPlumbUtil.isString(p[pName])) { |
- | };
| + | var m = p[pName].match(/-=/) ? -1 : 1, |
- | };
| + | v = p[pName].substring(2); |
- | | + | return o[pName] + (m * v); |
- | jsPlumbUtil.extend(OverlayCapableJsPlumbUIComponent, jsPlumbUIComponent, {
| + | |
- | applyType : function(t, doNotRepaint) {
| + | |
- | this.removeAllOverlays(doNotRepaint);
| + | |
- | if (t.overlays) { | + | |
- | for (var i = 0, j = t.overlays.length; i < j; i++) | + | |
- | this.addOverlay(t.overlays[i], true); | + | |
| } | | } |
- | },
| + | else return p[pName]; |
- | setHover : function(hover, ignoreAttachedElements, timestamp) {
| + | |
- | if (this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) {
| + | |
- | for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++) {
| + | |
- | this._jsPlumb.overlays[i][hover ? "addClass":"removeClass"](this._jsPlumb.instance.hoverClass);
| + | |
- | }
| + | |
- | }
| + | |
- | },
| + | |
- | addOverlay : function(overlay, doNotRepaint) {
| + | |
- | _processOverlay(this, overlay);
| + | |
- | if (!doNotRepaint) this.repaint();
| + | |
- | },
| + | |
- | getOverlay : function(id) {
| + | |
- | var idx = _getOverlayIndex(this, id);
| + | |
- | return idx >= 0 ? this._jsPlumb.overlays[idx] : null;
| + | |
- | },
| + | |
- | getOverlays : function() {
| + | |
- | return this._jsPlumb.overlays;
| + | |
- | },
| + | |
- | hideOverlay : function(id) {
| + | |
- | var o = this.getOverlay(id);
| + | |
- | if (o) o.hide();
| + | |
- | },
| + | |
- | hideOverlays : function() {
| + | |
- | for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++)
| + | |
- | this._jsPlumb.overlays[i].hide();
| + | |
- | },
| + | |
- | showOverlay : function(id) {
| + | |
- | var o = this.getOverlay(id);
| + | |
- | if (o) o.show();
| + | |
- | },
| + | |
- | showOverlays : function() {
| + | |
- | for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++)
| + | |
- | this._jsPlumb.overlays[i].show();
| + | |
- | },
| + | |
- | removeAllOverlays : function(doNotRepaint) {
| + | |
- | for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++) {
| + | |
- | if (this._jsPlumb.overlays[i].cleanup) this._jsPlumb.overlays[i].cleanup();
| + | |
- | }
| + | |
- | | + | |
- | this._jsPlumb.overlays.splice(0, this._jsPlumb.overlays.length);
| + | |
- | this._jsPlumb.overlayPositions = null;
| + | |
- | if (!doNotRepaint)
| + | |
- | this.repaint();
| + | |
- | },
| + | |
- | removeOverlay : function(overlayId) {
| + | |
- | var idx = _getOverlayIndex(this, overlayId);
| + | |
- | if (idx != -1) {
| + | |
- | var o = this._jsPlumb.overlays[idx];
| + | |
- | if (o.cleanup) o.cleanup();
| + | |
- | this._jsPlumb.overlays.splice(idx, 1);
| + | |
- | if (this._jsPlumb.overlayPositions)
| + | |
- | delete this._jsPlumb.overlayPositions[overlayId];
| + | |
- | }
| + | |
- | },
| + | |
- | removeOverlays : function() {
| + | |
- | for (var i = 0, j = arguments.length; i < j; i++)
| + | |
- | this.removeOverlay(arguments[i]);
| + | |
- | },
| + | |
- | moveParent:function(newParent) {
| + | |
- | if (this.bgCanvas) {
| + | |
- | this.bgCanvas.parentNode.removeChild(this.bgCanvas);
| + | |
- | newParent.appendChild(this.bgCanvas);
| + | |
- | }
| + | |
- |
| + | |
- | this.canvas.parentNode.removeChild(this.canvas);
| + | |
- | newParent.appendChild(this.canvas);
| + | |
- | | + | |
- | for (var i = 0; i < this._jsPlumb.overlays.length; i++) {
| + | |
- | if (this._jsPlumb.overlays[i].isAppendedAtTopLevel) {
| + | |
- | this._jsPlumb.overlays[i].canvas.parentNode.removeChild(this._jsPlumb.overlays[i].canvas);
| + | |
- | newParent.appendChild(this._jsPlumb.overlays[i].canvas);
| + | |
- | }
| + | |
- | }
| + | |
- | },
| + | |
- | getLabel : function() {
| + | |
- | var lo = this.getOverlay(_internalLabelOverlayId);
| + | |
- | return lo != null ? lo.getLabel() : null;
| + | |
- | },
| + | |
- | getLabelOverlay : function() {
| + | |
- | return this.getOverlay(_internalLabelOverlayId);
| + | |
- | },
| + | |
- | setLabel : function(l) {
| + | |
- | var lo = this.getOverlay(_internalLabelOverlayId);
| + | |
- | if (!lo) {
| + | |
- | var params = l.constructor == String || l.constructor == Function ? { label:l } : l;
| + | |
- | lo = _makeLabelOverlay(this, params);
| + | |
- | this._jsPlumb.overlays.push(lo);
| + | |
- | }
| + | |
- | else { | + | |
- | if (l.constructor == String || l.constructor == Function) lo.setLabel(l);
| + | |
- | else {
| + | |
- | if (l.label) lo.setLabel(l.label);
| + | |
- | if (l.location) lo.setLocation(l.location);
| + | |
- | }
| + | |
- | }
| + | |
- |
| + | |
- | if (!this._jsPlumb.instance.isSuspendDrawing())
| + | |
- | this.repaint();
| + | |
- | },
| + | |
- | cleanup:function() {
| + | |
- | for (var i = 0; i < this._jsPlumb.overlays.length; i++) {
| + | |
- | this._jsPlumb.overlays[i].cleanup();
| + | |
- | this._jsPlumb.overlays[i].destroy();
| + | |
- | }
| + | |
- | this._jsPlumb.overlays.splice(0);
| + | |
- | this._jsPlumb.overlayPositions = null;
| + | |
- | },
| + | |
- | setVisible:function(v) {
| + | |
- | this[v ? "showOverlays" : "hideOverlays"]();
| + | |
- | },
| + | |
- | setAbsoluteOverlayPosition:function(overlay, xy) {
| + | |
- | this._jsPlumb.overlayPositions = this._jsPlumb.overlayPositions || {};
| + | |
- | this._jsPlumb.overlayPositions[overlay.id] = xy;
| + | |
- | },
| + | |
- | getAbsoluteOverlayPosition:function(overlay) {
| + | |
- | return this._jsPlumb.overlayPositions ? this._jsPlumb.overlayPositions[overlay.id] : null;
| + | |
| } | | } |
- | }); | + | else |
| + | return o[pName]; |
| + | }; |
| + | return [ _one("left"), _one("top") ]; |
| + | }; |
| | | |
- | // ------------------------------ END OverlayCapablejsPlumbUIComponent --------------------------------------------
| + | jsPlumb.extend(jsPlumbInstance.prototype, { |
- |
| + | |
- | var _jsPlumbInstanceIndex = 0,
| + | |
- | getInstanceIndex = function() {
| + | |
- | var i = _jsPlumbInstanceIndex + 1;
| + | |
- | _jsPlumbInstanceIndex++;
| + | |
- | return i;
| + | |
- | };
| + | |
- | | + | |
- | var jsPlumbInstance = window.jsPlumbInstance = function(_defaults) {
| + | |
- |
| + | |
- | this.Defaults = {
| + | |
- | Anchor : "BottomCenter",
| + | |
- | Anchors : [ null, null ],
| + | |
- | ConnectionsDetachable : true,
| + | |
- | ConnectionOverlays : [ ],
| + | |
- | Connector : "Bezier",
| + | |
- | Container : null,
| + | |
- | DoNotThrowErrors:false,
| + | |
- | DragOptions : { },
| + | |
- | DropOptions : { },
| + | |
- | Endpoint : "Dot",
| + | |
- | EndpointOverlays : [ ],
| + | |
- | Endpoints : [ null, null ],
| + | |
- | EndpointStyle : { fillStyle : "#456" },
| + | |
- | EndpointStyles : [ null, null ],
| + | |
- | EndpointHoverStyle : null,
| + | |
- | EndpointHoverStyles : [ null, null ],
| + | |
- | HoverPaintStyle : null,
| + | |
- | LabelStyle : { color : "black" },
| + | |
- | LogEnabled : false,
| + | |
- | Overlays : [ ],
| + | |
- | MaxConnections : 1,
| + | |
- | PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
| + | |
- | ReattachConnections:false,
| + | |
- | RenderMode : "svg",
| + | |
- | Scope : "jsPlumb_DefaultScope"
| + | |
- | };
| + | |
- | if (_defaults) jsPlumb.extend(this.Defaults, _defaults);
| + | |
- |
| + | |
- | this.logEnabled = this.Defaults.LogEnabled;
| + | |
- | this._connectionTypes = {};
| + | |
- | this._endpointTypes = {};
| + | |
- | | + | |
- | jsPlumbUtil.EventGenerator.apply(this);
| + | |
- | | + | |
- | var _currentInstance = this,
| + | |
- | _instanceIndex = getInstanceIndex(),
| + | |
- | _bb = _currentInstance.bind,
| + | |
- | _initialDefaults = {},
| + | |
- | _zoom = 1,
| + | |
- | _info = function(el) {
| + | |
- | var _el = _currentInstance.getDOMElement(el);
| + | |
- | return { el:_el, id:(jsPlumbUtil.isString(el) && _el == null) ? el : _getId(_el) };
| + | |
- | };
| + | |
- |
| + | |
- | this.getInstanceIndex = function() { return _instanceIndex; };
| + | |
- | | + | |
- | this.setZoom = function(z, repaintEverything) {
| + | |
- | if (!jsPlumbUtil.oldIE) {
| + | |
- | _zoom = z;
| + | |
- | _currentInstance.fire("zoom", _zoom);
| + | |
- | if (repaintEverything) _currentInstance.repaintEverything();
| + | |
- | }
| + | |
- | return !jsPlumbUtil.oldIE;
| + | |
- | | + | |
- | };
| + | |
- | this.getZoom = function() { return _zoom; };
| + | |
- |
| + | |
- | for (var i in this.Defaults)
| + | |
- | _initialDefaults[i] = this.Defaults[i];
| + | |
- | | + | |
- | var _container;
| + | |
- | this.setContainer = function(c) {
| + | |
- | c = this.getDOMElement(c);
| + | |
- | this.select().each(function(conn) {
| + | |
- | conn.moveParent(c);
| + | |
- | });
| + | |
- | this.selectEndpoints().each(function(ep) {
| + | |
- | ep.moveParent(c);
| + | |
- | });
| + | |
- | _container = c;
| + | |
- | };
| + | |
- | this.getContainer = function() {
| + | |
- | return _container;
| + | |
- | };
| + | |
- |
| + | |
- | this.bind = function(event, fn) {
| + | |
- | if ("ready" === event && initialized) fn();
| + | |
- | else _bb.apply(_currentInstance,[event, fn]);
| + | |
- | };
| + | |
- | | + | |
- | _currentInstance.importDefaults = function(d) {
| + | |
- | for (var i in d) {
| + | |
- | _currentInstance.Defaults[i] = d[i];
| + | |
- | }
| + | |
- | if (d.Container)
| + | |
- | this.setContainer(d.Container);
| + | |
- | | + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- |
| + | |
- | _currentInstance.restoreDefaults = function() {
| + | |
- | _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults);
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- |
| + | |
- | var log = null,
| + | |
- | resizeTimer = null,
| + | |
- | initialized = false,
| + | |
- | // TODO remove from window scope
| + | |
- | connections = [],
| + | |
- | // map of element id -> endpoint lists. an element can have an arbitrary
| + | |
- | // number of endpoints on it, and not all of them have to be connected
| + | |
- | // to anything.
| + | |
- | endpointsByElement = {},
| + | |
- | endpointsByUUID = {},
| + | |
- | offsets = {},
| + | |
- | offsetTimestamps = {},
| + | |
- | floatingConnections = {},
| + | |
- | draggableStates = {},
| + | |
- | connectionBeingDragged = false,
| + | |
- | sizes = [],
| + | |
- | _suspendDrawing = false,
| + | |
- | _suspendedAt = null,
| + | |
- | DEFAULT_SCOPE = this.Defaults.Scope,
| + | |
- | renderMode = null, // will be set in init()
| + | |
- | _curIdStamp = 1,
| + | |
- | _idstamp = function() { return "" + _curIdStamp++; },
| + | |
- |
| + | |
- | //
| + | |
- | // appends an element to some other element, which is calculated as follows:
| + | |
- | //
| + | |
- | // 1. if Container exists, use that element.
| + | |
- | // 2. if the 'parent' parameter exists, use that.
| + | |
- | // 3. otherwise just use the root element (for DOM usage, the document body).
| + | |
- | //
| + | |
- | //
| + | |
- | _appendElement = function(el, parent) {
| + | |
- | if (_container)
| + | |
- | _container.appendChild(el);
| + | |
- | else if (!parent)
| + | |
- | _currentInstance.appendToRoot(el);
| + | |
- | else
| + | |
- | jsPlumb.getDOMElement(parent).appendChild(el);
| + | |
- | },
| + | |
- |
| + | |
- | //
| + | |
- | // YUI, for some reason, put the result of a Y.all call into an object that contains
| + | |
- | // a '_nodes' array, instead of handing back an array-like object like the other
| + | |
- | // libraries do.
| + | |
- | //
| + | |
- | _convertYUICollection = function(c) {
| + | |
- | return c._nodes ? c._nodes : c;
| + | |
- | },
| + | |
- | | + | |
- | //
| + | |
- | // Draws an endpoint and its connections. this is the main entry point into drawing connections as well
| + | |
- | // as endpoints, since jsPlumb is endpoint-centric under the hood.
| + | |
- | //
| + | |
- | // @param element element to draw (of type library specific element object)
| + | |
- | // @param ui UI object from current library's event system. optional.
| + | |
- | // @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do.
| + | |
- | // @param clearEdits defaults to false; indicates that mouse edits for connectors should be cleared
| + | |
- | ///
| + | |
- | _draw = function(element, ui, timestamp, clearEdits) {
| + | |
- | | + | |
- | // TODO is it correct to filter by headless at this top level? how would a headless adapter ever repaint?
| + | |
- | if (!jsPlumbAdapter.headless && !_suspendDrawing) {
| + | |
- | var id = _getId(element),
| + | |
- | repaintEls = _currentInstance.dragManager.getElementsForDraggable(id);
| + | |
- | | + | |
- | if (timestamp == null) timestamp = _timestamp();
| + | |
- | | + | |
- | // update the offset of everything _before_ we try to draw anything.
| + | |
- | var o = _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp });
| + | |
- | | + | |
- | if (repaintEls) {
| + | |
- | for (var i in repaintEls) {
| + | |
- | // TODO this seems to cause a lag, but we provide the offset, so in theory it
| + | |
- | // should not. is the timestamp failing?
| + | |
- | _updateOffset( {
| + | |
- | elId : repaintEls[i].id,
| + | |
- | offset : {
| + | |
- | left:o.o.left + repaintEls[i].offset.left,
| + | |
- | top:o.o.top + repaintEls[i].offset.top
| + | |
- | },
| + | |
- | recalc : false,
| + | |
- | timestamp : timestamp
| + | |
- | });
| + | |
- | }
| + | |
- | }
| + | |
- |
| + | |
- | | + | |
- | _currentInstance.anchorManager.redraw(id, ui, timestamp, null, clearEdits);
| + | |
- |
| + | |
- | if (repaintEls) {
| + | |
- | for (var j in repaintEls) {
| + | |
- | _currentInstance.anchorManager.redraw(repaintEls[j].id, ui, timestamp, repaintEls[j].offset, clearEdits, true);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | },
| + | |
- | | + | |
- | //
| + | |
- | // executes the given function against the given element if the first
| + | |
- | // argument is an object, or the list of elements, if the first argument
| + | |
- | // is a list. the function passed in takes (element, elementId) as
| + | |
- | // arguments.
| + | |
- | //
| + | |
- | _elementProxy = function(element, fn) {
| + | |
- | var retVal = null, el, id, del;
| + | |
- | if (_ju.isArray(element)) {
| + | |
- | retVal = [];
| + | |
- | for ( var i = 0, j = element.length; i < j; i++) {
| + | |
- | el = _currentInstance.getElementObject(element[i]);
| + | |
- | del = _currentInstance.getDOMElement(el);
| + | |
- | id = _currentInstance.getAttribute(del, "id");
| + | |
- | //retVal.push(fn(el, id)); // append return values to what we will return
| + | |
- | retVal.push(fn.apply(_currentInstance, [del, id])); // append return values to what we will return
| + | |
- | }
| + | |
- | } else {
| + | |
- | el = _currentInstance.getDOMElement(element);
| + | |
- | id = _currentInstance.getId(el);
| + | |
- | retVal = fn.apply(_currentInstance, [el, id]);
| + | |
- | }
| + | |
- | return retVal;
| + | |
- | },
| + | |
- | | + | |
- | //
| + | |
- | // gets an Endpoint by uuid.
| + | |
- | //
| + | |
- | _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; },
| + | |
- | | + | |
- | /**
| + | |
- | * inits a draggable if it's not already initialised.
| + | |
- | * TODO: somehow abstract this to the adapter, because the concept of "draggable" has no
| + | |
- | * place on the server.
| + | |
- | */
| + | |
- | _initDraggableIfNecessary = function(element, isDraggable, dragOptions) {
| + | |
- | // TODO move to DragManager?
| + | |
- | if (!jsPlumbAdapter.headless) {
| + | |
- | var _draggable = isDraggable == null ? false : isDraggable;
| + | |
- | if (_draggable) {
| + | |
- | if (jsPlumb.isDragSupported(element, _currentInstance) && !jsPlumb.isAlreadyDraggable(element, _currentInstance)) {
| + | |
- | var options = dragOptions || _currentInstance.Defaults.DragOptions;
| + | |
- | options = jsPlumb.extend( {}, options); // make a copy.
| + | |
- | var dragEvent = jsPlumb.dragEvents.drag,
| + | |
- | stopEvent = jsPlumb.dragEvents.stop,
| + | |
- | startEvent = jsPlumb.dragEvents.start,
| + | |
- | ancestorOffset = null,
| + | |
- | _del = _currentInstance.getDOMElement(element),
| + | |
- | _ancestor = _currentInstance.dragManager.getDragAncestor(_del),
| + | |
- | _noOffset = {left:0, top:0},
| + | |
- | _ancestorOffset = _noOffset,
| + | |
- | _started = false;
| + | |
| | | |
- | options[startEvent] = _ju.wrap(options[startEvent], function() {
| + | getDOMElement:function(el) { |
- | _ancestorOffset = _ancestor != null ? jsPlumbAdapter.getOffset(_ancestor, _currentInstance) : _noOffset;
| + | if (el == null) return null; |
- | _currentInstance.setHoverSuspended(true);
| + | // here we pluck the first entry if el was a list of entries. |
- | _currentInstance.select({source:element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
| + | // this is not my favourite thing to do, but previous versions of |
- | _currentInstance.select({target:element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
| + | // jsplumb supported jquery selectors, and it is possible a selector |
- | _currentInstance.setConnectionBeingDragged(true);
| + | // will be passed in here. |
- | if (options.canDrag) return dragOptions.canDrag();
| + | el = typeof el === "string" ? el : el.length != null ? el[0] : el; |
- | }, false);
| + | return typeof el === "string" ? document.getElementById(el) : el; |
- |
| + | |
- | options[dragEvent] = _ju.wrap(options[dragEvent], function() {
| + | |
- | // TODO: here we could actually use getDragObject, and then compute it ourselves,
| + | |
- | // since every adapter does the same thing. but i'm not sure why YUI's getDragObject
| + | |
- | // differs from getUIPosition so much
| + | |
- | var ui = _currentInstance.getUIPosition(arguments, _currentInstance.getZoom());
| + | |
- | // adjust by ancestor offset if there is one: this is for the case that a draggable
| + | |
- | // is contained inside some other element that is not the Container.
| + | |
- | ui.left += _ancestorOffset.left;
| + | |
- | ui.top += _ancestorOffset.top;
| + | |
- | _draw(element, ui, null, true);
| + | |
- | if (_started) _currentInstance.addClass(element, "jsPlumb_dragged");
| + | |
- | _started = true;
| + | |
- | });
| + | |
- | options[stopEvent] = _ju.wrap(options[stopEvent], function() {
| + | |
- | var ui = _currentInstance.getUIPosition(arguments, _currentInstance.getZoom(), true);
| + | |
- | _draw(element, ui);
| + | |
- | _started = false;
| + | |
- | _currentInstance.removeClass(element, "jsPlumb_dragged");
| + | |
- | _currentInstance.setHoverSuspended(false);
| + | |
- | _currentInstance.select({source:element}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
| + | |
- | _currentInstance.select({target:element}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
| + | |
- | _currentInstance.setConnectionBeingDragged(false);
| + | |
- | _currentInstance.dragManager.dragEnded(element);
| + | |
- | });
| + | |
- | var elId = _getId(element); // need ID
| + | |
- | draggableStates[elId] = true;
| + | |
- | var draggable = draggableStates[elId];
| + | |
- | options.disabled = draggable == null ? false : !draggable;
| + | |
- | _currentInstance.initDraggable(element, options, false);
| + | |
- | _currentInstance.dragManager.register(element);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
| }, | | }, |
- | | + | getElementObject:function(el) { return el; }, |
- | /*
| + | removeElement : function(element) { |
- | * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc.
| + | _getDragManager(this).elementRemoved(element); |
- | */
| + | _getEventManager(this).remove(element); |
- | _prepareConnectionParams = function(params, referenceParams) {
| + | |
- | var _p = jsPlumb.extend( { }, params);
| + | |
- | if (referenceParams) jsPlumb.extend(_p, referenceParams);
| + | |
- |
| + | |
- | // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively.
| + | |
- | if (_p.source) {
| + | |
- | if (_p.source.endpoint)
| + | |
- | _p.sourceEndpoint = _p.source;
| + | |
- | else
| + | |
- | _p.source = _currentInstance.getDOMElement(_p.source);
| + | |
- | }
| + | |
- | if (_p.target) {
| + | |
- | if (_p.target.endpoint)
| + | |
- | _p.targetEndpoint = _p.target;
| + | |
- | else
| + | |
- | _p.target = _currentInstance.getDOMElement(_p.target);
| + | |
- | }
| + | |
- |
| + | |
- | // test for endpoint uuids to connect
| + | |
- | if (params.uuids) {
| + | |
- | _p.sourceEndpoint = _getEndpoint(params.uuids[0]);
| + | |
- | _p.targetEndpoint = _getEndpoint(params.uuids[1]);
| + | |
- | }
| + | |
- | | + | |
- | // now ensure that if we do have Endpoints already, they're not full.
| + | |
- | // source:
| + | |
- | if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) {
| + | |
- | _ju.log(_currentInstance, "could not add connection; source endpoint is full");
| + | |
- | return;
| + | |
- | }
| + | |
- | | + | |
- | // target:
| + | |
- | if (_p.targetEndpoint && _p.targetEndpoint.isFull()) {
| + | |
- | _ju.log(_currentInstance, "could not add connection; target endpoint is full");
| + | |
- | return;
| + | |
- | }
| + | |
- |
| + | |
- | // if source endpoint mandates connection type and nothing specified in our params, use it.
| + | |
- | if (!_p.type && _p.sourceEndpoint)
| + | |
- | _p.type = _p.sourceEndpoint.connectionType;
| + | |
- |
| + | |
- | // copy in any connectorOverlays that were specified on the source endpoint.
| + | |
- | // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not.
| + | |
- | if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) {
| + | |
- | _p.overlays = _p.overlays || [];
| + | |
- | for (var i = 0, j = _p.sourceEndpoint.connectorOverlays.length; i < j; i++) {
| + | |
- | _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]);
| + | |
- | }
| + | |
- | }
| + | |
- |
| + | |
- | // pointer events
| + | |
- | if (!_p["pointer-events"] && _p.sourceEndpoint && _p.sourceEndpoint.connectorPointerEvents)
| + | |
- | _p["pointer-events"] = _p.sourceEndpoint.connectorPointerEvents;
| + | |
- | | + | |
- | var _mergeOverrides = function(def, values) {
| + | |
- | var m = jsPlumb.extend({}, def);
| + | |
- | for (var i in values) {
| + | |
- | if (values[i]) m[i] = values[i];
| + | |
- | }
| + | |
- | return m;
| + | |
- | };
| + | |
- | | + | |
- | var _addEndpoint = function(el, def, idx) {
| + | |
- | return _currentInstance.addEndpoint(el, _mergeOverrides(tep.def, {
| + | |
- | anchor:_p.anchors ? _p.anchors[idx] : _p.anchor,
| + | |
- | endpoint:_p.endpoints ? _p.endpoints[idx] : _p.endpoint,
| + | |
- | paintStyle:_p.endpointStyles ? _p.endpointStyles[idx] : _p.endpointStyle,
| + | |
- | hoverPaintStyle:_p.endpointHoverStyles ? _p.endpointHoverStyles[idx] : _p.endpointHoverStyle
| + | |
- | }));
| + | |
- | };
| + | |
- |
| + | |
- | // if there's a target specified (which of course there should be), and there is no
| + | |
- | // target endpoint specified, and 'newConnection' was not set to true, then we check to
| + | |
- | // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and
| + | |
- | // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set
| + | |
- | // to true, then if that target endpoint has already been created, we re-use it.
| + | |
- | | + | |
- | var tid, tep, existingUniqueEndpoint, newEndpoint;
| + | |
- | | + | |
- | // TODO: this code can be refactored to be a little dry.
| + | |
- | if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) {
| + | |
- | tid = _getId(_p.target);
| + | |
- | tep = this.targetEndpointDefinitions[tid];
| + | |
- | | + | |
- | if (tep) {
| + | |
- |
| + | |
- | // if target not enabled, return.
| + | |
- | if (!tep.enabled) return;
| + | |
- | | + | |
- | // TODO this is dubious. i think it is there so that the endpoint can subsequently
| + | |
- | // be dragged (ie it kicks off the draggable registration). but it is dubious.
| + | |
- | tep.isTarget = true;
| + | |
- | | + | |
- | // check for max connections??
| + | |
- | newEndpoint = tep.endpoint != null && tep.endpoint._jsPlumb ? tep.endpoint : _addEndpoint(_p.target, tep.def, 1);
| + | |
- | if (tep.uniqueEndpoint) tep.endpoint = newEndpoint;
| + | |
- | _p.targetEndpoint = newEndpoint;
| + | |
- | // TODO test options to makeTarget to see if we should do this?
| + | |
- | newEndpoint._doNotDeleteOnDetach = false; // reset.
| + | |
- | newEndpoint._deleteOnDetach = true;
| + | |
- | }
| + | |
- | } | + | |
- | | + | |
- | // same thing, but for source.
| + | |
- | if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) {
| + | |
- | tid = _getId(_p.source);
| + | |
- | tep = this.sourceEndpointDefinitions[tid];
| + | |
- | | + | |
- | if (tep) {
| + | |
- | // if source not enabled, return.
| + | |
- | if (!tep.enabled) return;
| + | |
- |
| + | |
- | newEndpoint = tep.endpoint != null && tep.endpoint._jsPlumb ? tep.endpoint : _addEndpoint(_p.source, tep.def, 0);
| + | |
- | if (tep.uniqueEndpoint) tep.endpoint = newEndpoint;
| + | |
- | _p.sourceEndpoint = newEndpoint;
| + | |
- | // TODO test options to makeSource to see if we should do this?
| + | |
- | newEndpoint._doNotDeleteOnDetach = false; // reset.
| + | |
- | newEndpoint._deleteOnDetach = true;
| + | |
- | }
| + | |
- | }
| + | |
- |
| + | |
- | return _p;
| + | |
- | }.bind(_currentInstance),
| + | |
- |
| + | |
- | _newConnection = function(params) {
| + | |
- | var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
| + | |
- | endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint;
| + | |
- |
| + | |
- | params._jsPlumb = _currentInstance;
| + | |
- | params.newConnection = _newConnection;
| + | |
- | params.newEndpoint = _newEndpoint;
| + | |
- | params.endpointsByUUID = endpointsByUUID;
| + | |
- | params.endpointsByElement = endpointsByElement;
| + | |
- | params.finaliseConnection = _finaliseConnection;
| + | |
- | var con = new connectionFunc(params);
| + | |
- | con.id = "con_" + _idstamp();
| + | |
- | _eventFireProxy("click", "click", con);
| + | |
- | _eventFireProxy("dblclick", "dblclick", con);
| + | |
- | _eventFireProxy("contextmenu", "contextmenu", con);
| + | |
- | | + | |
- | // if the connection is draggable, then maybe we need to tell the target endpoint to init the
| + | |
- | // dragging code. it won't run again if it already configured to be draggable.
| + | |
- | if (con.isDetachable()) {
| + | |
- | con.endpoints[0].initDraggable();
| + | |
- | con.endpoints[1].initDraggable();
| + | |
- | }
| + | |
- | | + | |
- | return con;
| + | |
| }, | | }, |
- |
| |
| // | | // |
- | // adds the connection to the backing model, fires an event if necessary and then redraws | + | // this adapter supports a rudimentary animation function. no easing is supported. only |
| + | // left/top properties are supported. property delta args are expected to be in the form |
| + | // |
| + | // +=x.xxxx |
| // | | // |
- | _finaliseConnection = function(jpc, params, originalEvent, doInformAnchorManager) { | + | // or |
- | params = params || {};
| + | // |
- | // add to list of connections (by scope). | + | // -=x.xxxx |
- | if (!jpc.suspendedEndpoint)
| + | // |
- | connections.push(jpc);
| + | doAnimate:function(el, properties, options) { |
- | | + | options = options || {}; |
- | // turn off isTemporarySource on the source endpoint (only viable on first draw)
| + | var o = jsPlumbAdapter.getOffset(el, this), |
- | jpc.endpoints[0].isTemporarySource = false;
| + | ap = _animProps(o, properties), |
- |
| + | ldist = ap[0] - o.left, |
- | // always inform the anchor manager
| + | tdist = ap[1] - o.top, |
- | // except that if jpc has a suspended endpoint it's not true to say the
| + | d = options.duration || 250, |
- | // connection is new; it has just (possibly) moved. the question is whether
| + | step = 15, steps = d / step, |
- | // to make that call here or in the anchor manager. i think perhaps here.
| + | linc = (step / d) * ldist, |
- | if (jpc.suspendedEndpoint == null || doInformAnchorManager)
| + | tinc = (step / d) * tdist, |
- | _currentInstance.anchorManager.newConnection(jpc);
| + | idx = 0, |
- | | + | int = setInterval(function() { |
- | // force a paint
| + | jsPlumbAdapter.setPosition(el, { |
- | _draw(jpc.source);
| + | left:o.left + (linc * (idx + 1)), |
- |
| + | top:o.top + (tinc * (idx + 1)) |
- | // fire an event
| + | }); |
- | if (!params.doNotFireConnectionEvent && params.fireEvent !== false) {
| + | if (options.step != null) options.step(); |
- |
| + | idx++; |
- | var eventArgs = { | + | if (idx >= steps) { |
- | connection:jpc,
| + | window.clearInterval(int); |
- | source : jpc.source, target : jpc.target,
| + | if (options.complete != null) options.complete(); |
- | sourceId : jpc.sourceId, targetId : jpc.targetId,
| + | } |
- | sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
| + | }, step); |
- | };
| + | }, |
- | | + | getSelector:function(ctx, spec) { |
- | _currentInstance.fire("connection", eventArgs, originalEvent); | + | var sel = null; |
| + | if (arguments.length == 1) { |
| + | sel = ctx.nodeType != null ? ctx : document.querySelectorAll(ctx); |
| } | | } |
| + | else |
| + | sel = ctx.querySelectorAll(spec); |
| + | |
| + | return sel; |
| }, | | }, |
- | | + | // DRAG/DROP |
- | _eventFireProxy = function(event, proxyEvent, obj) { | + | destroyDraggable:function(el) { |
- | obj.bind(event, function(originalObject, originalEvent) { | + | _getDragManager(this).destroyDraggable(el); |
- | _currentInstance.fire(proxyEvent, obj, originalEvent);
| + | |
- | });
| + | |
| }, | | }, |
- | | + | destroyDroppable:function(el) { |
- |
| + | _getDragManager(this).destroyDroppable(el); |
- | /*
| + | |
- | factory method to prepare a new endpoint. this should always be used instead of creating Endpoints
| + | |
- | manually, since this method attaches event listeners and an id.
| + | |
- | */
| + | |
- | _newEndpoint = function(params) {
| + | |
- | var endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint;
| + | |
- | var _p = jsPlumb.extend({}, params);
| + | |
- | _p._jsPlumb = _currentInstance;
| + | |
- | _p.newConnection = _newConnection;
| + | |
- | _p.newEndpoint = _newEndpoint;
| + | |
- | _p.endpointsByUUID = endpointsByUUID;
| + | |
- | _p.endpointsByElement = endpointsByElement;
| + | |
- | _p.finaliseConnection = _finaliseConnection;
| + | |
- | _p.fireDetachEvent = fireDetachEvent;
| + | |
- | _p.fireMoveEvent = fireMoveEvent;
| + | |
- | _p.floatingConnections = floatingConnections;
| + | |
- | _p.elementId = _getId(_p.source);
| + | |
- | var ep = new endpointFunc(_p);
| + | |
- | ep.id = "ep_" + _idstamp();
| + | |
- | _eventFireProxy("click", "endpointClick", ep);
| + | |
- | _eventFireProxy("dblclick", "endpointDblClick", ep);
| + | |
- | _eventFireProxy("contextmenu", "contextmenu", ep);
| + | |
- | if (!jsPlumbAdapter.headless)
| + | |
- | _currentInstance.dragManager.endpointAdded(_p.source);
| + | |
- | return ep;
| + | |
| }, | | }, |
- | | + | initDraggable : function(el, options, isPlumbedComponent) { |
- | /*
| + | _getDragManager(this, isPlumbedComponent).draggable(el, options); |
- | * performs the given function operation on all the connections found
| + | |
- | * for the given element id; this means we find all the endpoints for
| + | |
- | * the given element, and then for each endpoint find the connectors
| + | |
- | * connected to it. then we pass each connection in to the given
| + | |
- | * function.
| + | |
- | */
| + | |
- | _operation = function(elId, func, endpointFunc) {
| + | |
- | var endpoints = endpointsByElement[elId]; | + | |
- | if (endpoints && endpoints.length) {
| + | |
- | for ( var i = 0, ii = endpoints.length; i < ii; i++) {
| + | |
- | for ( var j = 0, jj = endpoints[i].connections.length; j < jj; j++) {
| + | |
- | var retVal = func(endpoints[i].connections[j]);
| + | |
- | // if the function passed in returns true, we exit.
| + | |
- | // most functions return false.
| + | |
- | if (retVal) return;
| + | |
- | }
| + | |
- | if (endpointFunc) endpointFunc(endpoints[i]);
| + | |
- | }
| + | |
- | }
| + | |
- | },
| + | |
- |
| + | |
- | _setDraggable = function(element, draggable) {
| + | |
- | return _elementProxy(element, function(el, id) {
| + | |
- | draggableStates[id] = draggable;
| + | |
- | if (this.isDragSupported(el)) {
| + | |
- | this.setElementDraggable(el, draggable);
| + | |
- | }
| + | |
- | });
| + | |
| }, | | }, |
- | /* | + | initDroppable : function(el, options, isPlumbedComponent) { |
- | * private method to do the business of hiding/showing.
| + | _getDragManager(this, isPlumbedComponent).droppable(el, options); |
- | *
| + | |
- | * @param el
| + | |
- | * either Id of the element in question or a library specific
| + | |
- | * object for the element.
| + | |
- | * @param state
| + | |
- | * String specifying a value for the css 'display' property
| + | |
- | * ('block' or 'none').
| + | |
- | */
| + | |
- | _setVisible = function(el, state, alsoChangeEndpoints) {
| + | |
- | state = state === "block"; | + | |
- | var endpointFunc = null;
| + | |
- | if (alsoChangeEndpoints) {
| + | |
- | if (state) endpointFunc = function(ep) {
| + | |
- | ep.setVisible(true, true, true);
| + | |
- | };
| + | |
- | else endpointFunc = function(ep) {
| + | |
- | ep.setVisible(false, true, true);
| + | |
- | };
| + | |
- | }
| + | |
- | var info = _info(el);
| + | |
- | _operation(info.id, function(jpc) {
| + | |
- | if (state && alsoChangeEndpoints) {
| + | |
- | // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility.
| + | |
- | // this block will only set a connection to be visible if the other endpoint in the connection is also visible.
| + | |
- | var oidx = jpc.sourceId === info.id ? 1 : 0;
| + | |
- | if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true);
| + | |
- | }
| + | |
- | else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever.
| + | |
- | jpc.setVisible(state);
| + | |
- | }, endpointFunc);
| + | |
| }, | | }, |
- | /* | + | isAlreadyDraggable : function(el) { return el._katavorioDrag != null; }, |
- | * toggles the draggable state of the given element(s).
| + | isDragSupported : function(el, options) { return true; }, |
- | * el is either an id, or an element object, or a list of ids/element objects.
| + | isDropSupported : function(el, options) { return true; }, |
- | */
| + | getDragObject : function(eventArgs) { return eventArgs[0].drag.getDragElement(); }, |
- | _toggleDraggable = function(el) { | + | getDragScope : function(el) { |
- | return _elementProxy(el, function(el, elId) {
| + | return el._katavorioDrag && el._katavorioDrag.scopes.join(" ") || ""; |
- | var state = draggableStates[elId] == null ? false : draggableStates[elId];
| + | |
- | state = !state;
| + | |
- | draggableStates[elId] = state;
| + | |
- | this.setDraggable(el, state);
| + | |
- | return state;
| + | |
- | }); | + | |
| }, | | }, |
- | /** | + | getDropEvent : function(args) { return args[0].e; }, |
- | * private method to do the business of toggling hiding/showing.
| + | getDropScope : function(el) { |
- | */
| + | return el._katavorioDrop && el._katavorioDrop.scopes.join(" ") || ""; |
- | _toggleVisible = function(elId, changeEndpoints) {
| + | |
- | var endpointFunc = null;
| + | |
- | if (changeEndpoints) {
| + | |
- | endpointFunc = function(ep) {
| + | |
- | var state = ep.isVisible();
| + | |
- | ep.setVisible(!state);
| + | |
- | };
| + | |
- | }
| + | |
- | _operation(elId, function(jpc) {
| + | |
- | var state = jpc.isVisible();
| + | |
- | jpc.setVisible(!state);
| + | |
- | }, endpointFunc);
| + | |
- | // todo this should call _elementProxy, and pass in the
| + | |
- | // _operation(elId, f) call as a function. cos _toggleDraggable does
| + | |
- | // that.
| + | |
| }, | | }, |
- | /** | + | getUIPosition : function(eventArgs, zoom) { |
- | * updates the offset and size for a given element, and stores the
| + | return { |
- | * values. if 'offset' is not null we use that (it would have been
| + | left:eventArgs[0].pos[0], |
- | * passed in from a drag call) because it's faster; but if it is null,
| + | top:eventArgs[0].pos[1] |
- | * or if 'recalc' is true in order to force a recalculation, we get the current values.
| + | }; |
- | */
| + | }, |
- | _updateOffset = this.updateOffset = function(params) {
| + | isDragFilterSupported:function() { return true; }, |
- | var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId, s; | + | setDragFilter : function(el, filter) { |
- | if (_suspendDrawing && !timestamp) timestamp = _suspendedAt;
| + | if (el._katavorioDrag) { |
- | if (!recalc) {
| + | el._katavorioDrag.setFilter(filter); |
- | if (timestamp && timestamp === offsetTimestamps[elId]) { | + | |
- | return {o:params.offset || offsets[elId], s:sizes[elId]};
| + | |
- | } | + | |
- | }
| + | |
- | if (recalc || !offset) { // if forced repaint or no offset available, we recalculate.
| + | |
- | // get the current size and offset, and store them
| + | |
- | s = document.getElementById(elId);
| + | |
- | if (s != null) {
| + | |
- | sizes[elId] = _currentInstance.getSize(s);
| + | |
- | offsets[elId] = _getOffset(s, _currentInstance);
| + | |
- | offsetTimestamps[elId] = timestamp;
| + | |
- | }
| + | |
- | } else {
| + | |
- | offsets[elId] = offset;
| + | |
- | if (sizes[elId] == null) {
| + | |
- | s = document.getElementById(elId);
| + | |
- | if (s != null) sizes[elId] = _currentInstance.getSize(s);
| + | |
- | }
| + | |
- | offsetTimestamps[elId] = timestamp;
| + | |
- | }
| + | |
- |
| + | |
- | if(offsets[elId] && !offsets[elId].right) { | + | |
- | offsets[elId].right = offsets[elId].left + sizes[elId][0]; | + | |
- | offsets[elId].bottom = offsets[elId].top + sizes[elId][1];
| + | |
- | offsets[elId].width = sizes[elId][0];
| + | |
- | offsets[elId].height = sizes[elId][1];
| + | |
- | offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2);
| + | |
- | offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2);
| + | |
| } | | } |
- | return {o:offsets[elId], s:sizes[elId]};
| |
| }, | | }, |
- | | + | setElementDraggable : function(el, draggable) { |
- | // TODO comparison performance | + | el = jsPlumb.getDOMElement(el); |
- | _getCachedData = function(elId) {
| + | if (el._katavorioDrag) |
- | var o = offsets[elId]; | + | el._katavorioDrag.setEnabled(draggable); |
- | if (!o) | + | |
- | return _updateOffset({elId:elId});
| + | |
- | else
| + | |
- | return {o:o, s:sizes[elId]};
| + | |
| }, | | }, |
- | | + | setDragScope : function(el, scope) { |
- | /** | + | if (el._katavorioDrag) |
- | * gets an id for the given element, creating and setting one if
| + | el._katavorioDrag.k.setDragScope(el, scope); |
- | * necessary. the id is of the form
| + | }, |
- | *
| + | dragEvents : { |
- | * jsPlumb_<instance index>_<index in instance>
| + | 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', |
- | *
| + | 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' |
- | * where "index in instance" is a monotonically increasing integer that starts at 0,
| + | }, |
- | * for each instance. this method is used not only to assign ids to elements that do not
| + | animEvents:{ |
- | * have them but also to connections and endpoints.
| + | 'step':"step", 'complete':'complete' |
- | */
| + | }, |
- | _getId = function(element, uuid, doNotCreateIfNotFound) {
| + | stopDrag : function(el) { |
- | if (jsPlumbUtil.isString(element)) return element;
| + | if (el._katavorioDrag) |
- | if (element == null) return null;
| + | el._katavorioDrag.abort(); |
- | var id = _currentInstance.getAttribute(element, "id");
| + | }, |
- | if (!id || id === "undefined") {
| + | // MULTIPLE ELEMENT DRAG |
- | // check if fixed uuid parameter is given
| + | // these methods are unique to this adapter, because katavorio |
- | if (arguments.length == 2 && arguments[1] !== undefined)
| + | // supports dragging multiple elements. |
- | id = uuid;
| + | addToDragSelection:function(spec) { |
- | else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2]))
| + | _getDragManager(this).select(spec); |
- | id = "jsPlumb_" + _instanceIndex + "_" + _idstamp();
| + | |
- |
| + | |
- | if (!doNotCreateIfNotFound) _currentInstance.setAttribute(element, "id", id);
| + | |
- | }
| + | |
- | return id;
| + | |
- | };
| + | |
- | | + | |
- | this.setConnectionBeingDragged = function(v) {
| + | |
- | connectionBeingDragged = v;
| + | |
- | };
| + | |
- | this.isConnectionBeingDragged = function() {
| + | |
- | return connectionBeingDragged;
| + | |
- | };
| + | |
- |
| + | |
- | this.connectorClass = "_jsPlumb_connector";
| + | |
- | this.hoverClass = "_jsPlumb_hover";
| + | |
- | this.endpointClass = "_jsPlumb_endpoint";
| + | |
- | this.endpointConnectedClass = "_jsPlumb_endpoint_connected";
| + | |
- | this.endpointFullClass = "_jsPlumb_endpoint_full";
| + | |
- | this.endpointDropAllowedClass = "_jsPlumb_endpoint_drop_allowed";
| + | |
- | this.endpointDropForbiddenClass = "_jsPlumb_endpoint_drop_forbidden";
| + | |
- | this.overlayClass = "_jsPlumb_overlay";
| + | |
- | this.draggingClass = "_jsPlumb_dragging";
| + | |
- | this.elementDraggingClass = "_jsPlumb_element_dragging";
| + | |
- | this.sourceElementDraggingClass = "_jsPlumb_source_element_dragging";
| + | |
- | this.targetElementDraggingClass = "_jsPlumb_target_element_dragging";
| + | |
- | this.endpointAnchorClassPrefix = "_jsPlumb_endpoint_anchor";
| + | |
- | this.hoverSourceClass = "_jsPlumb_source_hover";
| + | |
- | this.hoverTargetClass = "_jsPlumb_target_hover";
| + | |
- | this.dragSelectClass = "_jsPlumb_drag_select";
| + | |
- | | + | |
- | this.Anchors = {};
| + | |
- | this.Connectors = { "svg":{}, "vml":{} };
| + | |
- | this.Endpoints = { "svg":{}, "vml":{} };
| + | |
- | this.Overlays = { "svg":{}, "vml":{}};
| + | |
- | this.ConnectorRenderers = {};
| + | |
- | this.SVG = "svg";
| + | |
- | this.VML = "vml";
| + | |
- | | + | |
- | // --------------------------- jsPLumbInstance public API ---------------------------------------------------------
| + | |
- |
| + | |
- |
| + | |
- | this.addEndpoint = function(el, params, referenceParams) {
| + | |
- | referenceParams = referenceParams || {}; | + | |
- | var p = jsPlumb.extend({}, referenceParams);
| + | |
- | jsPlumb.extend(p, params);
| + | |
- | p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint;
| + | |
- | p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle;
| + | |
- | // YUI wrapper
| + | |
- | el = _convertYUICollection(el);
| + | |
- | | + | |
- | var results = [],
| + | |
- | inputs = (_ju.isArray(el) || (el.length != null && !_ju.isString(el))) ? el : [ el ];
| + | |
- |
| + | |
- | for (var i = 0, j = inputs.length; i < j; i++) {
| + | |
- | var _el = _currentInstance.getDOMElement(inputs[i]), id = _getId(_el);
| + | |
- | p.source = _el;
| + | |
- | | + | |
- | _ensureContainer(p.source);
| + | |
- | _updateOffset({ elId : id, timestamp:_suspendedAt });
| + | |
- | var e = _newEndpoint(p);
| + | |
- | if (p.parentAnchor) e.parentAnchor = p.parentAnchor;
| + | |
- | _ju.addToList(endpointsByElement, id, e);
| + | |
- | var myOffset = offsets[id],
| + | |
- | myWH = sizes[id],
| + | |
- | anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e, timestamp:_suspendedAt }),
| + | |
- | endpointPaintParams = { anchorLoc : anchorLoc, timestamp:_suspendedAt };
| + | |
- |
| + | |
- | if (_suspendDrawing) endpointPaintParams.recalc = false;
| + | |
- | if (!_suspendDrawing) e.paint(endpointPaintParams);
| + | |
- |
| + | |
- | results.push(e);
| + | |
- | e._doNotDeleteOnDetach = true; // mark this as being added via addEndpoint.
| + | |
- | }
| + | |
- |
| + | |
- | return results.length == 1 ? results[0] : results;
| + | |
- | };
| + | |
- |
| + | |
- |
| + | |
- | this.addEndpoints = function(el, endpoints, referenceParams) {
| + | |
- | var results = [];
| + | |
- | for ( var i = 0, j = endpoints.length; i < j; i++) {
| + | |
- | var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); | + | |
- | if (_ju.isArray(e))
| + | |
- | Array.prototype.push.apply(results, e);
| + | |
- | else results.push(e);
| + | |
- | }
| + | |
- | return results;
| + | |
- | };
| + | |
- |
| + | |
- | this.animate = function(el, properties, options) {
| + | |
- | options = options || {};
| + | |
- | var ele = this.getElementObject(el),
| + | |
- | del = this.getDOMElement(el),
| + | |
- | id = _getId(del),
| + | |
- | stepFunction = jsPlumb.animEvents.step,
| + | |
- | completeFunction = jsPlumb.animEvents.complete;
| + | |
- | | + | |
- | options[stepFunction] = _ju.wrap(options[stepFunction], function() {
| + | |
- | _currentInstance.repaint(id);
| + | |
- | });
| + | |
- | | + | |
- | // onComplete repaints, just to make sure everything looks good at the end of the animation.
| + | |
- | options[completeFunction] = _ju.wrap(options[completeFunction], function() {
| + | |
- | _currentInstance.repaint(id);
| + | |
- | });
| + | |
- | | + | |
- | _currentInstance.doAnimate(ele, properties, options);
| + | |
- | }; | + | |
- |
| + | |
- | /**
| + | |
- | * checks for a listener for the given condition, executing it if found, passing in the given value.
| + | |
- | * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since | + | |
- | * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition"
| + | |
- | * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these
| + | |
- | * condition events anyway.
| + | |
- | */
| + | |
- | this.checkCondition = function(conditionName, value) {
| + | |
- | var l = _currentInstance.getListener(conditionName), | + | |
- | r = true;
| + | |
- |
| + | |
- | if (l && l.length > 0) {
| + | |
- | try {
| + | |
- | for (var i = 0, j = l.length; i < j; i++) {
| + | |
- | r = r && l[i](value);
| + | |
- | }
| + | |
- | }
| + | |
- | catch (e) {
| + | |
- | _ju.log(_currentInstance, "cannot check condition [" + conditionName + "]" + e);
| + | |
- | }
| + | |
- | }
| + | |
- | return r;
| + | |
- | };
| + | |
- |
| + | |
- | /**
| + | |
- | * checks a condition asynchronously: fires the event handler and passes the handler
| + | |
- | * a 'proceed' function and a 'stop' function. The handler MUST execute one or other
| + | |
- | * of these once it has made up its mind.
| + | |
- | *
| + | |
- | * Note that although this reads the listener list for the given condition, it
| + | |
- | * does not loop through and hit each listener, because that, with asynchronous
| + | |
- | * callbacks, would be messy. so it uses only the first listener registered.
| + | |
- | */
| + | |
- | this.checkASyncCondition = function(conditionName, value, proceed, stop) {
| + | |
- | var l = _currentInstance.getListener(conditionName);
| + | |
- |
| + | |
- | if (l && l.length > 0) {
| + | |
- | try {
| + | |
- | l[0](value, proceed, stop);
| + | |
- | }
| + | |
- | catch (e) {
| + | |
- | _ju.log(_currentInstance, "cannot asynchronously check condition [" + conditionName + "]" + e);
| + | |
- | }
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- |
| + | |
- | this.connect = function(params, referenceParams) {
| + | |
- | // prepare a final set of parameters to create connection with
| + | |
- | var _p = _prepareConnectionParams(params, referenceParams), jpc;
| + | |
- | // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams
| + | |
- | // will return null (and log something) if either endpoint was full. what would be nicer is to
| + | |
- | // create a dedicated 'error' object.
| + | |
- | if (_p) {
| + | |
- | _ensureContainer(_p.source);
| + | |
- | // create the connection. it is not yet registered
| + | |
- | jpc = _newConnection(_p);
| + | |
- | // now add it the model, fire an event, and redraw
| + | |
- | _finaliseConnection(jpc, _p);
| + | |
- | }
| + | |
- | return jpc;
| + | |
- | };
| + | |
- |
| + | |
- | var stTypes = [
| + | |
- | { el:"source", elId:"sourceId", epDefs:"sourceEndpointDefinitions" },
| + | |
- | { el:"target", elId:"targetId", epDefs:"targetEndpointDefinitions" }
| + | |
- | ];
| + | |
- |
| + | |
- | var _set = function(c, el, idx, doNotRepaint) {
| + | |
- | var ep, _st = stTypes[idx], cId = c[_st.elId], cEl = c[_st.el], sid, sep,
| + | |
- | oldEndpoint = c.endpoints[idx];
| + | |
- |
| + | |
- | var evtParams = {
| + | |
- | index:idx,
| + | |
- | originalSourceId:idx === 0 ? cId : c.sourceId,
| + | |
- | newSourceId:c.sourceId,
| + | |
- | originalTargetId:idx == 1 ? cId : c.targetId,
| + | |
- | newTargetId:c.targetId,
| + | |
- | connection:c
| + | |
- | };
| + | |
- | | + | |
- | if (el.constructor == jsPlumb.Endpoint) { // TODO here match the current endpoint class; users can change it {
| + | |
- | ep = el;
| + | |
- | ep.addConnection(c);
| + | |
- | }
| + | |
- | else {
| + | |
- | sid = _getId(el);
| + | |
- | sep = this[_st.epDefs][sid];
| + | |
- | | + | |
- | if (sid === c[_st.elId])
| + | |
- | ep = null; // dont change source/target if the element is already the one given.
| + | |
- | else if (sep) {
| + | |
- | if (!sep.enabled) return;
| + | |
- | ep = sep.endpoint != null && sep.endpoint._jsPlumb ? sep.endpoint : this.addEndpoint(el, sep.def);
| + | |
- | if (sep.uniqueEndpoint) sep.endpoint = ep;
| + | |
- | ep._doNotDeleteOnDetach = false;
| + | |
- | ep._deleteOnDetach = true;
| + | |
- | ep.addConnection(c);
| + | |
- | }
| + | |
- | else {
| + | |
- | ep = c.makeEndpoint(idx === 0, el, sid);
| + | |
- | ep._doNotDeleteOnDetach = false;
| + | |
- | ep._deleteOnDetach = true;
| + | |
- | }
| + | |
- | }
| + | |
- |
| + | |
- | if (ep != null) {
| + | |
- | oldEndpoint.detachFromConnection(c);
| + | |
- | c.endpoints[idx] = ep;
| + | |
- | c[_st.el] = ep.element;
| + | |
- | c[_st.elId] = ep.elementId;
| + | |
- | evtParams[idx === 0 ? "newSourceId" : "newTargetId"] = ep.elementId;
| + | |
- | | + | |
- | fireMoveEvent(evtParams);
| + | |
- |
| + | |
- | if (!doNotRepaint)
| + | |
- | c.repaint();
| + | |
- | }
| + | |
- | | + | |
- | return evtParams;
| + | |
- |
| + | |
- | }.bind(this);
| + | |
- | | + | |
- | this.setSource = function(connection, el, doNotRepaint) {
| + | |
- | var p = _set(connection, el, 0, doNotRepaint);
| + | |
- | this.anchorManager.sourceChanged(p.originalSourceId, p.newSourceId, connection);
| + | |
- | };
| + | |
- | this.setTarget = function(connection, el, doNotRepaint) {
| + | |
- | var p = _set(connection, el, 1, doNotRepaint);
| + | |
- | this.anchorManager.updateOtherEndpoint(p.originalSourceId, p.originalTargetId, p.newTargetId, connection);
| + | |
- | };
| + | |
- |
| + | |
- | this.deleteEndpoint = function(object, doNotRepaintAfterwards) {
| + | |
- | var _is = _currentInstance.setSuspendDrawing(true);
| + | |
- | var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object;
| + | |
- | if (endpoint) {
| + | |
- | _currentInstance.deleteObject({
| + | |
- | endpoint:endpoint
| + | |
- | });
| + | |
- | }
| + | |
- | if(!_is) _currentInstance.setSuspendDrawing(false, doNotRepaintAfterwards);
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- |
| + | |
- | this.deleteEveryEndpoint = function() {
| + | |
- | var _is = _currentInstance.setSuspendDrawing(true);
| + | |
- | for ( var id in endpointsByElement) {
| + | |
- | var endpoints = endpointsByElement[id];
| + | |
- | if (endpoints && endpoints.length) {
| + | |
- | for ( var i = 0, j = endpoints.length; i < j; i++) {
| + | |
- | _currentInstance.deleteEndpoint(endpoints[i], true);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | endpointsByElement = {};
| + | |
- | endpointsByUUID = {};
| + | |
- | _currentInstance.anchorManager.reset();
| + | |
- | _currentInstance.dragManager.reset();
| + | |
- | if(!_is) _currentInstance.setSuspendDrawing(false);
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- | | + | |
- | var fireDetachEvent = function(jpc, doFireEvent, originalEvent) {
| + | |
- | // may have been given a connection, or in special cases, an object
| + | |
- | var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
| + | |
- | argIsConnection = jpc.constructor == connType,
| + | |
- | params = argIsConnection ? {
| + | |
- | connection:jpc,
| + | |
- | source : jpc.source, target : jpc.target,
| + | |
- | sourceId : jpc.sourceId, targetId : jpc.targetId,
| + | |
- | sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
| + | |
- | } : jpc;
| + | |
- | | + | |
- | if (doFireEvent)
| + | |
- | _currentInstance.fire("connectionDetached", params, originalEvent);
| + | |
- |
| + | |
- | _currentInstance.anchorManager.connectionDetached(params);
| + | |
- | };
| + | |
- | | + | |
- | var fireMoveEvent = function(params, evt) {
| + | |
- | _currentInstance.fire("connectionMoved", params, evt);
| + | |
- | };
| + | |
- | | + | |
- | this.unregisterEndpoint = function(endpoint) {
| + | |
- | //if (endpoint._jsPlumb == null) return;
| + | |
- | if (endpoint._jsPlumb.uuid) endpointsByUUID[endpoint._jsPlumb.uuid] = null;
| + | |
- | _currentInstance.anchorManager.deleteEndpoint(endpoint);
| + | |
- | // TODO at least replace this with a removeWithFunction call.
| + | |
- | for (var e in endpointsByElement) {
| + | |
- | var endpoints = endpointsByElement[e];
| + | |
- | if (endpoints) {
| + | |
- | var newEndpoints = [];
| + | |
- | for (var i = 0, j = endpoints.length; i < j; i++)
| + | |
- | if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]);
| + | |
- |
| + | |
- | endpointsByElement[e] = newEndpoints;
| + | |
- | }
| + | |
- | if(endpointsByElement[e].length <1){
| + | |
- | delete endpointsByElement[e];
| + | |
- | }
| + | |
- | }
| + | |
- | };
| + | |
- |
| + | |
- | this.detach = function() {
| + | |
- | | + | |
- | if (arguments.length === 0) return;
| + | |
- | var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
| + | |
- | firstArgIsConnection = arguments[0].constructor == connType,
| + | |
- | params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0],
| + | |
- | fireEvent = (params.fireEvent !== false),
| + | |
- | forceDetach = params.forceDetach,
| + | |
- | conn = firstArgIsConnection ? arguments[0] : params.connection;
| + | |
- |
| + | |
- | if (conn) {
| + | |
- | if (forceDetach || jsPlumbUtil.functionChain(true, false, [
| + | |
- | [ conn.endpoints[0], "isDetachAllowed", [ conn ] ],
| + | |
- | [ conn.endpoints[1], "isDetachAllowed", [ conn ] ],
| + | |
- | [ conn, "isDetachAllowed", [ conn ] ],
| + | |
- | [ _currentInstance, "checkCondition", [ "beforeDetach", conn ] ] ])) {
| + | |
- |
| + | |
- | conn.endpoints[0].detach(conn, false, true, fireEvent);
| + | |
- | }
| + | |
- | }
| + | |
- | else {
| + | |
- | var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case.
| + | |
- | // test for endpoint uuids to detach
| + | |
- | if (_p.uuids) {
| + | |
- | _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent);
| + | |
- | } else if (_p.sourceEndpoint && _p.targetEndpoint) {
| + | |
- | _p.sourceEndpoint.detachFrom(_p.targetEndpoint);
| + | |
- | } else {
| + | |
- | var sourceId = _getId(_currentInstance.getDOMElement(_p.source)),
| + | |
- | targetId = _getId(_currentInstance.getDOMElement(_p.target));
| + | |
- | _operation(sourceId, function(jpc) {
| + | |
- | if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) {
| + | |
- | if (_currentInstance.checkCondition("beforeDetach", jpc)) {
| + | |
- | jpc.endpoints[0].detach(jpc, false, true, fireEvent);
| + | |
- | }
| + | |
- | }
| + | |
- | });
| + | |
- | }
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | this.detachAllConnections = function(el, params) {
| + | |
- | params = params || {};
| + | |
- | el = _currentInstance.getDOMElement(el);
| + | |
- | var id = _getId(el),
| + | |
- | endpoints = endpointsByElement[id];
| + | |
- | if (endpoints && endpoints.length) {
| + | |
- | for ( var i = 0, j = endpoints.length; i < j; i++) {
| + | |
- | endpoints[i].detachAll(params.fireEvent !== false);
| + | |
- | }
| + | |
- | }
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- | | + | |
- | this.detachEveryConnection = function(params) {
| + | |
- | params = params || {};
| + | |
- | _currentInstance.doWhileSuspended(function() {
| + | |
- | for ( var id in endpointsByElement) {
| + | |
- | var endpoints = endpointsByElement[id];
| + | |
- | if (endpoints && endpoints.length) {
| + | |
- | for ( var i = 0, j = endpoints.length; i < j; i++) {
| + | |
- | endpoints[i].detachAll(params.fireEvent !== false);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | connections.splice(0);
| + | |
- | });
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- | | + | |
- | /// not public. but of course its exposed. how to change this.
| + | |
- | this.deleteObject = function(params) {
| + | |
- | var result = {
| + | |
- | endpoints : {},
| + | |
- | connections : {},
| + | |
- | endpointCount:0,
| + | |
- | connectionCount:0
| + | |
- | },
| + | |
- | fireEvent = params.fireEvent !== false,
| + | |
- | deleteAttachedObjects = params.deleteAttachedObjects !== false;
| + | |
- | | + | |
- | var unravelConnection = function(connection) {
| + | |
- | if(connection != null && result.connections[connection.id] == null) {
| + | |
- | if (connection._jsPlumb != null) connection.setHover(false);
| + | |
- | result.connections[connection.id] = connection;
| + | |
- | result.connectionCount++;
| + | |
- | if (deleteAttachedObjects) {
| + | |
- | for (var j = 0; j < connection.endpoints.length; j++) {
| + | |
- | if (connection.endpoints[j]._deleteOnDetach)
| + | |
- | unravelEndpoint(connection.endpoints[j]);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | };
| + | |
- | var unravelEndpoint = function(endpoint) {
| + | |
- | if(endpoint != null && result.endpoints[endpoint.id] == null) {
| + | |
- | if (endpoint._jsPlumb != null) endpoint.setHover(false);
| + | |
- | result.endpoints[endpoint.id] = endpoint;
| + | |
- | result.endpointCount++;
| + | |
- | | + | |
- | if (deleteAttachedObjects) {
| + | |
- | for (var i = 0; i < endpoint.connections.length; i++) {
| + | |
- | var c = endpoint.connections[i];
| + | |
- | unravelConnection(c);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | if (params.connection)
| + | |
- | unravelConnection(params.connection);
| + | |
- | else unravelEndpoint(params.endpoint);
| + | |
- | | + | |
- | // loop through connections
| + | |
- | for (var i in result.connections) {
| + | |
- | var c = result.connections[i];
| + | |
- | if (c._jsPlumb) {
| + | |
- | jsPlumbUtil.removeWithFunction(connections, function(_c) {
| + | |
- | return c.id == _c.id;
| + | |
- | });
| + | |
- | fireDetachEvent(c, fireEvent, params.originalEvent);
| + | |
- |
| + | |
- | c.endpoints[0].detachFromConnection(c);
| + | |
- | c.endpoints[1].detachFromConnection(c);
| + | |
- | // sp was ere
| + | |
- | c.cleanup();
| + | |
- | c.destroy();
| + | |
- | }
| + | |
- | }
| + | |
- | | + | |
- | // loop through endpoints
| + | |
- | for (var j in result.endpoints) {
| + | |
- | var e = result.endpoints[j];
| + | |
- | if (e._jsPlumb) {
| + | |
- | _currentInstance.unregisterEndpoint(e);
| + | |
- | // FIRE some endpoint deleted event?
| + | |
- | e.cleanup();
| + | |
- | e.destroy();
| + | |
- | }
| + | |
- | }
| + | |
- | | + | |
- | return result;
| + | |
- | };
| + | |
- |
| + | |
- | this.draggable = function(el, options) {
| + | |
- | var i,j,ele;
| + | |
- | // allows for array or jquery/mootools selector
| + | |
- | if (typeof el == 'object' && el.length) {
| + | |
- | for (i = 0, j = el.length; i < j; i++) {
| + | |
- | ele = _currentInstance.getDOMElement(el[i]);
| + | |
- | if (ele) _initDraggableIfNecessary(ele, true, options);
| + | |
- | }
| + | |
- | }
| + | |
- | // allows for YUI selector
| + | |
- | else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced
| + | |
- | // into the library adapters (for jquery and mootools aswell)
| + | |
- | for (i = 0, j = el._nodes.length; i < j; i++) {
| + | |
- | ele = _currentInstance.getDOMElement(el._nodes[i]);
| + | |
- | if (ele) _initDraggableIfNecessary(ele, true, options);
| + | |
- | }
| + | |
- | }
| + | |
- | else {
| + | |
- | ele = _currentInstance.getDOMElement(el);
| + | |
- | if (ele) _initDraggableIfNecessary(ele, true, options);
| + | |
- | }
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- | | + | |
- | // helpers for select/selectEndpoints
| + | |
- | var _setOperation = function(list, func, args, selector) {
| + | |
- | for (var i = 0, j = list.length; i < j; i++) {
| + | |
- | list[i][func].apply(list[i], args);
| + | |
- | }
| + | |
- | return selector(list);
| + | |
- | },
| + | |
- | _getOperation = function(list, func, args) {
| + | |
- | var out = [];
| + | |
- | for (var i = 0, j = list.length; i < j; i++) {
| + | |
- | out.push([ list[i][func].apply(list[i], args), list[i] ]);
| + | |
- | }
| + | |
- | return out;
| + | |
- | },
| + | |
- | setter = function(list, func, selector) {
| + | |
- | return function() {
| + | |
- | return _setOperation(list, func, arguments, selector);
| + | |
- | };
| + | |
- | },
| + | |
- | getter = function(list, func) {
| + | |
- | return function() {
| + | |
- | return _getOperation(list, func, arguments);
| + | |
- | };
| + | |
- | },
| + | |
- | prepareList = function(input, doNotGetIds) {
| + | |
- | var r = [];
| + | |
- | if (input) {
| + | |
- | if (typeof input == 'string') {
| + | |
- | if (input === "*") return input;
| + | |
- | r.push(input);
| + | |
- | }
| + | |
- | else {
| + | |
- | if (doNotGetIds) r = input;
| + | |
- | else {
| + | |
- | if (input.length) {
| + | |
- | //input = _currentInstance.getElementObject(input);
| + | |
- | for (var i = 0, j = input.length; i < j; i++)
| + | |
- | r.push(_info(input[i]).id);
| + | |
- | }
| + | |
- | else
| + | |
- | r.push(_info(input).id);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | return r;
| + | |
- | },
| + | |
- | filterList = function(list, value, missingIsFalse) {
| + | |
- | if (list === "*") return true;
| + | |
- | return list.length > 0 ? jsPlumbUtil.indexOf(list, value) != -1 : !missingIsFalse;
| + | |
- | };
| + | |
- | | + | |
- | // get some connections, specifying source/target/scope
| + | |
- | this.getConnections = function(options, flat) {
| + | |
- | if (!options) {
| + | |
- | options = {};
| + | |
- | } else if (options.constructor == String) {
| + | |
- | options = { "scope": options };
| + | |
- | }
| + | |
- | var scope = options.scope || _currentInstance.getDefaultScope(),
| + | |
- | scopes = prepareList(scope, true),
| + | |
- | sources = prepareList(options.source),
| + | |
- | targets = prepareList(options.target),
| + | |
- | results = (!flat && scopes.length > 1) ? {} : [],
| + | |
- | _addOne = function(scope, obj) {
| + | |
- | if (!flat && scopes.length > 1) {
| + | |
- | var ss = results[scope];
| + | |
- | if (ss == null) {
| + | |
- | ss = results[scope] = [];
| + | |
- | }
| + | |
- | ss.push(obj);
| + | |
- | } else results.push(obj);
| + | |
- | };
| + | |
- |
| + | |
- | for ( var j = 0, jj = connections.length; j < jj; j++) {
| + | |
- | var c = connections[j];
| + | |
- | if (filterList(scopes, c.scope) && filterList(sources, c.sourceId) && filterList(targets, c.targetId))
| + | |
- | _addOne(c.scope, c);
| + | |
- | }
| + | |
- |
| + | |
- | return results;
| + | |
- | };
| + | |
- |
| + | |
- | var _curryEach = function(list, executor) {
| + | |
- | return function(f) {
| + | |
- | for (var i = 0, ii = list.length; i < ii; i++) {
| + | |
- | f(list[i]);
| + | |
- | }
| + | |
- | return executor(list);
| + | |
- | };
| + | |
- | },
| + | |
- | _curryGet = function(list) {
| + | |
- | return function(idx) {
| + | |
- | return list[idx];
| + | |
- | };
| + | |
- | };
| + | |
- |
| + | |
- | var _makeCommonSelectHandler = function(list, executor) {
| + | |
- | var out = {
| + | |
- | length:list.length,
| + | |
- | each:_curryEach(list, executor),
| + | |
- | get:_curryGet(list)
| + | |
- | },
| + | |
- | setters = ["setHover", "removeAllOverlays", "setLabel", "addClass", "addOverlay", "removeOverlay",
| + | |
- | "removeOverlays", "showOverlay", "hideOverlay", "showOverlays", "hideOverlays", "setPaintStyle",
| + | |
- | "setHoverPaintStyle", "setSuspendEvents", "setParameter", "setParameters", "setVisible",
| + | |
- | "repaint", "addType", "toggleType", "removeType", "removeClass", "setType", "bind", "unbind" ],
| + | |
- |
| + | |
- | getters = ["getLabel", "getOverlay", "isHover", "getParameter", "getParameters", "getPaintStyle",
| + | |
- | "getHoverPaintStyle", "isVisible", "hasType", "getType", "isSuspendEvents" ],
| + | |
- | i, ii;
| + | |
- |
| + | |
- | for (i = 0, ii = setters.length; i < ii; i++)
| + | |
- | out[setters[i]] = setter(list, setters[i], executor);
| + | |
- |
| + | |
- | for (i = 0, ii = getters.length; i < ii; i++)
| + | |
- | out[getters[i]] = getter(list, getters[i]);
| + | |
- |
| + | |
- | return out;
| + | |
- | };
| + | |
- |
| + | |
- | var _makeConnectionSelectHandler = function(list) {
| + | |
- | var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler);
| + | |
- | return jsPlumb.extend(common, {
| + | |
- | // setters
| + | |
- | setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler),
| + | |
- | setReattach:setter(list, "setReattach", _makeConnectionSelectHandler),
| + | |
- | setConnector:setter(list, "setConnector", _makeConnectionSelectHandler),
| + | |
- | detach:function() {
| + | |
- | for (var i = 0, ii = list.length; i < ii; i++)
| + | |
- | _currentInstance.detach(list[i]);
| + | |
- | },
| + | |
- | // getters
| + | |
- | isDetachable:getter(list, "isDetachable"),
| + | |
- | isReattach:getter(list, "isReattach")
| + | |
- | });
| + | |
- | };
| + | |
- |
| + | |
- | var _makeEndpointSelectHandler = function(list) {
| + | |
- | var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler);
| + | |
- | return jsPlumb.extend(common, {
| + | |
- | setEnabled:setter(list, "setEnabled", _makeEndpointSelectHandler),
| + | |
- | setAnchor:setter(list, "setAnchor", _makeEndpointSelectHandler),
| + | |
- | isEnabled:getter(list, "isEnabled"),
| + | |
- | detachAll:function() {
| + | |
- | for (var i = 0, ii = list.length; i < ii; i++)
| + | |
- | list[i].detachAll();
| + | |
- | },
| + | |
- | "remove":function() {
| + | |
- | for (var i = 0, ii = list.length; i < ii; i++)
| + | |
- | _currentInstance.deleteObject({endpoint:list[i]});
| + | |
- | }
| + | |
- | });
| + | |
- | };
| + | |
- |
| + | |
- | | + | |
- | this.select = function(params) {
| + | |
- | params = params || {};
| + | |
- | params.scope = params.scope || "*";
| + | |
- | return _makeConnectionSelectHandler(params.connections || _currentInstance.getConnections(params, true));
| + | |
- | };
| + | |
- | | + | |
- | this.selectEndpoints = function(params) {
| + | |
- | params = params || {};
| + | |
- | params.scope = params.scope || "*";
| + | |
- | var noElementFilters = !params.element && !params.source && !params.target,
| + | |
- | elements = noElementFilters ? "*" : prepareList(params.element),
| + | |
- | sources = noElementFilters ? "*" : prepareList(params.source),
| + | |
- | targets = noElementFilters ? "*" : prepareList(params.target),
| + | |
- | scopes = prepareList(params.scope, true);
| + | |
- |
| + | |
- | var ep = [];
| + | |
- |
| + | |
- | for (var el in endpointsByElement) {
| + | |
- | var either = filterList(elements, el, true),
| + | |
- | source = filterList(sources, el, true),
| + | |
- | sourceMatchExact = sources != "*",
| + | |
- | target = filterList(targets, el, true),
| + | |
- | targetMatchExact = targets != "*";
| + | |
- |
| + | |
- | // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget.
| + | |
- | if ( either || source || target ) {
| + | |
- | inner:
| + | |
- | for (var i = 0, ii = endpointsByElement[el].length; i < ii; i++) {
| + | |
- | var _ep = endpointsByElement[el][i];
| + | |
- | if (filterList(scopes, _ep.scope, true)) {
| + | |
- |
| + | |
- | var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource),
| + | |
- | noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget);
| + | |
- |
| + | |
- | if (noMatchSource || noMatchTarget)
| + | |
- | continue inner;
| + | |
- |
| + | |
- | ep.push(_ep);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | } | + | |
- |
| + | |
- | return _makeEndpointSelectHandler(ep);
| + | |
- | };
| + | |
- | | + | |
- | // get all connections managed by the instance of jsplumb.
| + | |
- | this.getAllConnections = function() { return connections; };
| + | |
- | this.getDefaultScope = function() { return DEFAULT_SCOPE; };
| + | |
- | // get an endpoint by uuid.
| + | |
- | this.getEndpoint = _getEndpoint;
| + | |
- | // get endpoints for some element.
| + | |
- | this.getEndpoints = function(el) { return endpointsByElement[_info(el).id]; };
| + | |
- | // gets the default endpoint type. used when subclassing. see wiki.
| + | |
- | this.getDefaultEndpointType = function() { return jsPlumb.Endpoint; };
| + | |
- | // gets the default connection type. used when subclassing. see wiki.
| + | |
- | this.getDefaultConnectionType = function() { return jsPlumb.Connection; };
| + | |
- | /*
| + | |
- | * Gets an element's id, creating one if necessary. really only exposed
| + | |
- | * for the lib-specific functionality to access; would be better to pass
| + | |
- | * the current instance into the lib-specific code (even though this is
| + | |
- | * a static call. i just don't want to expose it to the public API).
| + | |
- | */
| + | |
- | this.getId = _getId;
| + | |
- | this.getOffset = function(id) {
| + | |
- | var o = offsets[id];
| + | |
- | return _updateOffset({elId:id});
| + | |
- | };
| + | |
- |
| + | |
- | this.appendElement = _appendElement;
| + | |
- |
| + | |
- | var _hoverSuspended = false;
| + | |
- | this.isHoverSuspended = function() { return _hoverSuspended; };
| + | |
- | this.setHoverSuspended = function(s) { _hoverSuspended = s; };
| + | |
- | | + | |
- | var _isAvailable = function(m) {
| + | |
- | return function() {
| + | |
- | return jsPlumbAdapter.isRenderModeAvailable(m);
| + | |
- | };
| + | |
- | };
| + | |
- | | + | |
- | this.isSVGAvailable = _isAvailable("svg");
| + | |
- | this.isVMLAvailable = _isAvailable("vml");
| + | |
- | | + | |
- | // set an element's connections to be hidden
| + | |
- | this.hide = function(el, changeEndpoints) {
| + | |
- | _setVisible(el, "none", changeEndpoints);
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- |
| + | |
- | // exposed for other objects to use to get a unique id.
| + | |
- | this.idstamp = _idstamp;
| + | |
- | | + | |
- | this.connectorsInitialized = false;
| + | |
- | var connectorTypes = [], rendererTypes = ["svg", "vml"];
| + | |
- | this.registerConnectorType = function(connector, name) {
| + | |
- | connectorTypes.push([connector, name]);
| + | |
- | };
| + | |
- |
| + | |
- | // ensure that, if the current container exists, it is a DOM element and not a selector.
| + | |
- | // if it does not exist and `candidate` is supplied, the offset parent of that element will be set as the Container.
| + | |
- | // this is used to do a better default behaviour for the case that the user has not set a container:
| + | |
- | // addEndpoint, makeSource, makeTarget and connect all call this method with the offsetParent of the
| + | |
- | // element in question (for connect it is the source element). So if no container is set, it is inferred
| + | |
- | // to be the offsetParent of the first element the user tries to connect.
| + | |
- | var _ensureContainer = function(candidate) {
| + | |
- | if (!_container && candidate) {
| + | |
- | var can = _currentInstance.getDOMElement(candidate);
| + | |
- | if (can.offsetParent) _container = can.offsetParent;
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | var _getContainerFromDefaults = function() {
| + | |
- | if (_currentInstance.Defaults.Container)
| + | |
- | _container = _currentInstance.getDOMElement(_currentInstance.Defaults.Container);
| + | |
- | };
| + | |
- |
| + | |
- | /**
| + | |
- | * callback from the current library to tell us to prepare ourselves (attach
| + | |
- | * mouse listeners etc; can't do that until the library has provided a bind method)
| + | |
- | */
| + | |
- | this.init = function() {
| + | |
- | var _oneType = function(renderer, name, fn) {
| + | |
- | jsPlumb.Connectors[renderer][name] = function() {
| + | |
- | fn.apply(this, arguments);
| + | |
- | jsPlumb.ConnectorRenderers[renderer].apply(this, arguments);
| + | |
- | };
| + | |
- | jsPlumbUtil.extend(jsPlumb.Connectors[renderer][name], [ fn, jsPlumb.ConnectorRenderers[renderer]]);
| + | |
- | };
| + | |
- | | + | |
- | if (!jsPlumb.connectorsInitialized) {
| + | |
- | for (var i = 0; i < connectorTypes.length; i++) {
| + | |
- | for (var j = 0; j < rendererTypes.length; j++) {
| + | |
- | _oneType(rendererTypes[j], connectorTypes[i][1], connectorTypes[i][0]);
| + | |
- | }
| + | |
- | | + | |
- | }
| + | |
- | jsPlumb.connectorsInitialized = true;
| + | |
- | }
| + | |
- |
| + | |
- | if (!initialized) {
| + | |
- | _getContainerFromDefaults();
| + | |
- | _currentInstance.anchorManager = new jsPlumb.AnchorManager({jsPlumbInstance:_currentInstance});
| + | |
- | _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run.
| + | |
- | initialized = true;
| + | |
- | _currentInstance.fire("ready", _currentInstance);
| + | |
- | }
| + | |
- | }.bind(this);
| + | |
- |
| + | |
- | this.log = log;
| + | |
- | this.jsPlumbUIComponent = jsPlumbUIComponent;
| + | |
- | | + | |
- | /*
| + | |
- | * Creates an anchor with the given params.
| + | |
- | *
| + | |
- | *
| + | |
- | * Returns: The newly created Anchor.
| + | |
- | * Throws: an error if a named anchor was not found.
| + | |
- | */
| + | |
- | this.makeAnchor = function() {
| + | |
- | var pp, _a = function(t, p) {
| + | |
- | if (jsPlumb.Anchors[t]) return new jsPlumb.Anchors[t](p);
| + | |
- | if (!_currentInstance.Defaults.DoNotThrowErrors)
| + | |
- | throw { msg:"jsPlumb: unknown anchor type '" + t + "'" };
| + | |
- | };
| + | |
- | if (arguments.length === 0) return null;
| + | |
- | var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null;
| + | |
- | // if it appears to be an anchor already...
| + | |
- | if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow.
| + | |
- | // is it the name of an anchor type?
| + | |
- | else if (typeof specimen == "string") {
| + | |
- | newAnchor = _a(arguments[0], {elementId:elementId, jsPlumbInstance:_currentInstance});
| + | |
- | }
| + | |
- | // is it an array? it will be one of:
| + | |
- | // an array of [spec, params] - this defines a single anchor, which may be dynamic, but has parameters.
| + | |
- | // an array of arrays - this defines some dynamic anchors
| + | |
- | // an array of numbers - this defines a single anchor.
| + | |
- | else if (_ju.isArray(specimen)) {
| + | |
- | if (_ju.isArray(specimen[0]) || _ju.isString(specimen[0])) {
| + | |
- | // if [spec, params] format
| + | |
- | if (specimen.length == 2 && _ju.isObject(specimen[1])) {
| + | |
- | // if first arg is a string, its a named anchor with params
| + | |
- | if (_ju.isString(specimen[0])) {
| + | |
- | pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]);
| + | |
- | newAnchor = _a(specimen[0], pp);
| + | |
- | }
| + | |
- | // otherwise first arg is array, second is params. we treat as a dynamic anchor, which is fine
| + | |
- | // even if the first arg has only one entry. you could argue all anchors should be implicitly dynamic in fact.
| + | |
- | else {
| + | |
- | pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance, anchors:specimen[0]}, specimen[1]);
| + | |
- | newAnchor = new jsPlumb.DynamicAnchor(pp);
| + | |
- | }
| + | |
- | }
| + | |
- | else
| + | |
- | newAnchor = new jsPlumb.DynamicAnchor({anchors:specimen, selector:null, elementId:elementId, jsPlumbInstance:jsPlumbInstance});
| + | |
- | | + | |
- | }
| + | |
- | else {
| + | |
- | var anchorParams = {
| + | |
- | x:specimen[0], y:specimen[1],
| + | |
- | orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0],
| + | |
- | offsets : (specimen.length >= 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ],
| + | |
- | elementId:elementId,
| + | |
- | jsPlumbInstance:jsPlumbInstance,
| + | |
- | cssClass:specimen.length == 7 ? specimen[6] : null
| + | |
- | };
| + | |
- | newAnchor = new jsPlumb.Anchor(anchorParams);
| + | |
- | newAnchor.clone = function() { return new jsPlumb.Anchor(anchorParams); };
| + | |
- | }
| + | |
- | }
| + | |
- |
| + | |
- | if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp();
| + | |
- | return newAnchor;
| + | |
- | };
| + | |
- | | + | |
- | /**
| + | |
- | * makes a list of anchors from the given list of types or coords, eg
| + | |
- | * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ]
| + | |
- | */
| + | |
- | this.makeAnchors = function(types, elementId, jsPlumbInstance) {
| + | |
- | var r = [];
| + | |
- | for ( var i = 0, ii = types.length; i < ii; i++) {
| + | |
- | if (typeof types[i] == "string")
| + | |
- | r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance}));
| + | |
- | else if (_ju.isArray(types[i]))
| + | |
- | r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance));
| + | |
- | }
| + | |
- | return r;
| + | |
- | };
| + | |
- | | + | |
- | /**
| + | |
- | * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor
| + | |
- | * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will
| + | |
- | * not need to provide this - i think).
| + | |
- | */
| + | |
- | this.makeDynamicAnchor = function(anchors, anchorSelector) {
| + | |
- | return new jsPlumb.DynamicAnchor({anchors:anchors, selector:anchorSelector, elementId:null, jsPlumbInstance:_currentInstance});
| + | |
- | };
| + | |
- |
| + | |
- | // --------------------- makeSource/makeTarget ----------------------------------------------
| + | |
- |
| + | |
- | this.targetEndpointDefinitions = {};
| + | |
- | var _setEndpointPaintStylesAndAnchor = function(ep, epIndex, _instance) {
| + | |
- | ep.paintStyle = ep.paintStyle ||
| + | |
- | _instance.Defaults.EndpointStyles[epIndex] ||
| + | |
- | _instance.Defaults.EndpointStyle;
| + | |
- |
| + | |
- | ep.hoverPaintStyle = ep.hoverPaintStyle ||
| + | |
- | _instance.Defaults.EndpointHoverStyles[epIndex] ||
| + | |
- | _instance.Defaults.EndpointHoverStyle;
| + | |
- | | + | |
- | ep.anchor = ep.anchor ||
| + | |
- | _instance.Defaults.Anchors[epIndex] ||
| + | |
- | _instance.Defaults.Anchor;
| + | |
- |
| + | |
- | ep.endpoint = ep.endpoint ||
| + | |
- | _instance.Defaults.Endpoints[epIndex] ||
| + | |
- | _instance.Defaults.Endpoint;
| + | |
- | };
| + | |
- |
| + | |
- | // TODO put all the source stuff inside one parent, keyed by id.
| + | |
- | this.sourceEndpointDefinitions = {};
| + | |
- |
| + | |
- | var selectorFilter = function(evt, _el, selector, _instance, negate) {
| + | |
- | var t = evt.target || evt.srcElement, ok = false,
| + | |
- | sel = _instance.getSelector(_el, selector);
| + | |
- | for (var j = 0; j < sel.length; j++) {
| + | |
- | if (sel[j] == t) {
| + | |
- | ok = true;
| + | |
- | break;
| + | |
- | }
| + | |
- | }
| + | |
- | return negate ? !ok : ok;
| + | |
- | };
| + | |
- | | + | |
- | // see API docs
| + | |
- | this.makeTarget = function(el, params, referenceParams) {
| + | |
- | | + | |
- | // put jsplumb ref into params without altering the params passed in
| + | |
- | var p = jsPlumb.extend({_jsPlumb:this}, referenceParams);
| + | |
- | jsPlumb.extend(p, params);
| + | |
- | | + | |
- | // calculate appropriate paint styles and anchor from the params given
| + | |
- | _setEndpointPaintStylesAndAnchor(p, 1, this);
| + | |
- | | + | |
- | var targetScope = p.scope || _currentInstance.Defaults.Scope,
| + | |
- | deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false),
| + | |
- | maxConnections = p.maxConnections || -1,
| + | |
- | onMaxConnections = p.onMaxConnections,
| + | |
- | | + | |
- | _doOne = function(el) {
| + | |
- |
| + | |
- | // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these,
| + | |
- | // and use the endpoint definition if found.
| + | |
- | // decode the info for this element (id and element)
| + | |
- | var elInfo = _info(el),
| + | |
- | elid = elInfo.id,
| + | |
- | proxyComponent = new jsPlumbUIComponent(p),
| + | |
- | dropOptions = jsPlumb.extend({}, p.dropOptions || {});
| + | |
- | | + | |
- | _ensureContainer(elid);
| + | |
- | | + | |
- | // store the definitions keyed against the element id.
| + | |
- | // TODO why not just store inside the element itself?
| + | |
- | this.targetEndpointDefinitions[elid] = {
| + | |
- | def:p,
| + | |
- | uniqueEndpoint:p.uniqueEndpoint,
| + | |
- | maxConnections:maxConnections,
| + | |
- | enabled:true
| + | |
- | };
| + | |
- | | + | |
- | var _drop = function() {
| + | |
- | this.currentlyDragging = false;
| + | |
- | var originalEvent = this.getDropEvent(arguments),
| + | |
- | targetCount = this.select({target:elid}).length,
| + | |
- | draggable = this.getDOMElement(this.getDragObject(arguments)),
| + | |
- | id = this.getAttribute(draggable, "dragId"),
| + | |
- | scope = this.getAttribute(draggable, "originalScope"),
| + | |
- | jpc = floatingConnections[id];
| + | |
- | | + | |
- | if (jpc == null) return;
| + | |
- | | + | |
- | var idx = jpc.endpoints[0].isFloating() ? 0 : 1,
| + | |
- | // this is not necessarily correct. if the source is being dragged,
| + | |
- | // then the source endpoint is actually the currently suspended endpoint.
| + | |
- | source = jpc.endpoints[0],
| + | |
- | _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {},
| + | |
- | def = this.targetEndpointDefinitions[elid];
| + | |
- |
| + | |
- | if (!def.enabled || def.maxConnections > 0 && targetCount >= def.maxConnections){
| + | |
- | if (onMaxConnections) {
| + | |
- | // TODO here we still have the id of the floating element, not the
| + | |
- | // actual target.
| + | |
- | onMaxConnections({
| + | |
- | element:elInfo.el,
| + | |
- | connection:jpc
| + | |
- | }, originalEvent);
| + | |
- | }
| + | |
- | return false;
| + | |
- | }
| + | |
- | | + | |
- | // unlock the source anchor to allow it to refresh its position if necessary
| + | |
- | source.anchor.locked = false;
| + | |
- | | + | |
- | // restore the original scope if necessary (issue 57)
| + | |
- | if (scope) this.setDragScope(draggable, scope);
| + | |
- | | + | |
- | // if no suspendedEndpoint and not pending, it is likely there was a drop on two
| + | |
- | // elements that are on top of each other. abort.
| + | |
- | if (jpc.suspendedEndpoint == null && !jpc.pending)
| + | |
- | return false;
| + | |
- |
| + | |
- | // check if drop is allowed here.
| + | |
- | // if the source is being dragged then in fact
| + | |
- | // the source and target ids to pass into the drop interceptor are
| + | |
- | // source - elid
| + | |
- | // target - jpc's targetId
| + | |
- | //
| + | |
- | // otherwise the ids are
| + | |
- | // source - jpc.sourceId
| + | |
- | // target - elid
| + | |
- | //
| + | |
- | var _continue = proxyComponent.isDropAllowed(idx === 0 ? elid : jpc.sourceId, idx === 0 ? jpc.targetId : elid, jpc.scope, jpc, null, idx === 0 ? elInfo.el : jpc.source, idx === 0 ? jpc.target : elInfo.el);
| + | |
- | | + | |
- | // reinstate any suspended endpoint; this just puts the connection back into
| + | |
- | // a state in which it will report sensible values if someone asks it about
| + | |
- | // its target. we're going to throw this connection away shortly so it doesnt matter
| + | |
- | // if we manipulate it a bit.
| + | |
- | if (jpc.suspendedEndpoint) {
| + | |
- | jpc[idx ? "targetId" : "sourceId"] = jpc.suspendedEndpoint.elementId;
| + | |
- | jpc[idx ? "target" : "source"] = jpc.suspendedEndpoint.element;
| + | |
- | jpc.endpoints[idx] = jpc.suspendedEndpoint;
| + | |
- |
| + | |
- | // TODO this and the normal endpoint drop should
| + | |
- | // be refactored to share more of the common code.
| + | |
- | var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId;
| + | |
- | fireMoveEvent({
| + | |
- | index:idx,
| + | |
- | originalSourceId:idx === 0 ? suspendedElementId : jpc.sourceId,
| + | |
- | newSourceId:idx === 0 ? elid : jpc.sourceId,
| + | |
- | originalTargetId:idx == 1 ? suspendedElementId : jpc.targetId,
| + | |
- | newTargetId:idx == 1 ? elid : jpc.targetId,
| + | |
- | connection:jpc
| + | |
- | }, originalEvent);
| + | |
- | }
| + | |
- | | + | |
- | if (_continue) {
| + | |
- | // make a new Endpoint for the target, or get it from the cache if uniqueEndpoint
| + | |
- | // is set.
| + | |
- | var _el = this.getElementObject(elInfo.el),
| + | |
- | newEndpoint = def.endpoint;
| + | |
- | | + | |
- | // if no cached endpoint, or there was one but it has been cleaned up
| + | |
- | // (ie. detached), then create a new one.
| + | |
- | if (newEndpoint == null || newEndpoint._jsPlumb == null)
| + | |
- | newEndpoint = this.addEndpoint(_el, p);
| + | |
- | | + | |
- | if (p.uniqueEndpoint) def.endpoint = newEndpoint; // may of course just store what it just pulled out. that's ok.
| + | |
- | // TODO test options to makeTarget to see if we should do this?
| + | |
- | newEndpoint._doNotDeleteOnDetach = false; // reset.
| + | |
- | newEndpoint._deleteOnDetach = true;
| + | |
- | | + | |
- | // if connection is detachable, init the new endpoint to be draggable, to support that happening.
| + | |
- | if (jpc.isDetachable())
| + | |
- | newEndpoint.initDraggable();
| + | |
- |
| + | |
- | // if the anchor has a 'positionFinder' set, then delegate to that function to find
| + | |
- | // out where to locate the anchor.
| + | |
- | if (newEndpoint.anchor.positionFinder != null) {
| + | |
- | var dropPosition = this.getUIPosition(arguments, this.getZoom()),
| + | |
- | elPosition = _getOffset(_el, this),
| + | |
- | elSize = this.getSize(_el),
| + | |
- | ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams);
| + | |
- | newEndpoint.anchor.x = ap[0];
| + | |
- | newEndpoint.anchor.y = ap[1];
| + | |
- | // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to
| + | |
- | // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation
| + | |
- | // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be
| + | |
- | // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which
| + | |
- | // the target is furthest away from the source.
| + | |
- | }
| + | |
- |
| + | |
- | // change the target endpoint and target element information. really this should be
| + | |
- | // done on a method on connection
| + | |
- | jpc[idx ? "target" : "source"] = newEndpoint.element;
| + | |
- | jpc[idx ? "targetId" : "sourceId"] = newEndpoint.elementId;
| + | |
- | jpc.endpoints[idx].detachFromConnection(jpc);
| + | |
- | if (jpc.endpoints[idx]._deleteOnDetach)
| + | |
- | jpc.endpoints[idx].deleteAfterDragStop = true; // tell this endpoint to delet itself after drag stop.
| + | |
- | // set new endpoint, and configure the settings for endpoints to delete on detach
| + | |
- | newEndpoint.addConnection(jpc);
| + | |
- | jpc.endpoints[idx] = newEndpoint;
| + | |
- | jpc.deleteEndpointsOnDetach = deleteEndpointsOnDetach;
| + | |
- | | + | |
- | // inform the anchor manager to update its target endpoint for this connection.
| + | |
- | // TODO refactor to make this a single method.
| + | |
- | if (idx == 1)
| + | |
- | this.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.suspendedElementId, jpc.targetId, jpc);
| + | |
- | else
| + | |
- | this.anchorManager.sourceChanged(jpc.suspendedEndpoint.elementId, jpc.sourceId, jpc);
| + | |
- | | + | |
- | _finaliseConnection(jpc, null, originalEvent);
| + | |
- | jpc.pending = false;
| + | |
- | | + | |
- | }
| + | |
- | // if not allowed to drop...
| + | |
- | else {
| + | |
- | // TODO this code is identical (pretty much) to what happens when a connection
| + | |
- | // dragged from a normal endpoint is in this situation. refactor.
| + | |
- | // is this an existing connection, and will we reattach?
| + | |
- | if (jpc.suspendedEndpoint) {
| + | |
- | if (jpc.isReattach()) {
| + | |
- | jpc.setHover(false);
| + | |
- | jpc.floatingAnchorIndex = null;
| + | |
- | jpc.suspendedEndpoint.addConnection(jpc);
| + | |
- | this.repaint(source.elementId);
| + | |
- | }
| + | |
- | else
| + | |
- | jpc.deleteConnectionNow = true;
| + | |
- | }
| + | |
- | }
| + | |
- | }.bind(this);
| + | |
- |
| + | |
- | // wrap drop events as needed and initialise droppable
| + | |
- | var dropEvent = jsPlumb.dragEvents.drop;
| + | |
- | dropOptions.scope = dropOptions.scope || targetScope;
| + | |
- | dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], _drop);
| + | |
- | // vanilla jsplumb only
| + | |
- | if (p.allowLoopback === false) {
| + | |
- | dropOptions.canDrop = function(_drag) {
| + | |
- | var de = _drag.getDragElement()._jsPlumbRelatedElement;
| + | |
- | return de != elInfo.el;
| + | |
- | };
| + | |
- | }
| + | |
- | this.initDroppable(this.getElementObject(elInfo.el), dropOptions, true);
| + | |
- | }.bind(this);
| + | |
- |
| + | |
- | // YUI collection fix
| + | |
- | el = _convertYUICollection(el);
| + | |
- | // make an array if only given one element
| + | |
- | var inputs = el.length && el.constructor != String ? el : [ el ];
| + | |
- |
| + | |
- | // register each one in the list.
| + | |
- | for (var i = 0, ii = inputs.length; i < ii; i++) {
| + | |
- | _doOne(inputs[i]);
| + | |
- | } | + | |
- | | + | |
- | return this;
| + | |
- | };
| + | |
- | | + | |
- | // see api docs
| + | |
- | this.unmakeTarget = function(el, doNotClearArrays) {
| + | |
- | var info = _info(el);
| + | |
- | | + | |
- | jsPlumb.destroyDroppable(info.el);
| + | |
- | // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from
| + | |
- | // the element. the effect will be to prevent it from behaving as a target, but it's not completely purged.
| + | |
- | if (!doNotClearArrays) {
| + | |
- | delete this.targetEndpointDefinitions[info.id];
| + | |
- | }
| + | |
- | | + | |
- | return this;
| + | |
- | };
| + | |
- | | + | |
- | // see api docs
| + | |
- | this.makeSource = function(el, params, referenceParams) {
| + | |
- | var p = jsPlumb.extend({}, referenceParams);
| + | |
- | jsPlumb.extend(p, params);
| + | |
- | _setEndpointPaintStylesAndAnchor(p, 0, this);
| + | |
- | var maxConnections = p.maxConnections || 1,
| + | |
- | onMaxConnections = p.onMaxConnections,
| + | |
- | _doOne = function(elInfo) {
| + | |
- | // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these,
| + | |
- | // and use the endpoint definition if found.
| + | |
- | var elid = elInfo.id,
| + | |
- | _el = this.getElementObject(elInfo.el),
| + | |
- | _del = this.getDOMElement(_el),
| + | |
- | parentElement = function() {
| + | |
- | return p.parent == null ? null : p.parent === "parent" ? elInfo.el.parentNode : _currentInstance.getDOMElement(p.parent);
| + | |
- | },
| + | |
- | idToRegisterAgainst = p.parent != null ? this.getId(parentElement()) : elid;
| + | |
- | | + | |
- | _ensureContainer(idToRegisterAgainst);
| + | |
- |
| + | |
- | this.sourceEndpointDefinitions[idToRegisterAgainst] = {
| + | |
- | def:p,
| + | |
- | uniqueEndpoint:p.uniqueEndpoint,
| + | |
- | maxConnections:maxConnections,
| + | |
- | enabled:true
| + | |
- | };
| + | |
- | var stopEvent = jsPlumb.dragEvents.stop,
| + | |
- | dragEvent = jsPlumb.dragEvents.drag,
| + | |
- | dragOptions = jsPlumb.extend({ }, p.dragOptions || {}),
| + | |
- | existingDrag = dragOptions.drag,
| + | |
- | existingStop = dragOptions.stop,
| + | |
- | ep = null,
| + | |
- | endpointAddedButNoDragYet = false;
| + | |
- | | + | |
- | // set scope if its not set in dragOptions but was passed in in params
| + | |
- | dragOptions.scope = dragOptions.scope || p.scope;
| + | |
- | | + | |
- | dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], function() {
| + | |
- | if (existingDrag) existingDrag.apply(this, arguments);
| + | |
- | endpointAddedButNoDragYet = false;
| + | |
- | });
| + | |
- |
| + | |
- | dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent], function() {
| + | |
- | | + | |
- | if (existingStop) existingStop.apply(this, arguments);
| + | |
- | this.currentlyDragging = false;
| + | |
- | if (ep._jsPlumb != null) { // if not cleaned up...
| + | |
- |
| + | |
- | // reset the anchor to the anchor that was initially provided. the one we were using to drag
| + | |
- | // the connection was just a placeholder that was located at the place the user pressed the
| + | |
- | // mouse button to initiate the drag.
| + | |
- | var anchorDef = p.anchor || this.Defaults.Anchor,
| + | |
- | oldAnchor = ep.anchor,
| + | |
- | oldConnection = ep.connections[0],
| + | |
- | newAnchor = this.makeAnchor(anchorDef, elid, this),
| + | |
- | _el = ep.element;
| + | |
- | | + | |
- | // if the anchor has a 'positionFinder' set, then delegate to that function to find
| + | |
- | // out where to locate the anchor. issue 117.
| + | |
- | if (newAnchor.positionFinder != null) {
| + | |
- | var elPosition = _getOffset(_el, this),
| + | |
- | elSize = this.getSize(_el),
| + | |
- | dropPosition = { left:elPosition.left + (oldAnchor.x * elSize[0]), top:elPosition.top + (oldAnchor.y * elSize[1]) },
| + | |
- | ap = newAnchor.positionFinder(dropPosition, elPosition, elSize, newAnchor.constructorParams);
| + | |
- | | + | |
- | newAnchor.x = ap[0];
| + | |
- | newAnchor.y = ap[1];
| + | |
- | }
| + | |
- | | + | |
- | ep.setAnchor(newAnchor, true);
| + | |
- |
| + | |
- | if (p.parent) {
| + | |
- | var parent = parentElement();
| + | |
- | if (parent) {
| + | |
- | var potentialParent = p.container || _container;
| + | |
- | ep.setElement(parent, potentialParent);
| + | |
- | }
| + | |
- | }
| + | |
- |
| + | |
- | ep.repaint();
| + | |
- | this.repaint(ep.elementId);
| + | |
- | this.repaint(oldConnection.targetId);
| + | |
- | }
| + | |
- | }.bind(this));
| + | |
- |
| + | |
- | // when the user presses the mouse, add an Endpoint, if we are enabled.
| + | |
- | var mouseDownListener = function(e) {
| + | |
- | var evt = this.getOriginalEvent(e);
| + | |
- | var def = this.sourceEndpointDefinitions[idToRegisterAgainst];
| + | |
- | elid = this.getId(this.getDOMElement(_el)); // elid might have changed since this method was called to configure the element.
| + | |
- |
| + | |
- | // if disabled, return.
| + | |
- | if (!def.enabled) return;
| + | |
- |
| + | |
- | // if a filter was given, run it, and return if it says no.
| + | |
- | if (p.filter) {
| + | |
- | var r = jsPlumbUtil.isString(p.filter) ? selectorFilter(evt, _el, p.filter, this, p.filterExclude) : p.filter(evt, _el);
| + | |
- | if (r === false) return;
| + | |
- | }
| + | |
- |
| + | |
- | // if maxConnections reached
| + | |
- | var sourceCount = this.select({source:idToRegisterAgainst}).length;
| + | |
- | if (def.maxConnections >= 0 && (def.uniqueEndpoint && sourceCount >= def.maxConnections)) {
| + | |
- | if (onMaxConnections) {
| + | |
- | onMaxConnections({
| + | |
- | element:_el,
| + | |
- | maxConnections:maxConnections
| + | |
- | }, e);
| + | |
- | }
| + | |
- | return false;
| + | |
- | }
| + | |
- | | + | |
- | // find the position on the element at which the mouse was pressed; this is where the endpoint
| + | |
- | // will be located.
| + | |
- | var elxy = jsPlumbAdapter.getPositionOnElement(evt, _del, _zoom), pelxy = elxy;
| + | |
- | // for mootools/YUI..this parent stuff should be deprecated.
| + | |
- | if (p.parent) {
| + | |
- | pelxy = jsPlumbAdapter.getPositionOnElement(evt, parentElement(), _zoom);
| + | |
- | }
| + | |
- |
| + | |
- | // we need to override the anchor in here, and force 'isSource', but we don't want to mess with
| + | |
- | // the params passed in, because after a connection is established we're going to reset the endpoint
| + | |
- | // to have the anchor we were given.
| + | |
- | var tempEndpointParams = {};
| + | |
- | jsPlumb.extend(tempEndpointParams, p);
| + | |
- | tempEndpointParams.isTemporarySource = true;
| + | |
- | tempEndpointParams.anchor = [ elxy[0], elxy[1] , 0,0];
| + | |
- | tempEndpointParams.parentAnchor = [ pelxy[0], pelxy[1], 0, 0 ];
| + | |
- | tempEndpointParams.dragOptions = dragOptions;
| + | |
- | ep = this.addEndpoint(elid, tempEndpointParams);
| + | |
- | endpointAddedButNoDragYet = true;
| + | |
- | ep.endpointWillMoveTo = p.parent ? parentElement() : null;
| + | |
- | // TODO test options to makeSource to see if we should do this?
| + | |
- | ep._doNotDeleteOnDetach = false; // reset.
| + | |
- | ep._deleteOnDetach = true;
| + | |
- | | + | |
- | var _delTempEndpoint = function() {
| + | |
- | // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools
| + | |
- | // it is fired even if dragging has occurred, in which case we would blow away a perfectly
| + | |
- | // legitimate endpoint, were it not for this check. the flag is set after adding an
| + | |
- | // endpoint and cleared in a drag listener we set in the dragOptions above.
| + | |
- | if(endpointAddedButNoDragYet) {
| + | |
- | endpointAddedButNoDragYet = false;
| + | |
- | _currentInstance.deleteEndpoint(ep);
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint);
| + | |
- | _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint);
| + | |
- |
| + | |
- | // and then trigger its mousedown event, which will kick off a drag, which will start dragging
| + | |
- | // a new connection from this endpoint.
| + | |
- | _currentInstance.trigger(ep.canvas, "mousedown", e);
| + | |
- | | + | |
- | jsPlumbUtil.consume(e);
| + | |
- |
| + | |
- | }.bind(this);
| + | |
- |
| + | |
- | // register this on jsPlumb so that it can be cleared by a reset.
| + | |
- | this.registerListener(_el, "mousedown", mouseDownListener);
| + | |
- | this.sourceEndpointDefinitions[idToRegisterAgainst].trigger = mouseDownListener;
| + | |
- | | + | |
- | // lastly, if a filter was provided, set it as a dragFilter on the element,
| + | |
- | // to prevent the element drag function from kicking in when we want to
| + | |
- | // drag a new connection
| + | |
- | if (p.filter && jsPlumbUtil.isString(p.filter)) {
| + | |
- | _currentInstance.setDragFilter(_el, p.filter);
| + | |
- | }
| + | |
- | }.bind(this);
| + | |
- |
| + | |
- | el = _convertYUICollection(el);
| + | |
- |
| + | |
- | var inputs = el.length && el.constructor != String ? el : [ el ];
| + | |
- | for (var i = 0, ii = inputs.length; i < ii; i++) {
| + | |
- | _doOne(_info(inputs[i]));
| + | |
- | }
| + | |
- | | + | |
- | return this;
| + | |
- | }; | + | |
- |
| + | |
- | // see api docs
| + | |
- | this.unmakeSource = function(el, doNotClearArrays) {
| + | |
- | var info = _info(el),
| + | |
- | mouseDownListener = this.sourceEndpointDefinitions[info.id].trigger;
| + | |
- |
| + | |
- | if (mouseDownListener)
| + | |
- | _currentInstance.unregisterListener(info.el, "mousedown", mouseDownListener);
| + | |
- | | + | |
- | if (!doNotClearArrays) {
| + | |
- | delete this.sourceEndpointDefinitions[info.id];
| + | |
- | }
| + | |
- | | + | |
- | return this;
| + | |
- | }; | + | |
- | | + | |
- | // see api docs
| + | |
- | this.unmakeEverySource = function() {
| + | |
- | for (var i in this.sourceEndpointDefinitions)
| + | |
- | _currentInstance.unmakeSource(i, true);
| + | |
- | | + | |
- | this.sourceEndpointDefinitions = {};
| + | |
- | return this;
| + | |
- | };
| + | |
- |
| + | |
- | // see api docs
| + | |
- | this.unmakeEveryTarget = function() {
| + | |
- | for (var i in this.targetEndpointDefinitions)
| + | |
- | _currentInstance.unmakeTarget(i, true);
| + | |
- |
| + | |
- | this.targetEndpointDefinitions = {};
| + | |
- | return this;
| + | |
- | };
| + | |
- | | + | |
- | // does the work of setting a source enabled or disabled.
| + | |
- | var _setEnabled = function(type, el, state, toggle) {
| + | |
- | var a = type == "source" ? this.sourceEndpointDefinitions : this.targetEndpointDefinitions;
| + | |
- | el = _convertYUICollection(el);
| + | |
- | | + | |
- | if (_ju.isString(el)) a[el].enabled = toggle ? !a[el].enabled : state;
| + | |
- | else if (el.length) {
| + | |
- | for (var i = 0, ii = el.length; i < ii; i++) {
| + | |
- | var info = _info(el[i]);
| + | |
- | if (a[info.id])
| + | |
- | a[info.id].enabled = toggle ? !a[info.id].enabled : state;
| + | |
- | }
| + | |
- | }
| + | |
- | // otherwise a DOM element
| + | |
- | else {
| + | |
- | var id = _info(el).id;
| + | |
- | a[id].enabled = toggle ? !a[id].enabled : state;
| + | |
- | }
| + | |
- | return this;
| + | |
- | }.bind(this);
| + | |
- |
| + | |
- | var _first = function(el, fn) {
| + | |
- | el = _convertYUICollection(el);
| + | |
- | if (_ju.isString(el) || !el.length) | + | |
- | return fn.apply(this, [ el ]); | + | |
- | else if (el.length)
| + | |
- | return fn.apply(this, [ el[0] ]);
| + | |
- |
| + | |
- | }.bind(this);
| + | |
- | | + | |
- | this.toggleSourceEnabled = function(el) {
| + | |
- | _setEnabled("source", el, null, true);
| + | |
- | return this.isSourceEnabled(el);
| + | |
- | };
| + | |
- | | + | |
- | this.setSourceEnabled = function(el, state) { return _setEnabled("source", el, state); };
| + | |
- | this.isSource = function(el) {
| + | |
- | return _first(el, function(_el) {
| + | |
- | return this.sourceEndpointDefinitions[_info(_el).id] != null;
| + | |
- | });
| + | |
- | };
| + | |
- | this.isSourceEnabled = function(el) {
| + | |
- | return _first(el, function(_el) {
| + | |
- | var sep = this.sourceEndpointDefinitions[_info(_el).id];
| + | |
- | return sep && sep.enabled === true;
| + | |
- | });
| + | |
- | };
| + | |
- | | + | |
- | this.toggleTargetEnabled = function(el) {
| + | |
- | _setEnabled("target", el, null, true);
| + | |
- | return this.isTargetEnabled(el);
| + | |
- | };
| + | |
- |
| + | |
- | this.isTarget = function(el) {
| + | |
- | return _first(el, function(_el) {
| + | |
- | return this.targetEndpointDefinitions[_info(_el).id] != null;
| + | |
- | });
| + | |
- | };
| + | |
- | this.isTargetEnabled = function(el) {
| + | |
- | return _first(el, function(_el) {
| + | |
- | var tep = this.targetEndpointDefinitions[_info(_el).id];
| + | |
- | return tep && tep.enabled === true;
| + | |
- | });
| + | |
- | };
| + | |
- | this.setTargetEnabled = function(el, state) { return _setEnabled("target", el, state); };
| + | |
- | | + | |
- | // --------------------- end makeSource/makeTarget ---------------------------------------------- | + | |
- |
| + | |
- | this.ready = function(fn) { | + | |
- | _currentInstance.bind("ready", fn);
| + | |
- | };
| + | |
- | | + | |
- | // repaint some element's endpoints and connections | + | |
- | this.repaint = function(el, ui, timestamp) {
| + | |
- | // support both lists...
| + | |
- | if (typeof el == 'object' && el.length)
| + | |
- | for ( var i = 0, ii = el.length; i < ii; i++) {
| + | |
- | _draw(el[i], ui, timestamp);
| + | |
- | }
| + | |
- | else // ...and single strings.
| + | |
- | _draw(el, ui, timestamp);
| + | |
- |
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- | | + | |
- | // repaint every endpoint and connection.
| + | |
- | this.repaintEverything = function(clearEdits) {
| + | |
- | // TODO this timestamp causes continuous anchors to not repaint properly.
| + | |
- | // fix this. do not just take out the timestamp. it runs a lot faster with
| + | |
- | // the timestamp included.
| + | |
- | //var timestamp = null;
| + | |
- | var timestamp = _timestamp();
| + | |
- | for ( var elId in endpointsByElement) {
| + | |
- | _draw(elId, null, timestamp, clearEdits);
| + | |
- | }
| + | |
- | return this;
| + | |
- | }; | + | |
- | | + | |
- | this.removeAllEndpoints = function(el, recurse) {
| + | |
- | var _one = function(_el) {
| + | |
- | var info = _info(_el),
| + | |
- | ebe = endpointsByElement[info.id],
| + | |
- | i, ii;
| + | |
- | | + | |
- | if (ebe) {
| + | |
- | for ( i = 0, ii = ebe.length; i < ii; i++)
| + | |
- | _currentInstance.deleteEndpoint(ebe[i]);
| + | |
- | }
| + | |
- | delete endpointsByElement[info.id];
| + | |
- |
| + | |
- | if (recurse) {
| + | |
- | if (info.el && info.el.nodeType != 3 && info.el.nodeType != 8 ) {
| + | |
- | for ( i = 0, ii = info.el.childNodes.length; i < ii; i++) {
| + | |
- | _one(info.el.childNodes[i]);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- |
| + | |
- | };
| + | |
- | _one(el);
| + | |
- | return this;
| + | |
- | };
| + | |
- |
| + | |
- | /**
| + | |
- | * Remove the given element, including cleaning up all endpoints registered for it.
| + | |
- | * This is exposed in the public API but also used internally by jsPlumb when removing the
| + | |
- | * element associated with a connection drag.
| + | |
- | */
| + | |
- | this.remove = function(el, doNotRepaint) {
| + | |
- | var info = _info(el);
| + | |
- | _currentInstance.doWhileSuspended(function() {
| + | |
- | _currentInstance.removeAllEndpoints(info.id, true);
| + | |
- | _currentInstance.dragManager.elementRemoved(info.id);
| + | |
- | delete floatingConnections[info.id];
| + | |
- | _currentInstance.anchorManager.clearFor(info.id);
| + | |
- | _currentInstance.anchorManager.removeFloatingConnection(info.id);
| + | |
- | }, doNotRepaint === false);
| + | |
- | if (info.el) _currentInstance.removeElement(info.el);
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- | | + | |
- | var _registeredListeners = {}, | + | |
- | _unbindRegisteredListeners = function() {
| + | |
- | for (var i in _registeredListeners) {
| + | |
- | for (var j = 0, jj = _registeredListeners[i].length; j < jj; j++) {
| + | |
- | var info = _registeredListeners[i][j];
| + | |
- | _currentInstance.off(info.el, info.event, info.listener);
| + | |
- | }
| + | |
- | }
| + | |
- | _registeredListeners = {};
| + | |
- | };
| + | |
- | | + | |
- | // internal register listener method. gives us a hook to clean things up
| + | |
- | // with if the user calls jsPlumb.reset.
| + | |
- | this.registerListener = function(el, type, listener) {
| + | |
- | _currentInstance.on(el, type, listener);
| + | |
- | jsPlumbUtil.addToList(_registeredListeners, type, {el:el, event:type, listener:listener});
| + | |
- | };
| + | |
- | | + | |
- | this.unregisterListener = function(el, type, listener) {
| + | |
- | _currentInstance.off(el, type, listener);
| + | |
- | jsPlumbUtil.removeWithFunction(_registeredListeners, function(rl) {
| + | |
- | return rl.type == type && rl.listener == listener;
| + | |
- | });
| + | |
- | };
| + | |
- |
| + | |
- | this.reset = function() {
| + | |
- | _currentInstance.deleteEveryEndpoint(); | + | |
- | _currentInstance.unbind();
| + | |
- | this.targetEndpointDefinitions = {};
| + | |
- | this.sourceEndpointDefinitions = {};
| + | |
- | connections.splice(0);
| + | |
- | _unbindRegisteredListeners();
| + | |
- | _currentInstance.anchorManager.reset();
| + | |
- | if (!jsPlumbAdapter.headless)
| + | |
- | _currentInstance.dragManager.reset();
| + | |
- | };
| + | |
- |
| + | |
- | | + | |
- | this.setDefaultScope = function(scope) {
| + | |
- | DEFAULT_SCOPE = scope;
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- | | + | |
- | // sets whether or not some element should be currently draggable.
| + | |
- | this.setDraggable = _setDraggable;
| + | |
- | | + | |
- | // sets the id of some element, changing whatever we need to to keep track.
| + | |
- | this.setId = function(el, newId, doNotSetAttribute) {
| + | |
- | //
| + | |
- | var id;
| + | |
- | | + | |
- | if (jsPlumbUtil.isString(el)) {
| + | |
- | id = el;
| + | |
- | }
| + | |
- | else {
| + | |
- | el = this.getDOMElement(el);
| + | |
- | id = this.getId(el);
| + | |
- | }
| + | |
- | | + | |
- | var sConns = this.getConnections({source:id, scope:'*'}, true),
| + | |
- | tConns = this.getConnections({target:id, scope:'*'}, true);
| + | |
- | | + | |
- | newId = "" + newId;
| + | |
- | | + | |
- | if (!doNotSetAttribute) {
| + | |
- | el = this.getDOMElement(id);
| + | |
- | this.setAttribute(el, "id", newId);
| + | |
- | }
| + | |
- | else
| + | |
- | el = this.getDOMElement(newId);
| + | |
- | | + | |
- | endpointsByElement[newId] = endpointsByElement[id] || [];
| + | |
- | for (var i = 0, ii = endpointsByElement[newId].length; i < ii; i++) {
| + | |
- | endpointsByElement[newId][i].setElementId(newId);
| + | |
- | endpointsByElement[newId][i].setReferenceElement(el);
| + | |
- | }
| + | |
- | delete endpointsByElement[id];
| + | |
- | | + | |
- | this.anchorManager.changeId(id, newId);
| + | |
- | if (this.dragManager) this.dragManager.changeId(id, newId);
| + | |
- | | + | |
- | var _conns = function(list, epIdx, type) {
| + | |
- | for (var i = 0, ii = list.length; i < ii; i++) {
| + | |
- | list[i].endpoints[epIdx].setElementId(newId);
| + | |
- | list[i].endpoints[epIdx].setReferenceElement(el);
| + | |
- | list[i][type + "Id"] = newId;
| + | |
- | list[i][type] = el;
| + | |
- | }
| + | |
- | };
| + | |
- | _conns(sConns, 0, "source");
| + | |
- | _conns(tConns, 1, "target");
| + | |
- | | + | |
- | this.repaint(newId);
| + | |
- | };
| + | |
- | | + | |
- | this.setDebugLog = function(debugLog) {
| + | |
- | log = debugLog;
| + | |
- | };
| + | |
- | | + | |
- | this.setSuspendDrawing = function(val, repaintAfterwards) {
| + | |
- | var curVal = _suspendDrawing;
| + | |
- | _suspendDrawing = val;
| + | |
- | if (val) _suspendedAt = new Date().getTime(); else _suspendedAt = null;
| + | |
- | if (repaintAfterwards) this.repaintEverything();
| + | |
- | return curVal;
| + | |
- | };
| + | |
- | | + | |
- | // returns whether or not drawing is currently suspended.
| + | |
- | this.isSuspendDrawing = function() {
| + | |
- | return _suspendDrawing;
| + | |
- | };
| + | |
- | | + | |
- | // return timestamp for when drawing was suspended.
| + | |
- | this.getSuspendedAt = function() { return _suspendedAt; };
| + | |
- | | + | |
- | this.doWhileSuspended = function(fn, doNotRepaintAfterwards) {
| + | |
- | var _wasSuspended = this.isSuspendDrawing();
| + | |
- | if (!_wasSuspended)
| + | |
- | this.setSuspendDrawing(true);
| + | |
- | try {
| + | |
- | fn();
| + | |
- | }
| + | |
- | catch (e) {
| + | |
- | _ju.log("Function run while suspended failed", e);
| + | |
- | }
| + | |
- | if (!_wasSuspended)
| + | |
- | this.setSuspendDrawing(false, !doNotRepaintAfterwards);
| + | |
- | };
| + | |
- | | + | |
- | this.getOffset = function(elId) { return offsets[elId]; };
| + | |
- | this.getCachedData = _getCachedData;
| + | |
- | this.timestamp = _timestamp;
| + | |
- | this.setRenderMode = function(mode) {
| + | |
- | if (mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new TypeError("Render mode [" + mode + "] not supported");
| + | |
- | renderMode = jsPlumbAdapter.setRenderMode(mode);
| + | |
- | return renderMode;
| + | |
- | };
| + | |
- | this.getRenderMode = function() { return renderMode; };
| + | |
- | this.show = function(el, changeEndpoints) {
| + | |
- | _setVisible(el, "block", changeEndpoints);
| + | |
- | return _currentInstance;
| + | |
- | };
| + | |
- | | + | |
- | // TODO: update this method to return the current state.
| + | |
- | this.toggleVisible = _toggleVisible;
| + | |
- | this.toggleDraggable = _toggleDraggable;
| + | |
- | this.addListener = this.bind;
| + | |
- | | + | |
- | if (!jsPlumbAdapter.headless) {
| + | |
- | _currentInstance.dragManager = jsPlumbAdapter.getDragManager(_currentInstance);
| + | |
- | _currentInstance.recalculateOffsets = _currentInstance.dragManager.updateOffsets;
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | jsPlumbUtil.extend(jsPlumbInstance, jsPlumbUtil.EventGenerator, {
| + | |
- | setAttribute : function(el, a, v) {
| + | |
- | this.setAttribute(el, a, v);
| + | |
- | },
| + | |
- | getAttribute : function(el, a) {
| + | |
- | return this.getAttribute(jsPlumb.getDOMElement(el), a);
| + | |
- | },
| + | |
- | registerConnectionType : function(id, type) {
| + | |
- | this._connectionTypes[id] = jsPlumb.extend({}, type);
| + | |
- | },
| + | |
- | registerConnectionTypes : function(types) {
| + | |
- | for (var i in types)
| + | |
- | this._connectionTypes[i] = jsPlumb.extend({}, types[i]);
| + | |
- | },
| + | |
- | registerEndpointType : function(id, type) {
| + | |
- | this._endpointTypes[id] = jsPlumb.extend({}, type);
| + | |
- | },
| + | |
- | registerEndpointTypes : function(types) {
| + | |
- | for (var i in types)
| + | |
- | this._endpointTypes[i] = jsPlumb.extend({}, types[i]);
| + | |
- | },
| + | |
- | getType : function(id, typeDescriptor) {
| + | |
- | return typeDescriptor === "connection" ? this._connectionTypes[id] : this._endpointTypes[id];
| + | |
- | },
| + | |
- | setIdChanged : function(oldId, newId) {
| + | |
- | this.setId(oldId, newId, true);
| + | |
- | },
| + | |
- | // set parent: change the parent for some node and update all the registrations we need to.
| + | |
- | setParent : function(el, newParent) {
| + | |
- | var _el = this.getElementObject(el),
| + | |
- | _dom = this.getDOMElement(_el),
| + | |
- | _id = this.getId(_dom),
| + | |
- | _pel = this.getElementObject(newParent),
| + | |
- | _pdom = this.getDOMElement(_pel),
| + | |
- | _pid = this.getId(_pdom);
| + | |
- | | + | |
- | _dom.parentNode.removeChild(_dom);
| + | |
- | _pdom.appendChild(_dom);
| + | |
- | this.dragManager.setParent(_el, _id, _pel, _pid);
| + | |
- | },
| + | |
- | /**
| + | |
- | * gets the size for the element, in an array : [ width, height ].
| + | |
- | */
| + | |
- | getSize : function(el) {
| + | |
- | return [ el.offsetWidth, el.offsetHeight ];
| + | |
| }, | | }, |
- | getWidth : function(el) { | + | removeFromDragSelection:function(spec) { |
- | return el.offsetWidth; | + | _getDragManager(this).deselect(spec); |
| }, | | }, |
- | getHeight : function(el) { | + | clearDragSelection:function() { |
- | return el.offsetHeight; | + | _getDragManager(this).deselectAll(); |
| }, | | }, |
- | extend : function(o1, o2, names) { | + | // EVENTS |
- | var i; | + | trigger : function(el, event, originalEvent) { |
- | if (names) {
| + | _getEventManager(this).trigger(el, event, originalEvent); |
- | for (i = 0; i < names.length; i++)
| + | }, |
- | o1[names[i]] = o2[names[i]];
| + | getOriginalEvent : function(e) { return e; }, |
- | } | + | on : function(el, event, callback) { |
- | else
| + | // TODO: here we would like to map the tap event if we know its |
- | for (i in o2) o1[i] = o2[i];
| + | // an internal bind to a click. we have to know its internal because only |
- | return o1; | + | // then can we be sure that the UP event wont be consumed (tap is a synthesized |
| + | // event from a mousedown followed by a mouseup). |
| + | //event = { "click":"tap", "dblclick":"dbltap"}[event] || event; |
| + | _getEventManager(this).on.apply(this, arguments); |
| + | }, |
| + | off : function(el, event, callback) { |
| + | _getEventManager(this).off.apply(this, arguments); |
| } | | } |
- | }, jsPlumbAdapter);
| + | }); |
| | | |
- | // --------------------- static instance + AMD registration -------------------------------------------
| + | var ready = function (f) { |
- | | + | var _do = function() { |
- | // create static instance and assign to window if window exists.
| + | if (/complete|loaded|interactive/.test(document.readyState) && typeof(document.body) != "1.6.4" && document.body != null) |
- | var jsPlumb = new jsPlumbInstance();
| + | f(); |
- | // register on window if defined (lets us run on server)
| + | else |
- | if (typeof window != 'undefined') window.jsPlumb = jsPlumb;
| + | setTimeout(_do, 9); |
- | // add 'getInstance' method to static instance
| + | }; |
- | jsPlumb.getInstance = function(_defaults) {
| + | |
- | var j = new jsPlumbInstance(_defaults);
| + | _do(); |
- | j.init();
| + | }; |
- | return j; | + | ready(jsPlumb.init); |
- | };
| + | |
- | // maybe register static instance as an AMD module, and getInstance method too.
| + | |
- | if ( typeof define === "function") {
| + | |
- | define( "jsplumb", [], function () { return jsPlumb; } ); | + | |
- | define( "jsplumbinstance", [], function () { return jsPlumb.getInstance(); } );
| + | |
- | }
| + | |
- | // CommonJS
| + | |
- | if (typeof exports !== 'undefined') {
| + | |
- | exports.jsPlumb = jsPlumb;
| + | |
- | }
| + | |
- |
| + | |
- |
| + | |
- | // --------------------- end static instance + AMD registration -------------------------------------------
| + | |
| | | |
- | })(); | + | }).call(this); |
var _getDragManager = function(instance, isPlumbedComponent) {
var k = instance[isPlumbedComponent ? "_internalKatavorio" : "_katavorio"],
e = _getEventManager(instance);
if (!k) {
k = new Katavorio( {
bind:e.on,
unbind:e.off,
getSize:jsPlumb.getSize,
getPosition:function(el) {
var o = jsPlumbAdapter.getOffset(el, instance);
return [o.left, o.top];
},
setPosition:function(el, xy) {
el.style.left = xy[0] + "px";
el.style.top = xy[1] + "px";
},
addClass:jsPlumbAdapter.addClass,
removeClass:jsPlumbAdapter.removeClass,
intersects:Biltong.intersects,
indexOf:jsPlumbUtil.indexOf,
css:{
noSelect : instance.dragSelectClass,
droppable:"jsplumb-droppable",
draggable:"jsplumb-draggable",
drag:"jsplumb-drag",
selected:"jsplumb-drag-selected",
active:"jsplumb-drag-active",
hover:"jsplumb-drag-hover"
}
});
instance[isPlumbedComponent ? "_internalKatavorio" : "_katavorio"] = k;
instance.bind("zoom", k.setZoom);
}
return k;
};
var _getEventManager = function(instance) {
var e = instance._mottle;
if (!e) {
e = instance._mottle = new Mottle();
}
return e;
};
var _animProps = function(o, p) {
var _one = function(pName) {
if (p[pName]) {
if (jsPlumbUtil.isString(p[pName])) {
var m = p[pName].match(/-=/) ? -1 : 1,
v = p[pName].substring(2);
return o[pName] + (m * v);
}
else return p[pName];
}
else
return o[pName];
};
return [ _one("left"), _one("top") ];
};
getDOMElement:function(el) {
if (el == null) return null;
// here we pluck the first entry if el was a list of entries.
// this is not my favourite thing to do, but previous versions of
// jsplumb supported jquery selectors, and it is possible a selector
// will be passed in here.
el = typeof el === "string" ? el : el.length != null ? el[0] : el;
return typeof el === "string" ? document.getElementById(el) : el;
},
getElementObject:function(el) { return el; },
removeElement : function(element) {
_getDragManager(this).elementRemoved(element);
_getEventManager(this).remove(element);
},
//
// this adapter supports a rudimentary animation function. no easing is supported. only
// left/top properties are supported. property delta args are expected to be in the form
//
// +=x.xxxx
//
// or
//
// -=x.xxxx
//
doAnimate:function(el, properties, options) {
options = options || {};
var o = jsPlumbAdapter.getOffset(el, this),
ap = _animProps(o, properties),
ldist = ap[0] - o.left,
tdist = ap[1] - o.top,
d = options.duration || 250,
step = 15, steps = d / step,
linc = (step / d) * ldist,
tinc = (step / d) * tdist,
idx = 0,
int = setInterval(function() {
jsPlumbAdapter.setPosition(el, {
left:o.left + (linc * (idx + 1)),
top:o.top + (tinc * (idx + 1))
});
if (options.step != null) options.step();
idx++;
if (idx >= steps) {
window.clearInterval(int);
if (options.complete != null) options.complete();
}
}, step);
},
getSelector:function(ctx, spec) {
var sel = null;
if (arguments.length == 1) {
sel = ctx.nodeType != null ? ctx : document.querySelectorAll(ctx);
}
else
sel = ctx.querySelectorAll(spec);
return sel;
},
// DRAG/DROP
destroyDraggable:function(el) {
_getDragManager(this).destroyDraggable(el);
},
destroyDroppable:function(el) {
_getDragManager(this).destroyDroppable(el);
},
initDraggable : function(el, options, isPlumbedComponent) {
_getDragManager(this, isPlumbedComponent).draggable(el, options);
},
initDroppable : function(el, options, isPlumbedComponent) {
_getDragManager(this, isPlumbedComponent).droppable(el, options);
},
isAlreadyDraggable : function(el) { return el._katavorioDrag != null; },
isDragSupported : function(el, options) { return true; },
isDropSupported : function(el, options) { return true; },
getDragObject : function(eventArgs) { return eventArgs[0].drag.getDragElement(); },
getDragScope : function(el) {
return el._katavorioDrag && el._katavorioDrag.scopes.join(" ") || "";
},
getDropEvent : function(args) { return args[0].e; },
getDropScope : function(el) {
return el._katavorioDrop && el._katavorioDrop.scopes.join(" ") || "";
},
getUIPosition : function(eventArgs, zoom) {
return {
left:eventArgs[0].pos[0],
top:eventArgs[0].pos[1]
};
},
isDragFilterSupported:function() { return true; },
setDragFilter : function(el, filter) {
if (el._katavorioDrag) {
el._katavorioDrag.setFilter(filter);
}
},
setElementDraggable : function(el, draggable) {
el = jsPlumb.getDOMElement(el);
if (el._katavorioDrag)
el._katavorioDrag.setEnabled(draggable);
},
setDragScope : function(el, scope) {
if (el._katavorioDrag)
el._katavorioDrag.k.setDragScope(el, scope);
},
dragEvents : {
'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step',
'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete'
},
animEvents:{
'step':"step", 'complete':'complete'
},
stopDrag : function(el) {
if (el._katavorioDrag)
el._katavorioDrag.abort();
// MULTIPLE ELEMENT DRAG
// these methods are unique to this adapter, because katavorio
// supports dragging multiple elements.
addToDragSelection:function(spec) {
_getDragManager(this).select(spec);
},
removeFromDragSelection:function(spec) {
_getDragManager(this).deselect(spec);
},
clearDragSelection:function() {
_getDragManager(this).deselectAll();
},
// EVENTS
trigger : function(el, event, originalEvent) {
_getEventManager(this).trigger(el, event, originalEvent);
},
getOriginalEvent : function(e) { return e; },
on : function(el, event, callback) {
// TODO: here we would like to map the tap event if we know its
// an internal bind to a click. we have to know its internal because only
// then can we be sure that the UP event wont be consumed (tap is a synthesized
// event from a mousedown followed by a mouseup).
//event = { "click":"tap", "dblclick":"dbltap"}[event] || event;
_getEventManager(this).on.apply(this, arguments);
},
off : function(el, event, callback) {
_getEventManager(this).off.apply(this, arguments);
}
});
var ready = function (f) {
var _do = function() {
if (/complete|loaded|interactive/.test(document.readyState) && typeof(document.body) != "1.6.4" && document.body != null)
f();
else
setTimeout(_do, 9);
};