X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=portal%2Fstatic%2Funbound_reservation_static%2Fsrc%2Fendpoint.js;fp=portal%2Fstatic%2Funbound_reservation_static%2Fsrc%2Fendpoint.js;h=8da35a593b13506c1065e688cb0ecfe9380cd039;hb=729a9dbb380b51a217194ba2a4e5978186fe50b0;hp=0000000000000000000000000000000000000000;hpb=c4bd5da6e2630eddf1262aa8d808dbb48b097d53;p=unfold.git diff --git a/portal/static/unbound_reservation_static/src/endpoint.js b/portal/static/unbound_reservation_static/src/endpoint.js new file mode 100644 index 00000000..8da35a59 --- /dev/null +++ b/portal/static/unbound_reservation_static/src/endpoint.js @@ -0,0 +1,997 @@ + +;(function() { + + // create the drag handler for a connection + var _makeConnectionDragHandler = function(placeholder, _jsPlumb) { + var stopped = false; + return { + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments, _jsPlumb.getZoom()); + + if (placeholder.element) { + jsPlumb.CurrentLibrary.setOffset(placeholder.element, _ui); + _jsPlumb.repaint(placeholder.element, _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + // creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. + var _makeDraggablePlaceholder = function(placeholder, parent, _jsPlumb) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = jsPlumb.CurrentLibrary.getElementObject(n); + jsPlumb.CurrentLibrary.appendElement(n, parent); + var id = _jsPlumb.getId(n); + _jsPlumb.updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = n; + }; + + // create a floating endpoint (for drag connections) + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement, _jsPlumb, _newEndpoint) { + var floatingAnchor = new jsPlumb.FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas, jsPlumbInstance:_jsPlumb }); + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + var typeParameters = [ "connectorStyle", "connectorHoverStyle", "connectorOverlays", + "connector", "connectionType", "connectorClass", "connectorHoverClass" ]; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(ep, elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < ep.connections.length; i++) { + if (ep.connections[i].sourceId == elementWithPrecedence || ep.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return ep.connections[idx]; + }; + + var findConnectionIndex = function(conn, ep) { + return jsPlumbUtil.findWithFunction(ep.connections, function(c) { return c.id == conn.id; }); + }; + + jsPlumb.Endpoint = function(params) { + var _jsPlumb = params._jsPlumb, + jpcl = jsPlumb.CurrentLibrary, + _att = jsPlumbAdapter.getAttribute, + _gel = jpcl.getElementObject, + _dom = jpcl.getDOMElement, + _ju = jsPlumbUtil, + _newConnection = params.newConnection, + _newEndpoint = params.newEndpoint, + _finaliseConnection = params.finaliseConnection, + _fireDetachEvent = params.fireDetachEvent, + _fireMoveEvent = params.fireMoveEvent, + floatingConnections = params.floatingConnections; + + this.idPrefix = "_jsplumb_e_"; + this.defaultLabelLocation = [ 0.5, 0.5 ]; + this.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + OverlayCapableJsPlumbUIComponent.apply(this, arguments); + +// TYPE + + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + maxConnections:this._jsPlumb.instance.Defaults.MaxConnections, + paintStyle:this._jsPlumb.instance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, + endpoint:this._jsPlumb.instance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint, + hoverPaintStyle:this._jsPlumb.instance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle, + overlays:this._jsPlumb.instance.Defaults.EndpointOverlays || jsPlumb.Defaults.EndpointOverlays, + connectorStyle:params.connectorStyle, + connectorHoverStyle:params.connectorHoverStyle, + connectorClass:params.connectorClass, + connectorHoverClass:params.connectorHoverClass, + connectorOverlays:params.connectorOverlays, + connector:params.connector, + connectorTooltip:params.connectorTooltip + }; + }; + +// END TYPE + + this._jsPlumb.enabled = !(params.enabled === false); + this._jsPlumb.visible = true; + this.element = _dom(params.source); + this._jsPlumb.uuid = params.uuid; + this._jsPlumb.floatingEndpoint = null; + var inPlaceCopy = null; + if (this._jsPlumb.uuid) params.endpointsByUUID[this._jsPlumb.uuid] = this; + this.elementId = params.elementId; + + this._jsPlumb.connectionCost = params.connectionCost; + this._jsPlumb.connectionsDirected = params.connectionsDirected; + this._jsPlumb.currentAnchorClass = ""; + this._jsPlumb.events = {}; + + var _updateAnchorClass = function() { + jpcl.removeClass(this.element, _jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass); + this.removeClass(_jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass); + this._jsPlumb.currentAnchorClass = this.anchor.getCssClass(); + this.addClass(_jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass); + jpcl.addClass(this.element, _jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass); + }.bind(this); + + this.setAnchor = function(anchorParams, doNotRepaint) { + this._jsPlumb.instance.continuousAnchorFactory.clear(this.elementId); + this.anchor = this._jsPlumb.instance.makeAnchor(anchorParams, this.elementId, _jsPlumb); + _updateAnchorClass(); + this.anchor.bind("anchorChanged", function(currentAnchor) { + this.fire("anchorChanged", {endpoint:this, anchor:currentAnchor}); + _updateAnchorClass(); + }.bind(this)); + if (!doNotRepaint) + this._jsPlumb.instance.repaint(this.elementId); + return this; + }; + + var anchorParamsToUse = params.anchor ? params.anchor : params.anchors ? params.anchors : (_jsPlumb.Defaults.Anchor || "Top"); + this.setAnchor(anchorParamsToUse, true); + + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (this.connections.length > 0) + this.connections[0].setHover(state, false); + else + this.setHover(state); + }.bind(this); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + this._jsPlumb.instance.anchorManager.add(this, this.elementId); + + this.setEndpoint = function(ep) { + + if (this.endpoint != null) { + this.endpoint.cleanup(); + this.endpoint.destroy(); + } + + var _e = function(t, p) { + var rm = _jsPlumb.getRenderMode(); + if (jsPlumb.Endpoints[rm][t]) return new jsPlumb.Endpoints[rm][t](p); + if (!_jsPlumb.Defaults.DoNotThrowErrors) + throw { msg:"jsPlumb: unknown endpoint type '" + t + "'" }; + }; + + var endpointArgs = { + _jsPlumb:this._jsPlumb.instance, + cssClass:params.cssClass, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:this + }; + if (_ju.isString(ep)) + this.endpoint = _e(ep, endpointArgs); + else if (_ju.isArray(ep)) { + endpointArgs = _ju.merge(ep[1], endpointArgs); + this.endpoint = _e(ep[0], endpointArgs); + } + else { + this.endpoint = ep.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + this.endpoint.clone = function() { + // TODO this, and the code above, can be refactored to be more dry. + if (_ju.isString(ep)) + return _e(ep, endpointArgs); + else if (_ju.isArray(ep)) { + endpointArgs = _ju.merge(ep[1], endpointArgs); + return _e(ep[0], endpointArgs); + } + }.bind(this); + + this.type = this.endpoint.type; + // bind listeners from endpoint to self, with the internal hover function defined above. + this.bindListeners(this.endpoint, this, internalHover); + }; + + this.setEndpoint(params.endpoint || _jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot"); + this.setPaintStyle(params.paintStyle || params.style || _jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || _jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle, true); + this._jsPlumb.paintStyleInUse = this.getPaintStyle(); + + _ju.copyValues(typeParameters, params, this); + + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + this._jsPlumb.maxConnections = params.maxConnections || _jsPlumb.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + this.canvas = this.endpoint.canvas; + // add anchor class (need to do this on construction because we set anchor first) + this.addClass(_jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass); + jpcl.addClass(this.element, _jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass); + this.connections = params.connections || []; + this.connectorPointerEvents = params["connector-pointer-events"]; + + this.scope = params.scope || _jsPlumb.getDefaultScope(); + this.timestamp = null; + this.reattachConnections = params.reattach || _jsPlumb.Defaults.ReattachConnections; + this.connectionsDetachable = _jsPlumb.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + this.connectionsDetachable = false; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + if (params.onMaxConnections) + this.bind("maxConnections", params.onMaxConnections); + + // + // add a connection. not part of public API. + // + this.addConnection = function(connection) { + this.connections.push(connection); + this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass); + this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass); + }; + + this.detachFromConnection = function(connection, idx) { + idx = idx == null ? findConnectionIndex(connection, this) : idx; + if (idx >= 0) { + this.connections.splice(idx, 1); + this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass); + this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass); + } + }; + + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent, endpointBeingDeleted, connectionIndex) { + + var idx = connectionIndex == null ? findConnectionIndex(connection, this) : connectionIndex, + actuallyDetached = false; + fireEvent = (fireEvent !== false); + + if (idx >= 0) { + if (forceDetach || connection._forceDetach || (connection.isDetachable() && connection.isDetachAllowed(connection) && this.isDetachAllowed(connection) )) { + + //connection.setHover(false); + + _jsPlumb.deleteObject({ + connection:connection, + fireEvent:(!ignoreTarget && fireEvent), + originalEvent:originalEvent + }); + actuallyDetached = true; + } + } + return actuallyDetached; + }; + + this.detachAll = function(fireEvent, originalEvent) { + while (this.connections.length > 0) { + // TODO this could pass the index in to the detach method to save some time (index will always be zero in this while loop) + // TODO now it defaults to fireEvent true. will that mess with things? + this.detach(this.connections[0], false, true, fireEvent !== false, originalEvent, this, 0); + } + return this; + }; + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < this.connections.length; i++) { + if (this.connections[i].endpoints[1] == targetEndpoint || this.connections[i].endpoints[0] == targetEndpoint) { + c.push(this.connections[i]); + } + } + for ( var j = 0; j < c.length; j++) { + this.detach(c[j], false, true, fireEvent, originalEvent); + } + return this; + }; + + this.getElement = function() { + return this.element; + }; + + // container not supported in 1.5.5; you cannot change the container once it is set. + // it might come back int a future release. + this.setElement = function(el/*, container*/) { + var parentId = this._jsPlumb.instance.getId(el), + curId = this.elementId; + // remove the endpoint from the list for the current endpoint's element + _ju.removeWithFunction(params.endpointsByElement[this.elementId], function(e) { + return e.id == this.id; + }.bind(this)); + this.element = _dom(el); + this.elementId = _jsPlumb.getId(this.element); + _jsPlumb.anchorManager.rehomeEndpoint(this, curId, this.element); + _jsPlumb.dragManager.endpointAdded(this.element); + _ju.addToList(params.endpointsByElement, parentId, this); + return this; + }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = this.anchor.getCurrentLocation({element:this}), + o = this.anchor.getOrientation(this), + acc = this.anchor.getCssClass(), + inPlaceAnchor = { + bind:function() { }, + compute:function() { return [ loc[0], loc[1] ]; }, + getCurrentLocation : function() { return [ loc[0], loc[1] ]; }, + getOrientation:function() { return o; }, + getCssClass:function() { return acc; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : this.element, + paintStyle : this.getPaintStyle(), + endpoint : params.hideOnDrag ? "Blank" : this.endpoint, + _transient:true, + scope:this.scope + }); + }; + + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return this.anchor != null && this.anchor.isFloating; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = this.connections[0]; + if (this.isTarget && candidate) return candidate; + else { + return (this.connections.length < this._jsPlumb.maxConnections) || this._jsPlumb.maxConnections == -1 ? null : candidate; + } + }; + + this.setStyle = this.setPaintStyle; + + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, recalc = !(params.recalc === false); + if (!timestamp || this.timestamp !== timestamp) { + + // TODO check: is this is a safe performance enhancement? + var info = _jsPlumb.updateOffset({ elId:this.elementId, timestamp:timestamp/*, recalc:recalc*/ }); + + var xy = params.offset ? params.offset.o : info.o; + if(xy != null) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || info.s, + anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : this, timestamp : timestamp }; + if (recalc && this.anchor.isDynamic && this.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(this, params.elementWithPrecedence), + oIdx = c.endpoints[0] == this ? 1 : 0, + oId = oIdx === 0 ? c.sourceId : c.targetId, + oInfo = _jsPlumb.getCachedData(oId), + oOffset = oInfo.o, oWH = oInfo.s; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = this.anchor.compute(anchorParams); + } + + this.endpoint.compute(ap, this.anchor.getOrientation(this), this._jsPlumb.paintStyleInUse, connectorPaintStyle || this.paintStyleInUse); + this.endpoint.paint(this._jsPlumb.paintStyleInUse, this.anchor); + this.timestamp = timestamp; + + // paint overlays + for ( var i = 0; i < this._jsPlumb.overlays.length; i++) { + var o = this._jsPlumb.overlays[i]; + if (o.isVisible()) { + this._jsPlumb.overlayPlacements[i] = o.draw(this.endpoint, this._jsPlumb.paintStyleInUse); + o.paint(this._jsPlumb.overlayPlacements[i]); + } + } + } + } + }; + + this.repaint = this.paint; + + var draggingInitialised = false; + this.initDraggable = function() { + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (!draggingInitialised && jpcl.isDragSupported(this.element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo, _jsPlumb); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = this.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!this.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !this.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (this.isSource && this.isFull() && !this.dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jpcl.stopDrag) jpcl.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // clear hover for all connections for this endpoint before continuing. + for (var i = 0; i < this.connections.length; i++) + this.connections[i].setHover(false); + + this.addClass("endpointDrag"); + _jsPlumb.setConnectionBeingDragged(true); + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !this.isFull() && this.isSource) jpc = null; + + _jsPlumb.updateOffset( { elId : this.elementId }); + inPlaceCopy = this.makeInPlaceCopy(); + inPlaceCopy.referenceEndpoint = this; + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, this.parent, _jsPlumb); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _gel(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel, _jsPlumb), + po = _jsPlumb.adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas), + canvasElement = _gel(this.canvas); + + jpcl.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (this.parentAnchor) this.anchor = _jsPlumb.makeAnchor(this.parentAnchor, this.elementId, _jsPlumb); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _jsPlumb.setAttribute(this.canvas, "dragId", placeholderInfo.id); + _jsPlumb.setAttribute(this.canvas, "elId", this.elementId); + + this._jsPlumb.floatingEndpoint = _makeFloatingEndpoint(this.getPaintStyle(), this.anchor, this.endpoint, this.canvas, placeholderInfo.element, _jsPlumb, _newEndpoint); + // TODO we should not know about DOM here. make the library adapter do this (or the + // dom adapter) + this.canvas.style.visibility = "hidden"; + + if (jpc == null) { + this.anchor.locked = true; + this.setHover(false, false); + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : this, + targetEndpoint : this._jsPlumb.floatingEndpoint, + source : this.endpointWillMoveTo || this.element, // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ this.anchor, this._jsPlumb.floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays, + type:this.connectionType, + cssClass:this.connectorClass, + hoverClass:this.connectorHoverClass + }); + jpc.pending = true; // mark this connection as not having been established. + jpc.addClass(_jsPlumb.draggingClass); + this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass); + // fire an event that informs that a connection is being dragged + _jsPlumb.fire("connectionDrag", jpc); + + } else { + existingJpc = true; + jpc.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(ipcoel, false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == this.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + this.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var dragScope = jsPlumb.CurrentLibrary.getDragScope(canvasElement); + _jsPlumb.setAttribute(this.canvas, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jpcl.getDropScope(canvasElement); + jpcl.setDragScope(canvasElement, dropScope); + + // fire an event that informs that a connection is being dragged. we do this before + // replacing the original target with the floating element info. + _jsPlumb.fire("connectionDrag", jpc); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx === 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, canvasElement, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, canvasElement, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx === 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + + // PROVIDE THE SUSPENDED ELEMENT, BE IT A SOURCE OR TARGET (ISSUE 39) + jpc.suspendedElement = jpc.endpoints[anchorIdx].getElement(); + jpc.suspendedElementId = jpc.endpoints[anchorIdx].elementId; + jpc.suspendedElementType = anchorIdx === 0 ? "source" : "target"; + + jpc.suspendedEndpoint.setHover(false); + this._jsPlumb.floatingEndpoint.referenceEndpoint = jpc.suspendedEndpoint; + jpc.endpoints[anchorIdx] = this._jsPlumb.floatingEndpoint; + + jpc.addClass(_jsPlumb.draggingClass); + this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + _jsPlumb.anchorManager.addFloatingConnection(placeholderInfo.id, jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _ju.addToList(params.endpointsByElement, placeholderInfo.id, this._jsPlumb.floatingEndpoint); + // tell jsplumb about it + _jsPlumb.currentlyDragging = true; + }.bind(this); + + var dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents.start, + stopEvent = jpcl.dragEvents.stop, + dragEvent = jpcl.dragEvents.drag; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || this.scope; + dragOptions[startEvent] = _ju.wrap(dragOptions[startEvent], start, false); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent], + function() { + + _jsPlumb.setConnectionBeingDragged(false); + // if no endpoints, jpc already cleaned up. + if (jpc.endpoints != null) { + // get the actual drop event (decode from library args to stop function) + var originalEvent = jpcl.getDropEvent(arguments); + // unlock the other endpoint (if it is dynamic, it would have been locked at drag start) + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx === 0 ? 1 : 0].anchor.locked = false; + // WHY does this need to happen? i suppose because the connection might not get + // deleted. TODO: i dont want to know about css classes inside jsplumb, ideally. + jpc.removeClass(_jsPlumb.draggingClass); + + // if we have the floating endpoint then the connection has not been dropped + // on another endpoint. If it is a new connection we throw it away. If it is an + // existing connection we check to see if we should reattach it, throwing it away + // if not. + if (jpc.endpoints[idx] == this._jsPlumb.floatingEndpoint) { + // 6a. if the connection was an existing one... + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx === 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jpcl.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + // IF the connection should be reattached, or the other endpoint refuses detach, then + // reset the connection to its original state + if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !jpc.endpoints[idx === 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc._forceDetach = null; + jpc._forceReattach = null; + this._jsPlumb.floatingEndpoint.detachFromConnection(jpc); + jpc.suspendedEndpoint.addConnection(jpc); + _jsPlumb.repaint(existingJpcParams[1]); + } + } + } + } + + // remove the element associated with the floating endpoint + // (and its associated floating endpoint and visual artefacts) + _jsPlumb.remove(placeholderInfo.element, false); + // remove the inplace copy + _jsPlumb.remove(inPlaceCopy.canvas, false); + + // makeTargets sets this flag, to tell us we have been replaced and should delete ourself. + if (this.deleteAfterDragStop) { + _jsPlumb.deleteObject({endpoint:this}); + } + else { + if (this._jsPlumb) { + this._jsPlumb.floatingEndpoint = null; + // repaint this endpoint. + // make our canvas visible (TODO: hand off to library; we should not know about DOM) + this.canvas.style.visibility = "visible"; + // unlock our anchor + this.anchor.locked = false; + this.paint({recalc:false}); + } + } + + // although the connection is no longer valid, there are use cases where this is useful. + _jsPlumb.fire("connectionDragStop", jpc, originalEvent); + + // tell jsplumb that dragging is finished. + _jsPlumb.currentlyDragging = false; + + jpc = null; + + }.bind(this)); + + var i = _gel(this.canvas); + jpcl.initDraggable(i, dragOptions, true, _jsPlumb); + + draggingInitialised = true; + } + }; + + // if marked as source or target at create time, init the dragging. + if (this.isSource || this.isTarget) + this.initDraggable(); + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((this.isTarget || forceInit) && jpcl.isDropSupported(this.element)) { + var dropOptions = params.dropOptions || _jsPlumb.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || this.scope; + var dropEvent = jpcl.dragEvents.drop, + overEvent = jpcl.dragEvents.over, + outEvent = jpcl.dragEvents.out, + drop = function() { + + this.removeClass(_jsPlumb.endpointDropAllowedClass); + this.removeClass(_jsPlumb.endpointDropForbiddenClass); + + var originalEvent = jpcl.getDropEvent(arguments), + draggable = _gel(jpcl.getDragObject(arguments)), + id = _jsPlumb.getAttribute(draggable, "dragId"), + elId = _jsPlumb.getAttribute(draggable, "elId"), + scope = _jsPlumb.getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + // if this is a drop back where the connection came from, mark it force rettach and + // return; the stop handler will reattach. without firing an event. + var redrop = jpc.suspendedEndpoint && (jpc.suspendedEndpoint.id == this.id || + this.referenceEndpoint && jpc.suspendedEndpoint.id == this.referenceEndpoint.id) ; + if (redrop) { + jpc._forceReattach = true; + return; + } + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx === 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (this.isFull()) { + this.fire("maxConnections", { + endpoint:this, + connection:jpc, + maxConnections:this._jsPlumb.maxConnections + }, originalEvent); + } + + if (!this.isFull() && !(idx === 0 && !this.isSource) && !(idx == 1 && !this.isTarget) && endpointEnabled) { + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != this.id) { + if (idx === 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_jsPlumb.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx === 0) { + jpc.source = this.element; + jpc.sourceId = this.elementId; + } else { + jpc.target = this.element; + jpc.targetId = this.elementId; + } + +// ------------ wrap the execution path in a function so we can support asynchronous beforeDrop + + // we want to execute this regardless. + var commonFunction = function() { + jpc.floatingAnchorIndex = null; + }; + + var continueFunction = function() { + jpc.pending = false; + + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = this; + this.addConnection(jpc); + + // copy our parameters in to the connection: + var params = this.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + // if not an existing connection and + if (params.draggable) + jsPlumb.CurrentLibrary.initDraggable(this.element, dragOptions, true, _jsPlumb); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + _fireMoveEvent({ + index:idx, + originalSourceId:idx === 0 ? suspendedElementId : jpc.sourceId, + newSourceId:idx === 0 ? this.elementId : jpc.sourceId, + originalTargetId:idx == 1 ? suspendedElementId : jpc.targetId, + newTargetId:idx == 1 ? this.elementId : jpc.targetId, + originalSourceEndpoint:idx === 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + newSourceEndpoint:idx === 0 ? this : jpc.endpoints[0], + originalTargetEndpoint:idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + newTargetEndpoint:idx == 1 ? this : jpc.endpoints[1], + connection:jpc + }, originalEvent); + /* var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _fireDetachEvent({ + source : idx === 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx === 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx === 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent);*/ + } + + // TODO this is like the makeTarget drop code. + if (idx == 1) + _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.suspendedElementId, jpc.targetId, jpc); + else + _jsPlumb.anchorManager.sourceChanged(jpc.suspendedEndpoint.elementId, jpc.sourceId, jpc); + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + // TODO if this is not set to true, then dragging a connection's target to a new + // target causes the connection to be forgotten. however if it IS set to true, then + // the opposite happens: dragging by source causes the connection to get forgotten + // about and then if you delete it jsplumb breaks. + _finaliseConnection(jpc, null, originalEvent/*, true*/); + + commonFunction(); + }.bind(this); + + var dontContinueFunction = function() { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx === 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _jsPlumb.repaint(jpc.sourceId); + jpc._forceDetach = false; + } + + commonFunction(); + }; + +// -------------------------------------- + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && this.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, this); + + if (_doContinue) { + continueFunction(); + } + else { + dontContinueFunction(); + } + } + _jsPlumb.currentlyDragging = false; + } + }.bind(this); + + dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _ju.wrap(dropOptions[overEvent], function() { + var draggable = jpcl.getDragObject(arguments), + id = _jsPlumb.getAttribute(draggable, "dragId"), + _jpc = floatingConnections[id]; + + if (_jpc != null) { + var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex; + // here we should fire the 'over' event if we are a target and this is a new connection, + // or we are the same as the floating endpoint. + var _cont = (this.isTarget && _jpc.floatingAnchorIndex !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id == _jpc.suspendedEndpoint.id); + if (_cont) { + var bb = _jsPlumb.checkCondition("checkDropAllowed", { + sourceEndpoint:_jpc.endpoints[idx], + targetEndpoint:this, + connection:_jpc + }); + this[(bb ? "add" : "remove") + "Class"](_jsPlumb.endpointDropAllowedClass); + this[(bb ? "remove" : "add") + "Class"](_jsPlumb.endpointDropForbiddenClass); + _jpc.endpoints[idx].anchor.over(this.anchor, this); + } + } + }.bind(this)); + + dropOptions[outEvent] = _ju.wrap(dropOptions[outEvent], function() { + var draggable = jpcl.getDragObject(arguments), + id = _jsPlumb.getAttribute( draggable, "dragId"), + _jpc = floatingConnections[id]; + + if (_jpc != null) { + var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex; + var _cont = (this.isTarget && _jpc.floatingAnchorIndex !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id == _jpc.suspendedEndpoint.id); + if (_cont) { + this.removeClass(_jsPlumb.endpointDropAllowedClass); + this.removeClass(_jsPlumb.endpointDropForbiddenClass); + _jpc.endpoints[idx].anchor.out(); + } + } + }.bind(this)); + jpcl.initDroppable(canvas, dropOptions, true, isTransient); + } + }.bind(this); + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_gel(this.canvas), true, !(params._transient || this.anchor.isFloating), this); + + // finally, set type if it was provided + if (params.type) + this.addType(params.type, params.data, _jsPlumb.isSuspendDrawing()); + + return this; + }; + + jsPlumbUtil.extend(jsPlumb.Endpoint, OverlayCapableJsPlumbUIComponent, { + getTypeDescriptor : function() { return "endpoint"; }, + isVisible : function() { return this._jsPlumb.visible; }, + setVisible : function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + this._jsPlumb.visible = v; + if (this.canvas) this.canvas.style.display = v ? "block" : "none"; + this[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < this.connections.length; i++) { + this.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = this === this.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (this.connections[i].endpoints[oIdx].connections.length == 1) this.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }, + getAttachedElements : function() { + return this.connections; + }, + applyType : function(t, doNotRepaint) { + if (t.maxConnections != null) this._jsPlumb.maxConnections = t.maxConnections; + if (t.scope) this.scope = t.scope; + jsPlumbUtil.copyValues(typeParameters, t, this); + if (t.anchor) { + this.anchor = this._jsPlumb.instance.makeAnchor(t.anchor); + } + }, + isEnabled : function() { return this._jsPlumb.enabled; }, + setEnabled : function(e) { this._jsPlumb.enabled = e; }, + cleanup : function() { + jsPlumb.CurrentLibrary.removeClass(this.element, this._jsPlumb.instance.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass); + this.anchor = null; + this.endpoint.cleanup(); + this.endpoint.destroy(); + this.endpoint = null; + // drag/drop + var i = jsPlumb.CurrentLibrary.getElementObject(this.canvas); + jsPlumb.CurrentLibrary.destroyDraggable(i); + jsPlumb.CurrentLibrary.destroyDroppable(i); + }, + setHover : function(h) { + if (this.endpoint && this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) + this.endpoint.setHover(h); + }, + isFull : function() { + return !(this.isFloating() || this._jsPlumb.maxConnections < 1 || this.connections.length < this._jsPlumb.maxConnections); + }, + getConnectionCost : function() { return this._jsPlumb.connectionCost; }, + setConnectionCost : function(c) { + this._jsPlumb.connectionCost = c; + }, + areConnectionsDirected : function() { return this._jsPlumb.connectionsDirected; }, + setConnectionsDirected : function(b) { this._jsPlumb.connectionsDirected = b; }, + setElementId : function(_elId) { + this.elementId = _elId; + this.anchor.elementId = _elId; + }, + setReferenceElement : function(_el) { + this.element = jsPlumb.CurrentLibrary.getDOMElement(_el); + }, + setDragAllowedWhenFull : function(allowed) { + this.dragAllowedWhenFull = allowed; + }, + equals : function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }, + getUuid : function() { + return this._jsPlumb.uuid; + }, + computeAnchor : function(params) { + return this.anchor.compute(params); + } + }); +})(); \ No newline at end of file