reservation plugin - unbound request (unclean
[unfold.git] / portal / static / unbound_reservation_static / src / endpoint.js
1
2 ;(function() {
3         
4     // create the drag handler for a connection
5     var _makeConnectionDragHandler = function(placeholder, _jsPlumb) {
6         var stopped = false;
7         return {
8             drag : function() {
9                 if (stopped) {
10                     stopped = false;
11                     return true;
12                 }
13                 var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments, _jsPlumb.getZoom());
14         
15                 if (placeholder.element) {
16                     jsPlumb.CurrentLibrary.setOffset(placeholder.element, _ui);                    
17                     _jsPlumb.repaint(placeholder.element, _ui);
18                 }
19             },
20             stopDrag : function() {
21                 stopped = true;
22             }
23         };
24     };
25         
26     // creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset.    
27     var _makeDraggablePlaceholder = function(placeholder, parent, _jsPlumb) {
28         var n = document.createElement("div");
29         n.style.position = "absolute";
30         var placeholderDragElement = jsPlumb.CurrentLibrary.getElementObject(n);
31         jsPlumb.CurrentLibrary.appendElement(n, parent);
32         var id = _jsPlumb.getId(n);
33         _jsPlumb.updateOffset( { elId : id });
34         // create and assign an id, and initialize the offset.
35         placeholder.id = id;
36         placeholder.element = n;
37     };
38     
39     // create a floating endpoint (for drag connections)
40     var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement, _jsPlumb, _newEndpoint) {                       
41         var floatingAnchor = new jsPlumb.FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas, jsPlumbInstance:_jsPlumb });
42         //setting the scope here should not be the way to fix that mootools issue.  it should be fixed by not
43         // adding the floating endpoint as a droppable.  that makes more sense anyway!
44         return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" });
45     };
46
47     var typeParameters = [ "connectorStyle", "connectorHoverStyle", "connectorOverlays",
48                 "connector", "connectionType", "connectorClass", "connectorHoverClass" ];
49
50     // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null,
51     // or no connection to it is found, we return the first connection in our list.
52     var findConnectionToUseForDynamicAnchor = function(ep, elementWithPrecedence) {
53         var idx = 0;
54         if (elementWithPrecedence != null) {
55             for (var i = 0; i < ep.connections.length; i++) {
56                 if (ep.connections[i].sourceId == elementWithPrecedence || ep.connections[i].targetId == elementWithPrecedence) {
57                     idx = i;
58                     break;
59                 }
60             }
61         }
62         
63         return ep.connections[idx];
64     };
65
66     var findConnectionIndex = function(conn, ep) {
67         return jsPlumbUtil.findWithFunction(ep.connections, function(c) { return c.id == conn.id; });
68     };
69
70     jsPlumb.Endpoint = function(params) {
71         var _jsPlumb = params._jsPlumb,
72             jpcl = jsPlumb.CurrentLibrary,
73             _att = jsPlumbAdapter.getAttribute,
74             _gel = jpcl.getElementObject,
75             _dom = jpcl.getDOMElement,
76             _ju = jsPlumbUtil,            
77             _newConnection = params.newConnection,
78             _newEndpoint = params.newEndpoint,
79             _finaliseConnection = params.finaliseConnection,
80             _fireDetachEvent = params.fireDetachEvent,
81             _fireMoveEvent = params.fireMoveEvent,
82             floatingConnections = params.floatingConnections;
83         
84         this.idPrefix = "_jsplumb_e_";                  
85         this.defaultLabelLocation = [ 0.5, 0.5 ];
86         this.defaultOverlayKeys = ["Overlays", "EndpointOverlays"];
87         this.parent = params.parent;
88         OverlayCapableJsPlumbUIComponent.apply(this, arguments);        
89         
90 // TYPE         
91                 
92         this.getDefaultType = function() {                                                              
93             return {
94                 parameters:{},
95                 scope:null,
96                 maxConnections:this._jsPlumb.instance.Defaults.MaxConnections,
97                 paintStyle:this._jsPlumb.instance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle,
98                 endpoint:this._jsPlumb.instance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint,
99                 hoverPaintStyle:this._jsPlumb.instance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle,                              
100                 overlays:this._jsPlumb.instance.Defaults.EndpointOverlays || jsPlumb.Defaults.EndpointOverlays,
101                 connectorStyle:params.connectorStyle,                           
102                 connectorHoverStyle:params.connectorHoverStyle,
103                 connectorClass:params.connectorClass,
104                 connectorHoverClass:params.connectorHoverClass,
105                 connectorOverlays:params.connectorOverlays,
106                 connector:params.connector,
107                 connectorTooltip:params.connectorTooltip
108             };
109         };
110                                 
111 // END TYPE
112             
113         this._jsPlumb.enabled = !(params.enabled === false);
114         this._jsPlumb.visible = true;        
115         this.element = _dom(params.source);  
116         this._jsPlumb.uuid = params.uuid;
117         this._jsPlumb.floatingEndpoint = null;  
118         var inPlaceCopy = null;
119         if (this._jsPlumb.uuid) params.endpointsByUUID[this._jsPlumb.uuid] = this;
120         this.elementId = params.elementId;
121         
122         this._jsPlumb.connectionCost = params.connectionCost;
123         this._jsPlumb.connectionsDirected = params.connectionsDirected;        
124         this._jsPlumb.currentAnchorClass = "";
125         this._jsPlumb.events = {};
126             
127         var  _updateAnchorClass = function() {
128             jpcl.removeClass(this.element, _jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
129             this.removeClass(_jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
130             this._jsPlumb.currentAnchorClass = this.anchor.getCssClass();
131             this.addClass(_jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
132             jpcl.addClass(this.element, _jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
133         }.bind(this);
134         
135         this.setAnchor = function(anchorParams, doNotRepaint) {
136             this._jsPlumb.instance.continuousAnchorFactory.clear(this.elementId);
137             this.anchor = this._jsPlumb.instance.makeAnchor(anchorParams, this.elementId, _jsPlumb);
138             _updateAnchorClass();
139             this.anchor.bind("anchorChanged", function(currentAnchor) {
140                 this.fire("anchorChanged", {endpoint:this, anchor:currentAnchor});
141                 _updateAnchorClass();
142             }.bind(this));
143             if (!doNotRepaint)
144                 this._jsPlumb.instance.repaint(this.elementId);
145             return this;
146         };
147
148         var anchorParamsToUse = params.anchor ? params.anchor : params.anchors ? params.anchors : (_jsPlumb.Defaults.Anchor || "Top");
149         this.setAnchor(anchorParamsToUse, true);
150
151         // endpoint delegates to first connection for hover, if there is one.
152         var internalHover = function(state) {
153           if (this.connections.length > 0)
154             this.connections[0].setHover(state, false);
155           else
156             this.setHover(state);
157         }.bind(this);
158             
159         // ANCHOR MANAGER
160         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.
161             this._jsPlumb.instance.anchorManager.add(this, this.elementId);
162         
163         this.setEndpoint = function(ep) {
164
165             if (this.endpoint != null) {
166                 this.endpoint.cleanup();
167                 this.endpoint.destroy();
168             }
169
170             var _e = function(t, p) {
171                 var rm = _jsPlumb.getRenderMode();
172                 if (jsPlumb.Endpoints[rm][t]) return new jsPlumb.Endpoints[rm][t](p);
173                 if (!_jsPlumb.Defaults.DoNotThrowErrors)
174                     throw { msg:"jsPlumb: unknown endpoint type '" + t + "'" };
175             };            
176
177             var endpointArgs = {
178                 _jsPlumb:this._jsPlumb.instance,
179                 cssClass:params.cssClass,
180                 parent:params.parent,
181                 container:params.container,
182                 tooltip:params.tooltip,
183                 connectorTooltip:params.connectorTooltip,
184                 endpoint:this
185             };
186             if (_ju.isString(ep)) 
187                 this.endpoint = _e(ep, endpointArgs);
188             else if (_ju.isArray(ep)) {
189                 endpointArgs = _ju.merge(ep[1], endpointArgs);
190                 this.endpoint = _e(ep[0], endpointArgs);
191             }
192             else {
193                 this.endpoint = ep.clone();
194             }
195
196             // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned,
197             // and the clone is left in its place while the original one goes off on a magical journey. 
198             // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by
199             // the whole world.
200             var argsForClone = jsPlumb.extend({}, endpointArgs);                                                
201             this.endpoint.clone = function() {
202                 // TODO this, and the code above, can be refactored to be more dry.
203                 if (_ju.isString(ep)) 
204                     return _e(ep, endpointArgs);
205                 else if (_ju.isArray(ep)) {
206                     endpointArgs = _ju.merge(ep[1], endpointArgs);
207                     return _e(ep[0], endpointArgs);
208                 }
209             }.bind(this);
210
211             this.type = this.endpoint.type;
212             // bind listeners from endpoint to self, with the internal hover function defined above.
213             this.bindListeners(this.endpoint, this, internalHover);
214         };
215          
216         this.setEndpoint(params.endpoint || _jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot");                                                                      
217         this.setPaintStyle(params.paintStyle || params.style || _jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, true);
218         this.setHoverPaintStyle(params.hoverPaintStyle || _jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle, true);
219         this._jsPlumb.paintStyleInUse = this.getPaintStyle();
220
221         _ju.copyValues(typeParameters, params, this);        
222
223         this.isSource = params.isSource || false;
224         this.isTarget = params.isTarget || false;        
225         this._jsPlumb.maxConnections = params.maxConnections || _jsPlumb.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of.                
226         this.canvas = this.endpoint.canvas;             
227         // add anchor class (need to do this on construction because we set anchor first)
228         this.addClass(_jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);     
229         jpcl.addClass(this.element, _jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
230         this.connections = params.connections || [];
231         this.connectorPointerEvents = params["connector-pointer-events"];
232         
233         this.scope = params.scope || _jsPlumb.getDefaultScope();        
234         this.timestamp = null;
235         this.reattachConnections = params.reattach || _jsPlumb.Defaults.ReattachConnections;
236         this.connectionsDetachable = _jsPlumb.Defaults.ConnectionsDetachable;
237         if (params.connectionsDetachable === false || params.detachable === false)
238             this.connectionsDetachable = false;
239         this.dragAllowedWhenFull = params.dragAllowedWhenFull || true;
240         
241         if (params.onMaxConnections)
242             this.bind("maxConnections", params.onMaxConnections);        
243         
244         //
245         // add a connection. not part of public API.
246         //
247         this.addConnection = function(connection) {
248             this.connections.push(connection);                  
249             this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass);       
250             this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass); 
251         };      
252         
253         this.detachFromConnection = function(connection, idx) {
254             idx = idx == null ? findConnectionIndex(connection, this) : idx;
255             if (idx >= 0) {
256                 this.connections.splice(idx, 1);
257                 this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass);       
258                 this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass);                 
259             }
260         };
261
262         this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent, endpointBeingDeleted, connectionIndex) {
263
264             var idx = connectionIndex == null ? findConnectionIndex(connection, this) : connectionIndex,
265                 actuallyDetached = false;
266                 fireEvent = (fireEvent !== false);
267
268             if (idx >= 0) {                             
269                 if (forceDetach || connection._forceDetach || (connection.isDetachable() && connection.isDetachAllowed(connection) && this.isDetachAllowed(connection) )) {
270
271                     //connection.setHover(false);
272
273                     _jsPlumb.deleteObject({
274                         connection:connection, 
275                         fireEvent:(!ignoreTarget && fireEvent), 
276                         originalEvent:originalEvent
277                     });
278                     actuallyDetached = true;                       
279                 }
280             }
281             return actuallyDetached;
282         };      
283
284         this.detachAll = function(fireEvent, originalEvent) {
285             while (this.connections.length > 0) {
286                 // TODO this could pass the index in to the detach method to save some time (index will always be zero in this while loop)
287                 // TODO now it defaults to fireEvent true.  will that mess with things?
288                 this.detach(this.connections[0], false, true, fireEvent !== false, originalEvent, this, 0);
289             }
290             return this;
291         };                
292         this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) {
293             var c = [];
294             for ( var i = 0; i < this.connections.length; i++) {
295                 if (this.connections[i].endpoints[1] == targetEndpoint || this.connections[i].endpoints[0] == targetEndpoint) {
296                     c.push(this.connections[i]);
297                 }
298             }
299             for ( var j = 0; j < c.length; j++) {
300                 this.detach(c[j], false, true, fireEvent, originalEvent);                               
301             }
302             return this;
303         };              
304         
305         this.getElement = function() {
306             return this.element;
307         };              
308                  
309         // container not supported in 1.5.5; you cannot change the container once it is set.
310         // it might come back int a future release.
311         this.setElement = function(el/*, container*/) {
312             var parentId = this._jsPlumb.instance.getId(el),
313                 curId = this.elementId;
314             // remove the endpoint from the list for the current endpoint's element
315             _ju.removeWithFunction(params.endpointsByElement[this.elementId], function(e) {
316                 return e.id == this.id;
317             }.bind(this));
318             this.element = _dom(el);
319             this.elementId = _jsPlumb.getId(this.element);                         
320             _jsPlumb.anchorManager.rehomeEndpoint(this, curId, this.element);
321             _jsPlumb.dragManager.endpointAdded(this.element);            
322             _ju.addToList(params.endpointsByElement, parentId, this);            
323             return this;
324         };
325                 
326         /**
327          * private but must be exposed.
328          */
329         this.makeInPlaceCopy = function() {
330             var loc = this.anchor.getCurrentLocation({element:this}),
331                 o = this.anchor.getOrientation(this),
332                 acc = this.anchor.getCssClass(),
333                 inPlaceAnchor = {
334                     bind:function() { },
335                     compute:function() { return [ loc[0], loc[1] ]; },
336                     getCurrentLocation : function() { return [ loc[0], loc[1] ]; },
337                     getOrientation:function() { return o; },
338                     getCssClass:function() { return acc; }
339                 };
340
341             return _newEndpoint( { 
342                 anchor : inPlaceAnchor, 
343                 source : this.element, 
344                 paintStyle : this.getPaintStyle(), 
345                 endpoint : params.hideOnDrag ? "Blank" : this.endpoint,
346                 _transient:true,
347                 scope:this.scope
348             });
349         };
350                 
351
352         /**
353          * private but needs to be exposed.
354          */
355         this.isFloating = function() {
356             return this.anchor != null && this.anchor.isFloating;
357         };
358         
359         /**
360          * returns a connection from the pool; used when dragging starts.  just gets the head of the array if it can.
361          */
362         this.connectorSelector = function() {
363             var candidate = this.connections[0];
364             if (this.isTarget && candidate) return candidate;
365             else {
366                 return (this.connections.length < this._jsPlumb.maxConnections) || this._jsPlumb.maxConnections == -1 ? null : candidate;
367             }
368         };        
369         
370         this.setStyle = this.setPaintStyle;        
371         
372         this.paint = function(params) {
373             params = params || {};
374             var timestamp = params.timestamp, recalc = !(params.recalc === false);                                                              
375             if (!timestamp || this.timestamp !== timestamp) {                                           
376                 
377                 // TODO check: is this is a safe performance enhancement?
378                 var info = _jsPlumb.updateOffset({ elId:this.elementId, timestamp:timestamp/*, recalc:recalc*/ });                
379
380                 var xy = params.offset ? params.offset.o : info.o;
381                 if(xy != null) {
382                     var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle;
383                     if (ap == null) {
384                         var wh = params.dimensions || info.s,                       
385                             anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : this, timestamp : timestamp };
386                         if (recalc && this.anchor.isDynamic && this.connections.length > 0) {
387                             var c = findConnectionToUseForDynamicAnchor(this, params.elementWithPrecedence),
388                                 oIdx = c.endpoints[0] == this ? 1 : 0,
389                                 oId = oIdx === 0 ? c.sourceId : c.targetId,
390                                 oInfo = _jsPlumb.getCachedData(oId),
391                                 oOffset = oInfo.o, oWH = oInfo.s;
392                             anchorParams.txy = [ oOffset.left, oOffset.top ];
393                             anchorParams.twh = oWH;
394                             anchorParams.tElement = c.endpoints[oIdx];
395                         }
396                         ap = this.anchor.compute(anchorParams);
397                     }
398                                         
399                     this.endpoint.compute(ap, this.anchor.getOrientation(this), this._jsPlumb.paintStyleInUse, connectorPaintStyle || this.paintStyleInUse);
400                     this.endpoint.paint(this._jsPlumb.paintStyleInUse, this.anchor);                                    
401                     this.timestamp = timestamp;
402
403                     // paint overlays
404                     for ( var i = 0; i < this._jsPlumb.overlays.length; i++) {
405                         var o = this._jsPlumb.overlays[i];
406                         if (o.isVisible()) { 
407                             this._jsPlumb.overlayPlacements[i] = o.draw(this.endpoint, this._jsPlumb.paintStyleInUse);
408                             o.paint(this._jsPlumb.overlayPlacements[i]);    
409                         }
410                     }
411                 }
412             }
413         };
414
415         this.repaint = this.paint; 
416
417         var draggingInitialised = false;
418         this.initDraggable = function() {
419             // is this a connection source? we make it draggable and have the
420             // drag listener maintain a connection with a floating endpoint.
421             if (!draggingInitialised && jpcl.isDragSupported(this.element)) {
422                 var placeholderInfo = { id:null, element:null },
423                     jpc = null,
424                     existingJpc = false,
425                     existingJpcParams = null,
426                     _dragHandler = _makeConnectionDragHandler(placeholderInfo, _jsPlumb);
427
428                 var start = function() {    
429                 // drag might have started on an endpoint that is not actually a source, but which has
430                 // one or more connections.
431                     jpc = this.connectorSelector();
432                     var _continue = true;
433                     // if not enabled, return
434                     if (!this.isEnabled()) _continue = false;
435                     // if no connection and we're not a source, return.
436                     if (jpc == null && !this.isSource) _continue = false;
437                     // otherwise if we're full and not allowed to drag, also return false.
438                     if (this.isSource && this.isFull() && !this.dragAllowedWhenFull) _continue = false;
439                     // if the connection was setup as not detachable or one of its endpoints
440                     // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable
441                     // is set to false...
442                     if (jpc != null && !jpc.isDetachable()) _continue = false;
443
444                     if (_continue === false) {
445                         // this is for mootools and yui. returning false from this causes jquery to stop drag.
446                         // the events are wrapped in both mootools and yui anyway, but i don't think returning
447                         // false from the start callback would stop a drag.
448                         if (jpcl.stopDrag) jpcl.stopDrag();
449                         _dragHandler.stopDrag();
450                         return false;
451                     }
452
453                     // clear hover for all connections for this endpoint before continuing.
454                     for (var i = 0; i < this.connections.length; i++)
455                         this.connections[i].setHover(false);
456
457                     this.addClass("endpointDrag");
458                     _jsPlumb.setConnectionBeingDragged(true);
459
460                     // if we're not full but there was a connection, make it null. we'll create a new one.
461                     if (jpc && !this.isFull() && this.isSource) jpc = null;
462
463                     _jsPlumb.updateOffset( { elId : this.elementId });
464                     inPlaceCopy = this.makeInPlaceCopy();
465                     inPlaceCopy.referenceEndpoint = this;
466                     inPlaceCopy.paint();                                                                
467                     
468                     _makeDraggablePlaceholder(placeholderInfo, this.parent, _jsPlumb);
469                     
470                     // set the offset of this div to be where 'inPlaceCopy' is, to start with.
471                     // TODO merge this code with the code in both Anchor and FloatingAnchor, because it
472                     // does the same stuff.
473                     var ipcoel = _gel(inPlaceCopy.canvas),
474                         ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel, _jsPlumb),
475                         po = _jsPlumb.adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas),
476                         canvasElement = _gel(this.canvas);                               
477                         
478                     jpcl.setOffset(placeholderInfo.element, {left:po[0], top:po[1]});                                                           
479                     
480                     // when using makeSource and a parent, we first draw the source anchor on the source element, then
481                     // move it to the parent.  note that this happens after drawing the placeholder for the
482                     // first time.
483                     if (this.parentAnchor) this.anchor = _jsPlumb.makeAnchor(this.parentAnchor, this.elementId, _jsPlumb);
484                     
485                     // store the id of the dragging div and the source element. the drop function will pick these up.                   
486                     _jsPlumb.setAttribute(this.canvas, "dragId", placeholderInfo.id);
487                     _jsPlumb.setAttribute(this.canvas, "elId", this.elementId);
488
489                     this._jsPlumb.floatingEndpoint = _makeFloatingEndpoint(this.getPaintStyle(), this.anchor, this.endpoint, this.canvas, placeholderInfo.element, _jsPlumb, _newEndpoint);
490                     // TODO we should not know about DOM here. make the library adapter do this (or the 
491                         // dom adapter)
492                     this.canvas.style.visibility = "hidden";            
493                     
494                     if (jpc == null) {                                                                                                                                                         
495                         this.anchor.locked = true;
496                         this.setHover(false, false);                        
497                         // create a connection. one end is this endpoint, the other is a floating endpoint.                    
498                         jpc = _newConnection({
499                             sourceEndpoint : this,
500                             targetEndpoint : this._jsPlumb.floatingEndpoint,
501                             source : this.endpointWillMoveTo || this.element,  // for makeSource with parent option.  ensure source element is represented correctly.
502                             target : placeholderInfo.element,
503                             anchors : [ this.anchor, this._jsPlumb.floatingEndpoint.anchor ],
504                             paintStyle : params.connectorStyle, // this can be null. Connection will use the default.
505                             hoverPaintStyle:params.connectorHoverStyle,
506                             connector : params.connector, // this can also be null. Connection will use the default.
507                             overlays : params.connectorOverlays,
508                             type:this.connectionType,
509                             cssClass:this.connectorClass,
510                             hoverClass:this.connectorHoverClass
511                         });
512                         jpc.pending = true; // mark this connection as not having been established.
513                         jpc.addClass(_jsPlumb.draggingClass);
514                         this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass);
515                         // fire an event that informs that a connection is being dragged                        
516                         _jsPlumb.fire("connectionDrag", jpc);
517
518                     } else {
519                         existingJpc = true;
520                         jpc.setHover(false);                        
521                         // if existing connection, allow to be dropped back on the source endpoint (issue 51).
522                         _initDropTarget(ipcoel, false, true);
523                         // new anchor idx
524                         var anchorIdx = jpc.endpoints[0].id == this.id ? 0 : 1;
525                         jpc.floatingAnchorIndex = anchorIdx;                    // save our anchor index as the connection's floating index.                        
526                         this.detachFromConnection(jpc);                         // detach from the connection while dragging is occurring.
527                         
528                         // store the original scope (issue 57)
529                         var dragScope = jsPlumb.CurrentLibrary.getDragScope(canvasElement);
530                         _jsPlumb.setAttribute(this.canvas, "originalScope", dragScope);
531                         // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones
532                         // that have our drop scope (issue 57).
533                         var dropScope = jpcl.getDropScope(canvasElement);
534                         jpcl.setDragScope(canvasElement, dropScope);
535
536                         // fire an event that informs that a connection is being dragged. we do this before
537                         // replacing the original target with the floating element info.
538                         _jsPlumb.fire("connectionDrag", jpc);
539                 
540                         // now we replace ourselves with the temporary div we created above:
541                         if (anchorIdx === 0) {
542                             existingJpcParams = [ jpc.source, jpc.sourceId, canvasElement, dragScope ];
543                             jpc.source = placeholderInfo.element;
544                             jpc.sourceId = placeholderInfo.id;
545                         } else {
546                             existingJpcParams = [ jpc.target, jpc.targetId, canvasElement, dragScope ];
547                             jpc.target = placeholderInfo.element;
548                             jpc.targetId = placeholderInfo.id;
549                         }
550
551                         // lock the other endpoint; if it is dynamic it will not move while the drag is occurring.
552                         jpc.endpoints[anchorIdx === 0 ? 1 : 0].anchor.locked = true;
553                         // store the original endpoint and assign the new floating endpoint for the drag.
554                         jpc.suspendedEndpoint = jpc.endpoints[anchorIdx];
555                         
556                         // PROVIDE THE SUSPENDED ELEMENT, BE IT A SOURCE OR TARGET (ISSUE 39)
557                         jpc.suspendedElement = jpc.endpoints[anchorIdx].getElement();
558                         jpc.suspendedElementId = jpc.endpoints[anchorIdx].elementId;
559                         jpc.suspendedElementType = anchorIdx === 0 ? "source" : "target";
560                         
561                         jpc.suspendedEndpoint.setHover(false);
562                         this._jsPlumb.floatingEndpoint.referenceEndpoint = jpc.suspendedEndpoint;
563                         jpc.endpoints[anchorIdx] = this._jsPlumb.floatingEndpoint;
564
565                         jpc.addClass(_jsPlumb.draggingClass);
566                         this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass);                    
567
568                     }
569                     // register it and register connection on it.
570                     floatingConnections[placeholderInfo.id] = jpc;
571                     _jsPlumb.anchorManager.addFloatingConnection(placeholderInfo.id, jpc);               
572                     // only register for the target endpoint; we will not be dragging the source at any time
573                     // before this connection is either discarded or made into a permanent connection.
574                     _ju.addToList(params.endpointsByElement, placeholderInfo.id, this._jsPlumb.floatingEndpoint);
575                     // tell jsplumb about it
576                     _jsPlumb.currentlyDragging = true;
577                 }.bind(this);
578
579                 var dragOptions = params.dragOptions || {},
580                     defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions),
581                     startEvent = jpcl.dragEvents.start,
582                     stopEvent = jpcl.dragEvents.stop,
583                     dragEvent = jpcl.dragEvents.drag;
584                 
585                 dragOptions = jsPlumb.extend(defaultOpts, dragOptions);
586                 dragOptions.scope = dragOptions.scope || this.scope;
587                 dragOptions[startEvent] = _ju.wrap(dragOptions[startEvent], start, false);
588                 // extracted drag handler function so can be used by makeSource
589                 dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], _dragHandler.drag);
590                 dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent],
591                     function() {        
592
593                         _jsPlumb.setConnectionBeingDragged(false);  
594                         // if no endpoints, jpc already cleaned up.
595                         if (jpc.endpoints != null) {          
596                             // get the actual drop event (decode from library args to stop function)
597                             var originalEvent = jpcl.getDropEvent(arguments);                                       
598                             // unlock the other endpoint (if it is dynamic, it would have been locked at drag start)
599                             var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
600                             jpc.endpoints[idx === 0 ? 1 : 0].anchor.locked = false;
601                             // WHY does this need to happen?  i suppose because the connection might not get 
602                             // deleted.  TODO: i dont want to know about css classes inside jsplumb, ideally.
603                             jpc.removeClass(_jsPlumb.draggingClass);   
604                         
605                             // if we have the floating endpoint then the connection has not been dropped
606                             // on another endpoint.  If it is a new connection we throw it away. If it is an 
607                             // existing connection we check to see if we should reattach it, throwing it away 
608                             // if not.
609                             if (jpc.endpoints[idx] == this._jsPlumb.floatingEndpoint) {
610                                 // 6a. if the connection was an existing one...
611                                 if (existingJpc && jpc.suspendedEndpoint) {
612                                     // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the
613                                     // floating endpoint has been replaced.
614                                     if (idx === 0) {
615                                         jpc.source = existingJpcParams[0];
616                                         jpc.sourceId = existingJpcParams[1];
617                                     } else {
618                                         jpc.target = existingJpcParams[0];
619                                         jpc.targetId = existingJpcParams[1];
620                                     }
621                                     
622                                     // restore the original scope (issue 57)
623                                     jpcl.setDragScope(existingJpcParams[2], existingJpcParams[3]);
624                                     jpc.endpoints[idx] = jpc.suspendedEndpoint;
625                                     // IF the connection should be reattached, or the other endpoint refuses detach, then
626                                     // reset the connection to its original state
627                                     if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !jpc.endpoints[idx === 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) {                                   
628                                         jpc.setHover(false);
629                                         jpc.floatingAnchorIndex = null;
630                                         jpc._forceDetach = null;
631                                         jpc._forceReattach = null;
632                                         this._jsPlumb.floatingEndpoint.detachFromConnection(jpc);
633                                         jpc.suspendedEndpoint.addConnection(jpc);
634                                         _jsPlumb.repaint(existingJpcParams[1]);
635                                     }
636                                 }                                                               
637                             }
638                         }
639
640                         // remove the element associated with the floating endpoint 
641                         // (and its associated floating endpoint and visual artefacts)                                        
642                         _jsPlumb.remove(placeholderInfo.element, false);
643                         // remove the inplace copy
644                         _jsPlumb.remove(inPlaceCopy.canvas, false);
645
646                         // makeTargets sets this flag, to tell us we have been replaced and should delete ourself.
647                         if (this.deleteAfterDragStop) {                        
648                             _jsPlumb.deleteObject({endpoint:this});
649                         }
650                         else {
651                             if (this._jsPlumb) {
652                                 this._jsPlumb.floatingEndpoint = null;
653                                 // repaint this endpoint.
654                                 // make our canvas visible (TODO: hand off to library; we should not know about DOM)
655                                 this.canvas.style.visibility = "visible";
656                                 // unlock our anchor
657                                 this.anchor.locked = false;
658                                 this.paint({recalc:false});                        
659                             }
660                         }                                                    
661
662                         // although the connection is no longer valid, there are use cases where this is useful.
663                         _jsPlumb.fire("connectionDragStop", jpc, originalEvent);
664
665                         // tell jsplumb that dragging is finished.
666                         _jsPlumb.currentlyDragging = false;
667
668                         jpc = null;
669
670                     }.bind(this));
671                 
672                 var i = _gel(this.canvas);              
673                 jpcl.initDraggable(i, dragOptions, true, _jsPlumb);
674
675                 draggingInitialised = true;
676             }
677         };
678
679         // if marked as source or target at create time, init the dragging.
680         if (this.isSource || this.isTarget)
681             this.initDraggable();        
682
683         // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections
684         // back onto the endpoint you detached it from.
685         var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) {
686             if ((this.isTarget || forceInit) && jpcl.isDropSupported(this.element)) {
687                 var dropOptions = params.dropOptions || _jsPlumb.Defaults.DropOptions || jsPlumb.Defaults.DropOptions;
688                 dropOptions = jsPlumb.extend( {}, dropOptions);
689                 dropOptions.scope = dropOptions.scope || this.scope;
690                 var dropEvent = jpcl.dragEvents.drop,
691                     overEvent = jpcl.dragEvents.over,
692                     outEvent = jpcl.dragEvents.out,
693                     drop = function() {                        
694
695                         this.removeClass(_jsPlumb.endpointDropAllowedClass);
696                         this.removeClass(_jsPlumb.endpointDropForbiddenClass);
697                                                     
698                         var originalEvent = jpcl.getDropEvent(arguments),
699                             draggable = _gel(jpcl.getDragObject(arguments)),
700                             id = _jsPlumb.getAttribute(draggable, "dragId"),
701                             elId = _jsPlumb.getAttribute(draggable, "elId"),                                            
702                             scope = _jsPlumb.getAttribute(draggable, "originalScope"),
703                             jpc = floatingConnections[id];
704                             
705                         // if this is a drop back where the connection came from, mark it force rettach and
706                         // return; the stop handler will reattach. without firing an event.
707                         var redrop = jpc.suspendedEndpoint && (jpc.suspendedEndpoint.id == this.id ||
708                                         this.referenceEndpoint && jpc.suspendedEndpoint.id == this.referenceEndpoint.id) ;                                                      
709                         if (redrop) {                                                           
710                             jpc._forceReattach = true;
711                             return;
712                         }
713
714                         if (jpc != null) {
715                             var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx === 0 ? 1 : 0;
716                             
717                             // restore the original scope if necessary (issue 57)                                               
718                             if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope);                                                   
719                             
720                             var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true;
721                             
722                             if (this.isFull()) {
723                                 this.fire("maxConnections", { 
724                                     endpoint:this, 
725                                     connection:jpc, 
726                                     maxConnections:this._jsPlumb.maxConnections 
727                                 }, originalEvent);
728                             }
729                                                             
730                             if (!this.isFull() && !(idx === 0 && !this.isSource) && !(idx == 1 && !this.isTarget) && endpointEnabled) {
731                                 var _doContinue = true;
732
733                                 // the second check here is for the case that the user is dropping it back
734                                 // where it came from.
735                                 if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != this.id) {
736                                     if (idx === 0) {
737                                         jpc.source = jpc.suspendedEndpoint.element;
738                                         jpc.sourceId = jpc.suspendedEndpoint.elementId;
739                                     } else {
740                                         jpc.target = jpc.suspendedEndpoint.element;
741                                         jpc.targetId = jpc.suspendedEndpoint.elementId;
742                                     }
743
744                                     if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_jsPlumb.checkCondition("beforeDetach", jpc))
745                                         _doContinue = false;                                                            
746                                 }
747             
748                                 // these have to be set before testing for beforeDrop.
749                                 if (idx === 0) {
750                                     jpc.source = this.element;
751                                     jpc.sourceId = this.elementId;
752                                 } else {
753                                     jpc.target = this.element;
754                                     jpc.targetId = this.elementId;
755                                 }
756                                                             
757 // ------------ wrap the execution path in a function so we can support asynchronous beforeDrop                                                                                                                         
758                                     
759                                 // we want to execute this regardless.
760                                 var commonFunction = function() {
761                                     jpc.floatingAnchorIndex = null;
762                                 };      
763                                                                                                 
764                                 var continueFunction = function() {
765                                     jpc.pending = false;
766
767                                     // remove this jpc from the current endpoint
768                                     jpc.endpoints[idx].detachFromConnection(jpc);
769                                     if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc);
770                                     jpc.endpoints[idx] = this;
771                                     this.addConnection(jpc);
772                                     
773                                     // copy our parameters in to the connection:
774                                     var params = this.getParameters();
775                                     for (var aParam in params)
776                                         jpc.setParameter(aParam, params[aParam]);
777
778                                     if (!jpc.suspendedEndpoint) {  
779                                         // if not an existing connection and
780                                         if (params.draggable)
781                                             jsPlumb.CurrentLibrary.initDraggable(this.element, dragOptions, true, _jsPlumb);
782                                     }
783                                     else {
784                                         var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId;
785                                         _fireMoveEvent({
786                                             index:idx,
787                                             originalSourceId:idx === 0 ? suspendedElementId : jpc.sourceId,
788                                             newSourceId:idx === 0 ? this.elementId : jpc.sourceId,
789                                             originalTargetId:idx == 1 ? suspendedElementId : jpc.targetId,
790                                             newTargetId:idx == 1 ? this.elementId : jpc.targetId,
791                                             originalSourceEndpoint:idx === 0 ? jpc.suspendedEndpoint : jpc.endpoints[0],
792                                             newSourceEndpoint:idx === 0 ? this : jpc.endpoints[0],
793                                             originalTargetEndpoint:idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1],
794                                             newTargetEndpoint:idx == 1 ? this : jpc.endpoints[1],
795                                             connection:jpc
796                                         }, originalEvent);
797                                        /* var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId;
798                                         // fire a detach event
799                                         _fireDetachEvent({
800                                             source : idx === 0 ? suspendedElement : jpc.source, 
801                                             target : idx == 1 ? suspendedElement : jpc.target,
802                                             sourceId : idx === 0 ? suspendedElementId : jpc.sourceId, 
803                                             targetId : idx == 1 ? suspendedElementId : jpc.targetId,
804                                             sourceEndpoint : idx === 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], 
805                                             targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1],
806                                             connection : jpc
807                                         }, true, originalEvent);*/
808                                     }
809
810                                     // TODO this is like the makeTarget drop code.
811                                     if (idx == 1)
812                                         _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.suspendedElementId, jpc.targetId, jpc);
813                                     else                                    
814                                         _jsPlumb.anchorManager.sourceChanged(jpc.suspendedEndpoint.elementId, jpc.sourceId, jpc);                                   
815
816                                     // finalise will inform the anchor manager and also add to
817                                     // connectionsByScope if necessary.
818                                     // TODO if this is not set to true, then dragging a connection's target to a new
819                                     // target causes the connection to be forgotten. however if it IS set to true, then
820                                     // the opposite happens: dragging by source causes the connection to get forgotten
821                                     // about and then if you delete it jsplumb breaks.
822                                     _finaliseConnection(jpc, null, originalEvent/*, true*/);
823                                     
824                                     commonFunction();
825                                 }.bind(this);
826                                 
827                                 var dontContinueFunction = function() {
828                                     // otherwise just put it back on the endpoint it was on before the drag.
829                                     if (jpc.suspendedEndpoint) {                                                                        
830                                         jpc.endpoints[idx] = jpc.suspendedEndpoint;
831                                         jpc.setHover(false);
832                                         jpc._forceDetach = true;
833                                         if (idx === 0) {
834                                             jpc.source = jpc.suspendedEndpoint.element;
835                                             jpc.sourceId = jpc.suspendedEndpoint.elementId;
836                                         } else {
837                                             jpc.target = jpc.suspendedEndpoint.element;
838                                             jpc.targetId = jpc.suspendedEndpoint.elementId;
839                                         }
840                                         jpc.suspendedEndpoint.addConnection(jpc);
841
842                                         jpc.endpoints[0].repaint();
843                                         jpc.repaint();
844                                         _jsPlumb.repaint(jpc.sourceId);
845                                         jpc._forceDetach = false;
846                                     }
847                                     
848                                     commonFunction();
849                                 };
850                                 
851 // --------------------------------------
852                                 // now check beforeDrop.  this will be available only on Endpoints that are setup to
853                                 // have a beforeDrop condition (although, secretly, under the hood all Endpoints and 
854                                 // the Connection have them, because they are on jsPlumbUIComponent.  shhh!), because
855                                 // it only makes sense to have it on a target endpoint.
856                                 _doContinue = _doContinue && this.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, this);
857                                                                                                                     
858                                 if (_doContinue) {
859                                     continueFunction();
860                                 }
861                                 else {
862                                     dontContinueFunction();
863                                 }
864                             }
865                             _jsPlumb.currentlyDragging = false;
866                         }
867                     }.bind(this);
868                 
869                 dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], drop);
870                 dropOptions[overEvent] = _ju.wrap(dropOptions[overEvent], function() {                                  
871                     var draggable = jpcl.getDragObject(arguments),
872                         id = _jsPlumb.getAttribute(draggable, "dragId"),
873                         _jpc = floatingConnections[id];
874                         
875                     if (_jpc != null) {                                                         
876                         var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex;
877                         // here we should fire the 'over' event if we are a target and this is a new connection,
878                         // or we are the same as the floating endpoint.                                                         
879                         var _cont = (this.isTarget && _jpc.floatingAnchorIndex !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id == _jpc.suspendedEndpoint.id);
880                         if (_cont) {
881                             var bb = _jsPlumb.checkCondition("checkDropAllowed", { 
882                                 sourceEndpoint:_jpc.endpoints[idx], 
883                                 targetEndpoint:this,
884                                 connection:_jpc
885                             }); 
886                             this[(bb ? "add" : "remove") + "Class"](_jsPlumb.endpointDropAllowedClass);
887                             this[(bb ? "remove" : "add") + "Class"](_jsPlumb.endpointDropForbiddenClass);
888                             _jpc.endpoints[idx].anchor.over(this.anchor, this);
889                         }
890                     }                                           
891                 }.bind(this));  
892
893                 dropOptions[outEvent] = _ju.wrap(dropOptions[outEvent], function() {                                    
894                     var draggable = jpcl.getDragObject(arguments),
895                         id = _jsPlumb.getAttribute( draggable, "dragId"),
896                         _jpc = floatingConnections[id];
897                         
898                     if (_jpc != null) {
899                         var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex;
900                         var _cont = (this.isTarget && _jpc.floatingAnchorIndex !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id == _jpc.suspendedEndpoint.id);
901                         if (_cont) {
902                             this.removeClass(_jsPlumb.endpointDropAllowedClass);
903                             this.removeClass(_jsPlumb.endpointDropForbiddenClass);
904                             _jpc.endpoints[idx].anchor.out();
905                         }
906                     }
907                 }.bind(this));
908                 jpcl.initDroppable(canvas, dropOptions, true, isTransient);
909             }
910         }.bind(this);
911         
912         // 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.
913         _initDropTarget(_gel(this.canvas), true, !(params._transient || this.anchor.isFloating), this);
914         
915          // finally, set type if it was provided
916          if (params.type)
917             this.addType(params.type, params.data, _jsPlumb.isSuspendDrawing());
918
919         return this;                                            
920     };
921
922     jsPlumbUtil.extend(jsPlumb.Endpoint, OverlayCapableJsPlumbUIComponent, {
923         getTypeDescriptor : function() { return "endpoint"; },        
924         isVisible : function() { return this._jsPlumb.visible; },
925         setVisible : function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) {
926             this._jsPlumb.visible = v;
927             if (this.canvas) this.canvas.style.display = v ? "block" : "none";
928             this[v ? "showOverlays" : "hideOverlays"]();
929             if (!doNotChangeConnections) {
930                 for (var i = 0; i < this.connections.length; i++) {
931                     this.connections[i].setVisible(v);
932                     if (!doNotNotifyOtherEndpoint) {
933                         var oIdx = this === this.connections[i].endpoints[0] ? 1 : 0;
934                         // only change the other endpoint if this is its only connection.
935                         if (this.connections[i].endpoints[oIdx].connections.length == 1) this.connections[i].endpoints[oIdx].setVisible(v, true, true);
936                     }
937                 }
938             }
939         },
940         getAttachedElements : function() {
941             return this.connections;
942         },
943         applyType : function(t, doNotRepaint) {         
944             if (t.maxConnections != null) this._jsPlumb.maxConnections = t.maxConnections;
945             if (t.scope) this.scope = t.scope;
946             jsPlumbUtil.copyValues(typeParameters, t, this);
947             if (t.anchor) {
948                 this.anchor = this._jsPlumb.instance.makeAnchor(t.anchor);
949             }
950         },
951         isEnabled : function() { return this._jsPlumb.enabled; },
952         setEnabled : function(e) { this._jsPlumb.enabled = e; },
953         cleanup : function() {            
954             jsPlumb.CurrentLibrary.removeClass(this.element, this._jsPlumb.instance.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);            
955             this.anchor = null;
956             this.endpoint.cleanup();
957             this.endpoint.destroy();
958             this.endpoint = null;
959             // drag/drop
960             var i = jsPlumb.CurrentLibrary.getElementObject(this.canvas);              
961             jsPlumb.CurrentLibrary.destroyDraggable(i);
962             jsPlumb.CurrentLibrary.destroyDroppable(i);
963         },
964         setHover : function(h) {
965             if (this.endpoint && this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged())
966                 this.endpoint.setHover(h);            
967         },
968         isFull : function() {
969             return !(this.isFloating() || this._jsPlumb.maxConnections < 1 || this.connections.length < this._jsPlumb.maxConnections);              
970         },
971         getConnectionCost : function() { return this._jsPlumb.connectionCost; },
972         setConnectionCost : function(c) {
973             this._jsPlumb.connectionCost = c; 
974         },
975         areConnectionsDirected : function() { return this._jsPlumb.connectionsDirected; },
976         setConnectionsDirected : function(b) { this._jsPlumb.connectionsDirected = b; },
977         setElementId : function(_elId) {
978             this.elementId = _elId;
979             this.anchor.elementId = _elId;
980         },        
981         setReferenceElement : function(_el) {
982             this.element = jsPlumb.CurrentLibrary.getDOMElement(_el);
983         },
984         setDragAllowedWhenFull : function(allowed) {
985             this.dragAllowedWhenFull = allowed;
986         },
987         equals : function(endpoint) {
988             return this.anchor.equals(endpoint.anchor);
989         },
990         getUuid : function() {
991             return this._jsPlumb.uuid;
992         },
993         computeAnchor : function(params) {
994             return this.anchor.compute(params);
995         }
996     });
997 })();