reservation plugin - unbound request (unclean
[unfold.git] / portal / static / unbound_reservation_static / src / jsPlumb.js
1 /**
2  * @module jsPlumb
3  * @description Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
4  * elements, or VML.   
5  * 
6  * - [Demo Site](http://jsplumb.org)
7  * - [GitHub](http://github.com/sporritt/jsplumb)
8  * 
9  * Dual licensed under the MIT and GPL2 licenses.
10  *
11  * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
12  */
13 ;(function() {
14                         
15     var _ju = jsPlumbUtil,
16         _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_gel(el), clazz); },
17                 _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_gel(el), clazz); },
18                 _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_gel(el), clazz); },
19                 _gel = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
20                 _dom = function(el) { return jsPlumb.CurrentLibrary.getDOMElement(el); },               
21                 _getOffset = function(el, _instance) {
22             var o = jsPlumb.CurrentLibrary.getOffset(_gel(el));
23                         if (_instance != null) {
24                 var z = _instance.getZoom();
25                 return {left:o.left / z, top:o.top / z };    
26             }
27             else
28                 return o;
29         },              
30                 _getSize = function(el) {
31             return jsPlumb.CurrentLibrary.getSize(_gel(el));
32         },
33                 
34                 /**
35                  * creates a timestamp, using milliseconds since 1970, but as a string.
36                  */
37                 _timestamp = function() { return "" + (new Date()).getTime(); },
38
39                 // helper method to update the hover style whenever it, or paintStyle, changes.
40                 // we use paintStyle as the foundation and merge hoverPaintStyle over the
41                 // top.
42                 _updateHoverStyle = function(component) {
43                         if (component._jsPlumb.paintStyle && component._jsPlumb.hoverPaintStyle) {
44                                 var mergedHoverStyle = {};
45                                 jsPlumb.extend(mergedHoverStyle, component._jsPlumb.paintStyle);
46                                 jsPlumb.extend(mergedHoverStyle, component._jsPlumb.hoverPaintStyle);
47                                 delete component._jsPlumb.hoverPaintStyle;
48                                 // we want the fillStyle of paintStyle to override a gradient, if possible.
49                                 if (mergedHoverStyle.gradient && component._jsPlumb.paintStyle.fillStyle)
50                                         delete mergedHoverStyle.gradient;
51                                 component._jsPlumb.hoverPaintStyle = mergedHoverStyle;
52                         }
53                 },              
54                 events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ],
55                 eventFilters = { "mouseout":"mouseexit" },
56                 _updateAttachedElements = function(component, state, timestamp, sourceElement) {
57                         var affectedElements = component.getAttachedElements();
58                         if (affectedElements) {
59                                 for (var i = 0, j = affectedElements.length; i < j; i++) {
60                                         if (!sourceElement || sourceElement != affectedElements[i])
61                                                 affectedElements[i].setHover(state, true, timestamp);                   // tell the attached elements not to inform their own attached elements.
62                                 }
63                         }
64                 },
65                 _splitType = function(t) { return t == null ? null : t.split(" "); },           
66                 _applyTypes = function(component, params, doNotRepaint) {
67                         if (component.getDefaultType) {
68                                 var td = component.getTypeDescriptor();
69                                         
70                                 var o = _ju.merge({}, component.getDefaultType());
71                                 for (var i = 0, j = component._jsPlumb.types.length; i < j; i++)
72                                         o = _ju.merge(o, component._jsPlumb.instance.getType(component._jsPlumb.types[i], td));                                         
73                                         
74                                 if (params) {
75                                         o = _ju.populate(o, params);
76                                 }
77                         
78                                 component.applyType(o, doNotRepaint);                                   
79                                 if (!doNotRepaint) component.repaint();
80                         }
81                 },              
82
83 // ------------------------------ BEGIN jsPlumbUIComponent --------------------------------------------
84
85                 jsPlumbUIComponent = window.jsPlumbUIComponent = function(params) {
86
87                         jsPlumbUtil.EventGenerator.apply(this, arguments);
88
89                         var self = this, 
90                                 a = arguments,                                                          
91                                 idPrefix = self.idPrefix,
92                                 id = idPrefix + (new Date()).getTime(),
93                                 jpcl = jsPlumb.CurrentLibrary;
94
95                         this._jsPlumb = { 
96                                 instance: params._jsPlumb,
97                                 parameters:params.parameters || {},
98                                 paintStyle:null,
99                                 hoverPaintStyle:null,
100                                 paintStyleInUse:null,
101                                 hover:false,
102                                 beforeDetach:params.beforeDetach,
103                                 beforeDrop:params.beforeDrop,
104                                 overlayPlacements : [],
105                                 hoverClass: params.hoverClass || params._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass,
106                                 types:[]
107                         };
108
109                         this.getId = function() { return id; }; 
110                         
111                         // all components can generate events
112                         
113                         if (params.events) {
114                                 for (var i in params.events)
115                                         self.bind(i, params.events[i]);
116                         }
117
118                         // all components get this clone function.
119                         // TODO issue 116 showed a problem with this - it seems 'a' that is in
120                         // the clone function's scope is shared by all invocations of it, the classic
121                         // JS closure problem.  for now, jsPlumb does a version of this inline where 
122                         // it used to call clone.  but it would be nice to find some time to look
123                         // further at this.
124                         this.clone = function() {
125                                 var o = {};//new Object();
126                                 this.constructor.apply(o, a);
127                                 return o;
128                         }.bind(this);                           
129                                                 
130                         // user can supply a beforeDetach callback, which will be executed before a detach
131                         // is performed; returning false prevents the detach.                   
132                         this.isDetachAllowed = function(connection) {
133                                 var r = true;
134                                 if (this._jsPlumb.beforeDetach) {
135                                         try { 
136                                                 r = this._jsPlumb.beforeDetach(connection); 
137                                         }
138                                         catch (e) { _ju.log("jsPlumb: beforeDetach callback failed", e); }
139                                 }
140                                 return r;
141                         };
142                         
143                         // user can supply a beforeDrop callback, which will be executed before a dropped
144                         // connection is confirmed. user can return false to reject connection.                 
145                         this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) {
146                                 var r = this._jsPlumb.instance.checkCondition("beforeDrop", { 
147                                         sourceId:sourceId, 
148                                         targetId:targetId, 
149                                         scope:scope,
150                                         connection:connection,
151                                         dropEndpoint:dropEndpoint 
152                                 });
153                                 if (this._jsPlumb.beforeDrop) {
154                                         try { 
155                                                 r = this._jsPlumb.beforeDrop({ 
156                                                         sourceId:sourceId, 
157                                                         targetId:targetId, 
158                                                         scope:scope, 
159                                                         connection:connection,
160                                                         dropEndpoint:dropEndpoint
161                                                 }); 
162                                         }
163                                         catch (e) { _ju.log("jsPlumb: beforeDrop callback failed", e); }
164                                 }
165                                 return r;
166                         };                                                                                                      
167
168                     var boundListeners = [],
169                         bindAListener = function(obj, type, fn) {
170                                 boundListeners.push([obj, type, fn]);
171                                 obj.bind(type, fn);
172                             },
173                         domListeners = [],
174                 bindOne = function(o, c, evt) {
175                                         var filteredEvent = eventFilters[evt] || evt,
176                                                 fn = function(ee) {
177                                                         c.fire(filteredEvent, c, ee);
178                                                 };
179                                         domListeners.push([o, evt, fn]);
180                                         jpcl.bind(o, evt, fn);
181                                 },
182                                 unbindOne = function(o, evt, fn) {
183                                         var filteredEvent = eventFilters[evt] || evt;
184                                         jpcl.unbind(o, evt, fn);
185                                 };
186
187             this.bindListeners = function(obj, _self, _hoverFunction) {
188                 bindAListener(obj, "click", function(ep, e) { _self.fire("click", _self, e); });             
189                 bindAListener(obj, "dblclick", function(ep, e) { _self.fire("dblclick", _self, e); });
190                 bindAListener(obj, "contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); });
191                 bindAListener(obj, "mouseenter", function(ep, e) {
192                     if (!_self.isHover()) {
193                         _hoverFunction(true);
194                         _self.fire("mouseenter", _self, e);
195                     }
196                 });
197                 bindAListener(obj, "mouseexit", function(ep, e) {
198                     if (_self.isHover()) {
199                         _hoverFunction(false);
200                         _self.fire("mouseexit", _self, e);
201                     }
202                 });       
203                 bindAListener(obj, "mousedown", function(ep, e) { _self.fire("mousedown", _self, e); });
204                 bindAListener(obj, "mouseup", function(ep, e) { _self.fire("mouseup", _self, e); });
205             };
206
207             this.unbindListeners = function() {
208                 for (var i = 0; i < boundListeners.length; i++) {
209                         var o = boundListeners[i];
210                         o[0].unbind(o[1], o[2]);
211                 }               
212                 boundListeners = null;
213             };            
214                     
215                     this.attachListeners = function(o, c) {
216                                 for (var i = 0, j = events.length; i < j; i++) {
217                                         bindOne(o, c, events[i]);                       
218                                 }
219                         };      
220                         this.detachListeners = function() {
221                                 for (var i = 0; i < domListeners.length; i++) {
222                                         unbindOne(domListeners[i][0], domListeners[i][1], domListeners[i][2]);
223                                 }
224                                 domListeners = null;
225                         };                          
226                     
227                     this.reattachListenersForElement = function(o) {
228                             if (arguments.length > 1) {
229                                 for (var i = 0, j = events.length; i < j; i++)
230                                         unbindOne(o, events[i]);
231                                 for (i = 1, j = arguments.length; i < j; i++)
232                                         this.attachListeners(o, arguments[i]);
233                         }
234                     };                                                                
235                 };
236
237                 jsPlumbUtil.extend(jsPlumbUIComponent, jsPlumbUtil.EventGenerator, {
238                         
239                         getParameter : function(name) { 
240                                 return this._jsPlumb.parameters[name]; 
241                         },
242                         
243                         setParameter : function(name, value) { 
244                                 this._jsPlumb.parameters[name] = value; 
245                         },
246                         
247                         getParameters : function() { 
248                                 return this._jsPlumb.parameters; 
249                         },                      
250                         
251                         setParameters : function(p) { 
252                                 this._jsPlumb.parameters = p; 
253                         },                      
254                         
255                         addClass : function(clazz) {
256                             if (this.canvas != null)
257                                 _addClass(this.canvas, clazz);
258                         },
259                                                 
260                         removeClass : function(clazz) {
261                             if (this.canvas != null)
262                                 _removeClass(this.canvas, clazz);
263                         },
264                         
265                         setType : function(typeId, params, doNotRepaint) {                              
266                                 this._jsPlumb.types = _splitType(typeId) || [];
267                                 _applyTypes(this, params, doNotRepaint);                                                                        
268                         },
269                         
270                         getType : function() {
271                                 return this._jsPlumb.types;
272                         },
273                         
274                         reapplyTypes : function(params, doNotRepaint) {
275                                 _applyTypes(this, params, doNotRepaint);
276                         },
277                         
278                         hasType : function(typeId) {
279                                 return jsPlumbUtil.indexOf(this._jsPlumb.types, typeId) != -1;
280                         },
281                         
282                         addType : function(typeId, params, doNotRepaint) {
283                                 var t = _splitType(typeId), _cont = false;
284                                 if (t != null) {
285                                         for (var i = 0, j = t.length; i < j; i++) {
286                                                 if (!this.hasType(t[i])) {
287                                                         this._jsPlumb.types.push(t[i]);
288                                                         _cont = true;                                           
289                                                 }
290                                         }
291                                         if (_cont) _applyTypes(this, params, doNotRepaint);
292                                 }
293                         },
294                         
295                         removeType : function(typeId, doNotRepaint) {
296                                 var t = _splitType(typeId), _cont = false, _one = function(tt) {
297                                                 var idx = _ju.indexOf(this._jsPlumb.types, tt);
298                                                 if (idx != -1) {
299                                                         this._jsPlumb.types.splice(idx, 1);
300                                                         return true;
301                                                 }
302                                                 return false;
303                                         }.bind(this);
304                                 
305                                 if (t != null) {
306                                         for (var i = 0,j = t.length; i < j; i++) {
307                                                 _cont = _one(t[i]) || _cont;
308                                         }
309                                         if (_cont) _applyTypes(this, null, doNotRepaint);
310                                 }
311                         },
312                         
313                         toggleType : function(typeId, params, doNotRepaint) {
314                                 var t = _splitType(typeId);
315                                 if (t != null) {
316                                         for (var i = 0, j = t.length; i < j; i++) {
317                                                 var idx = jsPlumbUtil.indexOf(this._jsPlumb.types, t[i]);
318                                                 if (idx != -1)
319                                                         this._jsPlumb.types.splice(idx, 1);
320                                                 else
321                                                         this._jsPlumb.types.push(t[i]);
322                                         }
323                                                 
324                                         _applyTypes(this, params, doNotRepaint);
325                                 }
326                         },
327                         applyType : function(t, doNotRepaint) {
328                                 this.setPaintStyle(t.paintStyle, doNotRepaint);                         
329                                 this.setHoverPaintStyle(t.hoverPaintStyle, doNotRepaint);
330                                 if (t.parameters){
331                                         for (var i in t.parameters)
332                                                 this.setParameter(i, t.parameters[i]);
333                                 }
334                         },
335                         setPaintStyle : function(style, doNotRepaint) {
336 //                      this._jsPlumb.paintStyle = jsPlumb.extend({}, style);
337 // TODO figure out if we want components to clone paintStyle so as not to share it.
338                                 this._jsPlumb.paintStyle = style;
339                         this._jsPlumb.paintStyleInUse = this._jsPlumb.paintStyle;
340                         _updateHoverStyle(this);
341                         if (!doNotRepaint) this.repaint();
342                     },
343                     getPaintStyle : function() {
344                         return this._jsPlumb.paintStyle;
345                     },
346                     setHoverPaintStyle : function(style, doNotRepaint) {                        
347                         //this._jsPlumb.hoverPaintStyle = jsPlumb.extend({}, style);
348 // TODO figure out if we want components to clone paintStyle so as not to share it.                     
349                         this._jsPlumb.hoverPaintStyle = style;
350                         _updateHoverStyle(this);
351                         if (!doNotRepaint) this.repaint();
352                     },
353                     getHoverPaintStyle : function() {
354                         return this._jsPlumb.hoverPaintStyle;
355                     },
356                         cleanup:function() {            
357                                 this.unbindListeners();
358                                 this.detachListeners();
359                         },
360                         destroy:function() {
361                                 this.cleanupListeners();
362                                 this.clone = null;                              
363                                 this._jsPlumb = null;
364                         },
365                         
366                         isHover : function() { return this._jsPlumb.hover; },
367                         
368                         setHover : function(hover, ignoreAttachedElements, timestamp) {
369                                 var jpcl = jsPlumb.CurrentLibrary;
370                         // while dragging, we ignore these events.  this keeps the UI from flashing and
371                         // swishing and whatevering.
372                                 if (this._jsPlumb && !this._jsPlumb.instance.currentlyDragging && !this._jsPlumb.instance.isHoverSuspended()) {
373                     
374                                 this._jsPlumb.hover = hover;
375                         
376                     if (this.canvas != null) {
377                         if (this._jsPlumb.instance.hoverClass != null) {                            
378                             jpcl[hover ? "addClass" : "removeClass"](this.canvas, this._jsPlumb.instance.hoverClass);                                                                       
379                         }                                              
380                     }
381                                         if (this._jsPlumb.hoverPaintStyle != null) {
382                                                 this._jsPlumb.paintStyleInUse = hover ? this._jsPlumb.hoverPaintStyle : this._jsPlumb.paintStyle;
383                                                 if (!this._jsPlumb.instance.isSuspendDrawing()) {
384                                                         timestamp = timestamp || _timestamp();
385                                                         this.repaint({timestamp:timestamp, recalc:false});
386                                                 }
387                                         }
388                                         // get the list of other affected elements, if supported by this component.
389                                         // for a connection, its the endpoints.  for an endpoint, its the connections! surprise.
390                                         if (this.getAttachedElements && !ignoreAttachedElements)
391                                                 _updateAttachedElements(this, hover, _timestamp(), this);
392                                 }
393                     }
394                 });
395
396 // ------------------------------ END jsPlumbUIComponent --------------------------------------------
397
398 // ------------------------------ BEGIN OverlayCapablejsPlumbUIComponent --------------------------------------------
399
400                 var _internalLabelOverlayId = "__label",
401                         // helper to get the index of some overlay
402                         _getOverlayIndex = function(component, id) {
403                                 var idx = -1;
404                                 for (var i = 0, j = component._jsPlumb.overlays.length; i < j; i++) {
405                                         if (id === component._jsPlumb.overlays[i].id) {
406                                                 idx = i;
407                                                 break;
408                                         }
409                                 }
410                                 return idx;
411                         },
412                         // this is a shortcut helper method to let people add a label as
413                         // overlay.                                             
414                         _makeLabelOverlay = function(component, params) {
415
416                                 var _params = {
417                                         cssClass:params.cssClass,
418                                         labelStyle : component.labelStyle,                                      
419                                         id:_internalLabelOverlayId,
420                                         component:component,
421                                         _jsPlumb:component._jsPlumb.instance  // TODO not necessary, since the instance can be accessed through the component.
422                                 },
423                                 mergedParams = jsPlumb.extend(_params, params);
424
425                                 return new jsPlumb.Overlays[component._jsPlumb.instance.getRenderMode()].Label( mergedParams );
426                         },
427                         _processOverlay = function(component, o) {
428                                 var _newOverlay = null;
429                                 if (_ju.isArray(o)) {   // this is for the shorthand ["Arrow", { width:50 }] syntax
430                                         // there's also a three arg version:
431                                         // ["Arrow", { width:50 }, {location:0.7}] 
432                                         // which merges the 3rd arg into the 2nd.
433                                         var type = o[0],
434                                                 // make a copy of the object so as not to mess up anyone else's reference...
435                                                 p = jsPlumb.extend({component:component, _jsPlumb:component._jsPlumb.instance}, o[1]);
436                                         if (o.length == 3) jsPlumb.extend(p, o[2]);
437                                         _newOverlay = new jsPlumb.Overlays[component._jsPlumb.instance.getRenderMode()][type](p);                                       
438                                 } else if (o.constructor == String) {
439                                         _newOverlay = new jsPlumb.Overlays[component._jsPlumb.instance.getRenderMode()][o]({component:component, _jsPlumb:component._jsPlumb.instance});
440                                 } else {
441                                         _newOverlay = o;
442                                 }                                                                               
443                                         
444                                 component._jsPlumb.overlays.push(_newOverlay);
445                         },
446                         _calculateOverlaysToAdd = function(component, params) {
447                                 var defaultKeys = component.defaultOverlayKeys || [], o = params.overlays,
448                                         checkKey = function(k) {
449                                                 return component._jsPlumb.instance.Defaults[k] || jsPlumb.Defaults[k] || [];
450                                         };
451                                 
452                                 if (!o) o = [];
453
454                                 for (var i = 0, j = defaultKeys.length; i < j; i++)
455                                         o.unshift.apply(o, checkKey(defaultKeys[i]));
456                                 
457                                 return o;
458                         },              
459                         OverlayCapableJsPlumbUIComponent = window.OverlayCapableJsPlumbUIComponent = function(params) {
460
461                                 jsPlumbUIComponent.apply(this, arguments);
462                                 this._jsPlumb.overlays = [];                    
463
464                                 var _overlays = _calculateOverlaysToAdd(this, params);
465                                 if (_overlays) {
466                                         for (var i = 0, j = _overlays.length; i < j; i++) {
467                                                 _processOverlay(this, _overlays[i]);
468                                         }
469                                 }
470                                 
471                                 if (params.label) {
472                                         var loc = params.labelLocation || this.defaultLabelLocation || 0.5,
473                                                 labelStyle = params.labelStyle || this._jsPlumb.instance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle;
474
475                                         this._jsPlumb.overlays.push(_makeLabelOverlay(this, {
476                                                 label:params.label,
477                                                 location:loc,
478                                                 labelStyle:labelStyle
479                                         }));
480                                 }                                                         
481                         };
482
483                 jsPlumbUtil.extend(OverlayCapableJsPlumbUIComponent, jsPlumbUIComponent, {
484                         applyType : function(t, doNotRepaint) {                 
485                                 this.removeAllOverlays(doNotRepaint);
486                                 if (t.overlays) {
487                                         for (var i = 0, j = t.overlays.length; i < j; i++)
488                                                 this.addOverlay(t.overlays[i], true);
489                                 }
490                         },
491                         setHover : function(hover, ignoreAttachedElements, timestamp) {            
492                                 if (this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) {
493                         for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++) {
494                                                 this._jsPlumb.overlays[i][hover ? "addClass":"removeClass"](this._jsPlumb.instance.hoverClass);
495                                         }
496                                 }
497             },
498             addOverlay : function(overlay, doNotRepaint) { 
499                                 _processOverlay(this, overlay); 
500                                 if (!doNotRepaint) this.repaint();
501                         },
502                         getOverlay : function(id) {
503                                 var idx = _getOverlayIndex(this, id);
504                                 return idx >= 0 ? this._jsPlumb.overlays[idx] : null;
505                         },                      
506                         getOverlays : function() {
507                                 return this._jsPlumb.overlays;
508                         },                      
509                         hideOverlay : function(id) {
510                                 var o = this.getOverlay(id);
511                                 if (o) o.hide();
512                         },
513                         hideOverlays : function() {
514                                 for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++)
515                                         this._jsPlumb.overlays[i].hide();
516                         },
517                         showOverlay : function(id) {
518                                 var o = this.getOverlay(id);
519                                 if (o) o.show();
520                         },
521                         showOverlays : function() {
522                                 for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++)
523                                         this._jsPlumb.overlays[i].show();
524                         },
525                         removeAllOverlays : function(doNotRepaint) {
526                                 for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++) {
527                                         if (this._jsPlumb.overlays[i].cleanup) this._jsPlumb.overlays[i].cleanup();
528                                 }
529
530                                 this._jsPlumb.overlays.splice(0, this._jsPlumb.overlays.length);
531                                 this._jsPlumb.overlayPositions = null;
532                                 if (!doNotRepaint)
533                                         this.repaint();
534                         },
535                         removeOverlay : function(overlayId) {
536                                 var idx = _getOverlayIndex(this, overlayId);
537                                 if (idx != -1) {
538                                         var o = this._jsPlumb.overlays[idx];
539                                         if (o.cleanup) o.cleanup();
540                                         this._jsPlumb.overlays.splice(idx, 1);
541                                         this._jsPlumb.overlayPositions && delete this._jsPlumb.overlayPositions[overlayId];
542                                 }
543                         },
544                         removeOverlays : function() {
545                                 for (var i = 0, j = arguments.length; i < j; i++)
546                                         this.removeOverlay(arguments[i]);
547                         },
548                         getLabel : function() {
549                                 var lo = this.getOverlay(_internalLabelOverlayId);
550                                 return lo != null ? lo.getLabel() : null;
551                         },              
552                         getLabelOverlay : function() {
553                                 return this.getOverlay(_internalLabelOverlayId);
554                         },
555                         setLabel : function(l) {
556                                 var lo = this.getOverlay(_internalLabelOverlayId);
557                                 if (!lo) {
558                                         var params = l.constructor == String || l.constructor == Function ? { label:l } : l;
559                                         lo = _makeLabelOverlay(this, params);   
560                                         this._jsPlumb.overlays.push(lo);
561                                 }
562                                 else {
563                                         if (l.constructor == String || l.constructor == Function) lo.setLabel(l);
564                                         else {
565                                                 if (l.label) lo.setLabel(l.label);
566                                                 if (l.location) lo.setLocation(l.location);
567                                         }
568                                 }
569                                 
570                                 if (!this._jsPlumb.instance.isSuspendDrawing()) 
571                                         this.repaint();
572                         },
573                         cleanup:function() {
574                                 for (var i = 0; i < this._jsPlumb.overlays.length; i++) {
575                                         this._jsPlumb.overlays[i].cleanup();
576                                         this._jsPlumb.overlays[i].destroy();
577                                 }
578                                 this._jsPlumb.overlays.splice(0);
579                                 this._jsPlumb.overlayPositions = null;
580                         },
581                         setVisible:function(v) {
582                                 this[v ? "showOverlays" : "hideOverlays"]();
583                         },
584                         setAbsoluteOverlayPosition:function(overlay, xy) {
585                                 this._jsPlumb.overlayPositions = this._jsPlumb.overlayPositions || {};
586                                 this._jsPlumb.overlayPositions[overlay.id] = xy;
587                         },
588                         getAbsoluteOverlayPosition:function(overlay) {
589                                 return this._jsPlumb.overlayPositions ? this._jsPlumb.overlayPositions[overlay.id] : null;
590                         }
591                 });             
592
593 // ------------------------------ END OverlayCapablejsPlumbUIComponent --------------------------------------------
594                 
595                 var _jsPlumbInstanceIndex = 0,
596                         getInstanceIndex = function() {
597                                 var i = _jsPlumbInstanceIndex + 1;
598                                 _jsPlumbInstanceIndex++;
599                                 return i;
600                         };
601
602                 var jsPlumbInstance = window.jsPlumbInstance = function(_defaults) {
603                                 
604                         this.Defaults = {
605                                 Anchor : "BottomCenter",
606                                 Anchors : [ null, null ],
607                     ConnectionsDetachable : true,
608                     ConnectionOverlays : [ ],
609                     Connector : "Bezier",
610                                 Container : null,
611                                 DoNotThrowErrors:false,
612                                 DragOptions : { },
613                                 DropOptions : { },
614                                 Endpoint : "Dot",
615                                 EndpointOverlays : [ ],
616                                 Endpoints : [ null, null ],
617                                 EndpointStyle : { fillStyle : "#456" },
618                                 EndpointStyles : [ null, null ],
619                                 EndpointHoverStyle : null,
620                                 EndpointHoverStyles : [ null, null ],
621                                 HoverPaintStyle : null,
622                                 LabelStyle : { color : "black" },
623                                 LogEnabled : false,
624                                 Overlays : [ ],
625                                 MaxConnections : 1, 
626                                 PaintStyle : { lineWidth : 8, strokeStyle : "#456" },            
627                                 ReattachConnections:false,
628                                 RenderMode : "svg",
629                                 Scope : "jsPlumb_DefaultScope"
630                         };
631                         if (_defaults) jsPlumb.extend(this.Defaults, _defaults);
632                 
633                         this.logEnabled = this.Defaults.LogEnabled;
634                         this._connectionTypes = {};
635                         this._endpointTypes = {};               
636
637                         jsPlumbUtil.EventGenerator.apply(this);
638
639                         var _currentInstance = this,
640                                 _instanceIndex = getInstanceIndex(),
641                                 _bb = _currentInstance.bind,
642                                 _initialDefaults = {},
643                     _zoom = 1,
644                     _info = function(el) {
645                         var _el = _dom(el);     
646                         return { el:_el, id:(jsPlumbUtil.isString(el) && _el == null) ? el : _getId(_el) };
647                     };
648             
649                 this.getInstanceIndex = function() { return _instanceIndex; };
650
651                 this.setZoom = function(z, repaintEverything) {
652                 _zoom = z;
653                 if (repaintEverything) _currentInstance.repaintEverything();
654                 };
655                 this.getZoom = function() { return _zoom; };
656                         
657                         for (var i in this.Defaults)
658                                 _initialDefaults[i] = this.Defaults[i];
659                         
660                         this.bind = function(event, fn) {               
661                                 if ("ready" === event && initialized) fn();
662                                 else _bb.apply(_currentInstance,[event, fn]);
663                         };
664
665                         _currentInstance.importDefaults = function(d) {
666                                 for (var i in d) {
667                                         _currentInstance.Defaults[i] = d[i];
668                                 }       
669                                 return _currentInstance;
670                         };              
671                         
672                         _currentInstance.restoreDefaults = function() {
673                                 _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults);
674                                 return _currentInstance;
675                         };
676                 
677                     var log = null,
678                         resizeTimer = null,
679                         initialized = false,
680                         // TODO remove from window scope       
681                         connections = [],
682                         // map of element id -> endpoint lists. an element can have an arbitrary
683                         // number of endpoints on it, and not all of them have to be connected
684                         // to anything.         
685                         endpointsByElement = {},
686                         endpointsByUUID = {},
687                         offsets = {},
688                         offsetTimestamps = {},
689                         floatingConnections = {},
690                         draggableStates = {},           
691                         connectionBeingDragged = false,
692                         sizes = [],
693                         _suspendDrawing = false,
694                         _suspendedAt = null,
695                         DEFAULT_SCOPE = this.Defaults.Scope,
696                         renderMode = null,  // will be set in init()            
697                         _curIdStamp = 1,
698                         _idstamp = function() { return "" + _curIdStamp++; },                                                   
699                 
700                                 //
701                                 // appends an element to some other element, which is calculated as follows:
702                                 // 
703                                 // 1. if _currentInstance.Defaults.Container exists, use that element.
704                                 // 2. if the 'parent' parameter exists, use that.
705                                 // 3. otherwise just use the root element (for DOM usage, the document body).
706                                 // 
707                                 //
708                                 _appendElement = function(el, parent) {
709                                         if (_currentInstance.Defaults.Container)
710                                                 jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container);
711                                         else if (!parent)
712                                                 jsPlumbAdapter.appendToRoot(el);
713                                         else
714                                                 jsPlumb.CurrentLibrary.appendElement(el, parent);
715                                 },              
716                                 
717                                 //
718                                 // YUI, for some reason, put the result of a Y.all call into an object that contains
719                                 // a '_nodes' array, instead of handing back an array-like object like the other
720                                 // libraries do.
721                                 //
722                                 _convertYUICollection = function(c) {
723                                         return c._nodes ? c._nodes : c;
724                                 },                
725
726                         //
727                         // Draws an endpoint and its connections. this is the main entry point into drawing connections as well
728                         // as endpoints, since jsPlumb is endpoint-centric under the hood.
729                         // 
730                         // @param element element to draw (of type library specific element object)
731                         // @param ui UI object from current library's event system. optional.
732                         // @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do.
733                         // @param clearEdits defaults to false; indicates that mouse edits for connectors should be cleared
734                         ///
735                         _draw = function(element, ui, timestamp, clearEdits) {
736
737                                 // TODO is it correct to filter by headless at this top level? how would a headless adapter ever repaint?
738                     if (!jsPlumbAdapter.headless && !_suspendDrawing) {
739                                     var id = _getId(element),
740                                         repaintEls = _currentInstance.dragManager.getElementsForDraggable(id);                      
741
742                                     if (timestamp == null) timestamp = _timestamp();
743
744                                     // update the offset of everything _before_ we try to draw anything.
745                                     var o = _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp });
746
747                                 if (repaintEls) {
748                                     for (var i in repaintEls) {                                                                                                                         
749                                         // TODO this seems to cause a lag, but we provide the offset, so in theory it 
750                                         // should not.  is the timestamp failing?
751                                                 _updateOffset( { 
752                                                         elId : repaintEls[i].id, 
753                                                         offset : {
754                                                                         left:o.o.left + repaintEls[i].offset.left,
755                                                                 top:o.o.top + repaintEls[i].offset.top
756                                                         }, 
757                                                         recalc : false, 
758                                                         timestamp : timestamp 
759                                                 });
760                                         }
761                                     }   
762                                                           
763
764                                     _currentInstance.anchorManager.redraw(id, ui, timestamp, null, clearEdits);
765                                     
766                                     if (repaintEls) {
767                                             for (var j in repaintEls) {
768                                                         _currentInstance.anchorManager.redraw(repaintEls[j].id, ui, timestamp, repaintEls[j].offset, clearEdits, true);                         
769                                             }
770                                         }               
771                     }
772                         },
773
774                         //
775                         // executes the given function against the given element if the first
776                         // argument is an object, or the list of elements, if the first argument
777                         // is a list. the function passed in takes (element, elementId) as
778                         // arguments.
779                         //
780                         _elementProxy = function(element, fn) {
781                                 var retVal = null, el, id;
782                                 if (_ju.isArray(element)) {
783                                         retVal = [];
784                                         for ( var i = 0, j = element.length; i < j; i++) {
785                                                 el = _gel(element[i]);
786                                                 id = _currentInstance.getAttribute(el, "id");
787                                                 retVal.push(fn(el, id)); // append return values to what we will return
788                                         }
789                                 } else {
790                                         el = _gel(element);
791                                         id = _currentInstance.getAttribute(el, "id");
792                                         retVal = fn(el, id);
793                                 }
794                                 return retVal;
795                         },                              
796
797                         //
798                         // gets an Endpoint by uuid.
799                         //
800                         _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; },
801
802                 /**
803                  * inits a draggable if it's not already initialised.
804                  * TODO: somehow abstract this to the adapter, because the concept of "draggable" has no
805                  * place on the server.
806                  */
807                 _initDraggableIfNecessary = function(element, isDraggable, dragOptions) {
808                         // TODO move to DragManager?
809                         if (!jsPlumbAdapter.headless) {
810                                 var _draggable = isDraggable == null ? false : isDraggable, jpcl = jsPlumb.CurrentLibrary;
811                                 if (_draggable) {
812                                         if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) {
813                                                 var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions;
814                                                 options = jsPlumb.extend( {}, options); // make a copy.
815                                                 var dragEvent = jpcl.dragEvents.drag,
816                                                         stopEvent = jpcl.dragEvents.stop,
817                                                         startEvent = jpcl.dragEvents.start;
818         
819                                                 options[startEvent] = _ju.wrap(options[startEvent], function() {
820                                                         _currentInstance.setHoverSuspended(true);                                                       
821                                                         _currentInstance.select({source:element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
822                                                         _currentInstance.select({target:element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
823                                                         _currentInstance.setConnectionBeingDragged(true);
824                                                 });
825         
826                                                 options[dragEvent] = _ju.wrap(options[dragEvent], function() {                            
827                                                         var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom());
828                                                         _draw(element, ui, null, true);
829                                                         _addClass(element, "jsPlumb_dragged");
830                                                 });
831                                                 options[stopEvent] = _ju.wrap(options[stopEvent], function() {
832                                                         var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom());
833                                                         _draw(element, ui);
834                                                         _removeClass(element, "jsPlumb_dragged");
835                                                         _currentInstance.setHoverSuspended(false);                                                      
836                                                         _currentInstance.select({source:element}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
837                                                         _currentInstance.select({target:element}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
838                                                         _currentInstance.setConnectionBeingDragged(false);
839                                                         _currentInstance.dragManager.dragEnded(element);
840                                                 });
841                                                 var elId = _getId(element); // need ID
842                                                 draggableStates[elId] = true;  
843                                                 var draggable = draggableStates[elId];
844                                                 options.disabled = draggable == null ? false : !draggable;
845                                                 jpcl.initDraggable(element, options, false, _currentInstance);
846                                                 _currentInstance.dragManager.register(element);
847                                         }
848                                 }
849                         }
850                 },
851                 
852                 /*
853                 * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc.
854                 */
855                 _prepareConnectionParams = function(params, referenceParams) {
856                         var _p = jsPlumb.extend( { }, params);
857                         if (referenceParams) jsPlumb.extend(_p, referenceParams);
858                         
859                         // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively.
860                         if (_p.source) {
861                                 if (_p.source.endpoint) 
862                                         _p.sourceEndpoint = _p.source;
863                                 else
864                                         _p.source = _dom(_p.source);
865                         }
866                         if (_p.target) {
867                                 if (_p.target.endpoint) 
868                                         _p.targetEndpoint = _p.target;
869                                 else
870                                         _p.target = _dom(_p.target);
871                         }
872                         
873                         // test for endpoint uuids to connect
874                         if (params.uuids) {
875                                 _p.sourceEndpoint = _getEndpoint(params.uuids[0]);
876                                 _p.targetEndpoint = _getEndpoint(params.uuids[1]);
877                         }                                               
878
879                         // now ensure that if we do have Endpoints already, they're not full.
880                         // source:
881                         if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) {
882                                 _ju.log(_currentInstance, "could not add connection; source endpoint is full");
883                                 return;
884                         }
885
886                         // target:
887                         if (_p.targetEndpoint && _p.targetEndpoint.isFull()) {
888                                 _ju.log(_currentInstance, "could not add connection; target endpoint is full");
889                                 return;
890                         }
891                         
892                         // if source endpoint mandates connection type and nothing specified in our params, use it.
893                         if (!_p.type && _p.sourceEndpoint)
894                                 _p.type = _p.sourceEndpoint.connectionType;
895                         
896                         // copy in any connectorOverlays that were specified on the source endpoint.
897                         // it doesnt copy target endpoint overlays.  i'm not sure if we want it to or not.
898                         if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) {
899                                 _p.overlays = _p.overlays || [];
900                                 for (var i = 0, j = _p.sourceEndpoint.connectorOverlays.length; i < j; i++) {
901                                         _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]);
902                                 }
903                         }               
904             
905             // pointer events
906             if (!_p["pointer-events"] && _p.sourceEndpoint && _p.sourceEndpoint.connectorPointerEvents)
907                 _p["pointer-events"] = _p.sourceEndpoint.connectorPointerEvents;
908                                                                         
909                         // if there's a target specified (which of course there should be), and there is no
910                         // target endpoint specified, and 'newConnection' was not set to true, then we check to
911                         // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and
912                         // we use those if so.  additionally, if the makeTarget call was specified with 'uniqueEndpoint' set
913                         // to true, then if that target endpoint has already been created, we re-use it.
914
915                         var tid, tep, existingUniqueEndpoint, newEndpoint;
916
917                         // TODO: this code can be refactored to be a little dry.
918                         if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) {
919                                 tid = _getId(_p.target);
920                                 tep =_targetEndpointDefinitions[tid];
921                                 existingUniqueEndpoint = _targetEndpoints[tid];                 
922
923                                 if (tep) {                      
924                                         // if target not enabled, return.
925                                         if (!_targetsEnabled[tid]) return;
926
927                                         // TODO this is dubious. i think it is there so that the endpoint can subsequently
928                                         // be dragged (ie it kicks off the draggable registration). but it is dubious.
929                                         tep.isTarget = true;
930
931                                         // check for max connections??                                          
932                                         newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep);
933                                         if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint;
934                                          _p.targetEndpoint = newEndpoint;
935                                          // TODO test options to makeTarget to see if we should do this?
936                                          newEndpoint._doNotDeleteOnDetach = false; // reset.
937                                          newEndpoint._deleteOnDetach = true;                                     
938                                 }
939                         }
940
941                         // same thing, but for source.
942                         if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) {
943                                 tid = _getId(_p.source);
944                                 tep = _sourceEndpointDefinitions[tid];
945                                 existingUniqueEndpoint = _sourceEndpoints[tid];                         
946
947                                 if (tep) {
948                                         // if source not enabled, return.                                       
949                                         if (!_sourcesEnabled[tid]) return;
950
951                                         // TODO this is dubious. i think it is there so that the endpoint can subsequently
952                                         // be dragged (ie it kicks off the draggable registration). but it is dubious.
953                                         //tep.isSource = true;
954                                 
955                                         newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep);
956                                         if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint;
957                                          _p.sourceEndpoint = newEndpoint;
958                                          // TODO test options to makeSource to see if we should do this?
959                                          newEndpoint._doNotDeleteOnDetach = false; // reset.
960                                          newEndpoint._deleteOnDetach = true;
961                                 }
962                         }
963                         
964                         return _p;
965                 },
966                 
967                 _newConnection = function(params) {
968                         var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
969                             endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint,
970                             parent = jsPlumb.CurrentLibrary.getParent;
971                         
972                         if (params.container)
973                                 params.parent = params.container;
974                         else {
975                                 if (params.sourceEndpoint)
976                                         params.parent = params.sourceEndpoint.parent;
977                                 else if (params.source.constructor == endpointFunc)
978                                         params.parent = params.source.parent;
979                                 else params.parent = parent(params.source);
980                         }
981                         
982                         params._jsPlumb = _currentInstance;
983             params.newConnection = _newConnection;
984             params.newEndpoint = _newEndpoint;
985             params.endpointsByUUID = endpointsByUUID;             
986             params.endpointsByElement = endpointsByElement;  
987             params.finaliseConnection = _finaliseConnection;
988                         var con = new connectionFunc(params);
989                         con.id = "con_" + _idstamp();
990                         _eventFireProxy("click", "click", con);
991                         _eventFireProxy("dblclick", "dblclick", con);
992             _eventFireProxy("contextmenu", "contextmenu", con);
993
994             // if the connection is draggable, then maybe we need to tell the target endpoint to init the
995             // dragging code. it won't run again if it already configured to be draggable.
996             if (con.isDetachable()) {
997                 con.endpoints[0].initDraggable();
998                 con.endpoints[1].initDraggable();
999             }
1000
1001                         return con;
1002                 },
1003                 
1004                 //
1005                 // adds the connection to the backing model, fires an event if necessary and then redraws
1006                 //
1007                 _finaliseConnection = function(jpc, params, originalEvent, doInformAnchorManager) {
1008             params = params || {};
1009                         // add to list of connections (by scope).
1010             if (!jpc.suspendedEndpoint)
1011                             connections.push(jpc);
1012                         
1013             // always inform the anchor manager
1014             // except that if jpc has a suspended endpoint it's not true to say the
1015             // connection is new; it has just (possibly) moved. the question is whether
1016             // to make that call here or in the anchor manager.  i think perhaps here.
1017             if (jpc.suspendedEndpoint == null || doInformAnchorManager)
1018                 _currentInstance.anchorManager.newConnection(jpc);
1019
1020                         // force a paint
1021                         _draw(jpc.source);
1022                         
1023                         // fire an event
1024                         if (!params.doNotFireConnectionEvent && params.fireEvent !== false) {
1025                         
1026                                 var eventArgs = {
1027                                         connection:jpc,
1028                                         source : jpc.source, target : jpc.target,
1029                                         sourceId : jpc.sourceId, targetId : jpc.targetId,
1030                                         sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
1031                                 };
1032                         
1033                                 _currentInstance.fire("connection", eventArgs, originalEvent);
1034                         }
1035                 },
1036                 
1037                 _eventFireProxy = function(event, proxyEvent, obj) {
1038                         obj.bind(event, function(originalObject, originalEvent) {
1039                                 _currentInstance.fire(proxyEvent, obj, originalEvent);
1040                         });
1041                 },
1042                 
1043                 /*
1044                  * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added.
1045                  * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb.
1046                  * 
1047                  *   the logic is to first look for a "container" member of params, and pass that back if found.  otherwise we
1048                  *   handoff to the 'getParent' function in the current library.
1049                  */
1050                 _getParentFromParams = function(params) {
1051                         if (params.container)
1052                                 return params.container;
1053                         else {
1054                 var tag = jsPlumb.CurrentLibrary.getTagName(params.source),
1055                     p = jsPlumb.CurrentLibrary.getParent(params.source);
1056                 if (tag && tag.toLowerCase() === "td")
1057                     return jsPlumb.CurrentLibrary.getParent(p);
1058                 else return p;
1059             }
1060                 },
1061                 
1062                 /*
1063                         factory method to prepare a new endpoint.  this should always be used instead of creating Endpoints
1064                         manually, since this method attaches event listeners and an id.
1065                 */
1066                 _newEndpoint = function(params) {
1067                                 var endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint;
1068                                 var _p = jsPlumb.extend({}, params);                            
1069                                 _p.parent = _getParentFromParams(_p);
1070                                 _p._jsPlumb = _currentInstance;
1071                 _p.newConnection = _newConnection;
1072                 _p.newEndpoint = _newEndpoint;                
1073                 _p.endpointsByUUID = endpointsByUUID;             
1074                 _p.endpointsByElement = endpointsByElement;  
1075                 _p.finaliseConnection = _finaliseConnection;
1076                 _p.fireDetachEvent = fireDetachEvent;
1077                 _p.fireMoveEvent = fireMoveEvent;
1078                 _p.floatingConnections = floatingConnections;
1079                 _p.getParentFromParams = _getParentFromParams;
1080                 _p.elementId = _getId(_p.source);                
1081                                 var ep = new endpointFunc(_p);                  
1082                                 ep.id = "ep_" + _idstamp();
1083                                 _eventFireProxy("click", "endpointClick", ep);
1084                                 _eventFireProxy("dblclick", "endpointDblClick", ep);
1085                                 _eventFireProxy("contextmenu", "contextmenu", ep);
1086                                 if (!jsPlumbAdapter.headless)
1087                                         _currentInstance.dragManager.endpointAdded(_p.source);
1088                         return ep;
1089                 },
1090                 
1091                 /*
1092                  * performs the given function operation on all the connections found
1093                  * for the given element id; this means we find all the endpoints for
1094                  * the given element, and then for each endpoint find the connectors
1095                  * connected to it. then we pass each connection in to the given
1096                  * function.             
1097                  */
1098                 _operation = function(elId, func, endpointFunc) {
1099                         var endpoints = endpointsByElement[elId];
1100                         if (endpoints && endpoints.length) {
1101                                 for ( var i = 0, ii = endpoints.length; i < ii; i++) {
1102                                         for ( var j = 0, jj = endpoints[i].connections.length; j < jj; j++) {
1103                                                 var retVal = func(endpoints[i].connections[j]);
1104                                                 // if the function passed in returns true, we exit.
1105                                                 // most functions return false.
1106                                                 if (retVal) return;
1107                                         }
1108                                         if (endpointFunc) endpointFunc(endpoints[i]);
1109                                 }
1110                         }
1111                 },      
1112                 
1113                 _setDraggable = function(element, draggable) {
1114                         return _elementProxy(element, function(el, id) {
1115                                 draggableStates[id] = draggable;
1116                                 if (jsPlumb.CurrentLibrary.isDragSupported(el)) {
1117                                         jsPlumb.CurrentLibrary.setDraggable(el, draggable);
1118                                 }
1119                         });
1120                 },
1121                 /*
1122                  * private method to do the business of hiding/showing.
1123                  * 
1124                  * @param el
1125                  *            either Id of the element in question or a library specific
1126                  *            object for the element.
1127                  * @param state
1128                  *            String specifying a value for the css 'display' property
1129                  *            ('block' or 'none').
1130                  */
1131                 _setVisible = function(el, state, alsoChangeEndpoints) {
1132                         state = state === "block";
1133                         var endpointFunc = null;
1134                         if (alsoChangeEndpoints) {
1135                                 if (state) endpointFunc = function(ep) {
1136                                         ep.setVisible(true, true, true);
1137                                 };
1138                                 else endpointFunc = function(ep) {
1139                                         ep.setVisible(false, true, true);
1140                                 };
1141                         }
1142                         var info = _info(el);
1143                         _operation(info.id, function(jpc) {
1144                                 if (state && alsoChangeEndpoints) {             
1145                                         // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility.
1146                                         // this block will only set a connection to be visible if the other endpoint in the connection is also visible.
1147                                         var oidx = jpc.sourceId === info.id ? 1 : 0;
1148                                         if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true);
1149                                 }
1150                                 else  // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever.
1151                                         jpc.setVisible(state);
1152                         }, endpointFunc);
1153                 },
1154                 /*
1155                  * toggles the draggable state of the given element(s).
1156                  * el is either an id, or an element object, or a list of ids/element objects.
1157                  */
1158                 _toggleDraggable = function(el) {
1159                         return _elementProxy(el, function(el, elId) {
1160                                 var state = draggableStates[elId] == null ? false : draggableStates[elId];
1161                                 state = !state;
1162                                 draggableStates[elId] = state;
1163                                 jsPlumb.CurrentLibrary.setDraggable(el, state);
1164                                 return state;
1165                         });
1166                 },
1167                 /**
1168                  * private method to do the business of toggling hiding/showing.
1169                  */
1170                 _toggleVisible = function(elId, changeEndpoints) {
1171                         var endpointFunc = null;
1172                         if (changeEndpoints) {
1173                                 endpointFunc = function(ep) {
1174                                         var state = ep.isVisible();
1175                                         ep.setVisible(!state);
1176                                 };
1177                         }
1178                         _operation(elId, function(jpc) {
1179                                 var state = jpc.isVisible();
1180                                 jpc.setVisible(!state);                         
1181                         }, endpointFunc);
1182                         // todo this should call _elementProxy, and pass in the
1183                         // _operation(elId, f) call as a function. cos _toggleDraggable does
1184                         // that.
1185                 },
1186                 /**
1187                  * updates the offset and size for a given element, and stores the
1188                  * values. if 'offset' is not null we use that (it would have been
1189                  * passed in from a drag call) because it's faster; but if it is null,
1190                  * or if 'recalc' is true in order to force a recalculation, we get the current values.
1191                  */
1192                 _updateOffset = function(params) {
1193                         var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId, s;
1194                         if (_suspendDrawing && !timestamp) timestamp = _suspendedAt;
1195                         if (!recalc) {
1196                                 if (timestamp && timestamp === offsetTimestamps[elId]) {                        
1197                                         return {o:params.offset || offsets[elId], s:sizes[elId]};
1198                                 }
1199                         }                       
1200                         if (recalc || !offset) { // if forced repaint or no offset available, we recalculate.
1201                                 // get the current size and offset, and store them
1202                                 s = _gel(elId);
1203                                 if (s != null) {                                                
1204                                         sizes[elId] = _getSize(s);
1205                                         offsets[elId] = _getOffset(s, _currentInstance);
1206                                         offsetTimestamps[elId] = timestamp;
1207                                 }
1208                         } else {
1209                                 offsets[elId] = offset;
1210                 if (sizes[elId] == null) {
1211                     s = _gel(elId);
1212                     if (s != null) sizes[elId] = _getSize(s);
1213                 }
1214                 offsetTimestamps[elId] = timestamp;
1215             }
1216                         
1217                         if(offsets[elId] && !offsets[elId].right) {
1218                                 offsets[elId].right = offsets[elId].left + sizes[elId][0];
1219                                 offsets[elId].bottom = offsets[elId].top + sizes[elId][1];      
1220                                 offsets[elId].width = sizes[elId][0];
1221                                 offsets[elId].height = sizes[elId][1];  
1222                                 offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2);
1223                                 offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2);                         
1224                         }
1225                         return {o:offsets[elId], s:sizes[elId]};
1226                 },
1227
1228                 // TODO comparison performance
1229                 _getCachedData = function(elId) {
1230                         var o = offsets[elId];
1231                         if (!o) 
1232                 return _updateOffset({elId:elId});
1233                         else
1234                 return {o:o, s:sizes[elId]};
1235                 },
1236
1237                 /**
1238                  * gets an id for the given element, creating and setting one if
1239                  * necessary.  the id is of the form
1240                  *
1241                  *      jsPlumb_<instance index>_<index in instance>
1242                  *
1243                  * where "index in instance" is a monotonically increasing integer that starts at 0,
1244                  * for each instance.  this method is used not only to assign ids to elements that do not
1245                  * have them but also to connections and endpoints.
1246                  */
1247                 _getId = function(element, uuid, doNotCreateIfNotFound) {
1248                         if (jsPlumbUtil.isString(element)) return element;                      
1249                         if (element == null) return null;                       
1250                         var id = jsPlumbAdapter.getAttribute(element, "id");
1251                         if (!id || id === "undefined") {
1252                                 // check if fixed uuid parameter is given
1253                                 if (arguments.length == 2 && arguments[1] !== undefined)
1254                                         id = uuid;
1255                                 else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2]))
1256                                         id = "jsPlumb_" + _instanceIndex + "_" + _idstamp();
1257                                 
1258                 if (!doNotCreateIfNotFound) jsPlumbAdapter.setAttribute(element, "id", id);
1259                         }
1260                         return id;
1261                 };
1262
1263                 this.setConnectionBeingDragged = function(v) {
1264                         connectionBeingDragged = v;
1265                 };
1266                 this.isConnectionBeingDragged = function() {
1267                         return connectionBeingDragged;
1268                 };
1269     
1270                 this.connectorClass = "_jsPlumb_connector";                     
1271                 this.hoverClass = "_jsPlumb_hover";                     
1272                 this.endpointClass = "_jsPlumb_endpoint";               
1273                 this.endpointConnectedClass = "_jsPlumb_endpoint_connected";            
1274                 this.endpointFullClass = "_jsPlumb_endpoint_full";              
1275                 this.endpointDropAllowedClass = "_jsPlumb_endpoint_drop_allowed";               
1276                 this.endpointDropForbiddenClass = "_jsPlumb_endpoint_drop_forbidden";           
1277                 this.overlayClass = "_jsPlumb_overlay";                         
1278                 this.draggingClass = "_jsPlumb_dragging";               
1279                 this.elementDraggingClass = "_jsPlumb_element_dragging";                        
1280                 this.sourceElementDraggingClass = "_jsPlumb_source_element_dragging";
1281                 this.targetElementDraggingClass = "_jsPlumb_target_element_dragging";
1282                 this.endpointAnchorClassPrefix = "_jsPlumb_endpoint_anchor";
1283                 this.hoverSourceClass = "_jsPlumb_source_hover";        
1284                 this.hoverTargetClass = "_jsPlumb_target_hover";
1285                 this.dragSelectClass = "_jsPlumb_drag_select";
1286
1287                 this.Anchors = {};              
1288                 this.Connectors = {  "canvas":{}, "svg":{}, "vml":{} };                         
1289                 this.Endpoints = { "canvas":{}, "svg":{}, "vml":{} };
1290                 this.Overlays = { "canvas":{}, "svg":{}, "vml":{}};             
1291                 this.ConnectorRenderers = {};                           
1292                 this.SVG = "svg";
1293                 this.CANVAS = "canvas";         
1294                 this.VML = "vml";
1295                                 
1296
1297 // --------------------------- jsPLumbInstance public API ---------------------------------------------------------
1298                                         
1299                 
1300                 this.addEndpoint = function(el, params, referenceParams) {
1301                         referenceParams = referenceParams || {};
1302                         var p = jsPlumb.extend({}, referenceParams);
1303                         jsPlumb.extend(p, params);
1304                         p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint;
1305                         p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
1306             // YUI wrapper
1307                         el = _convertYUICollection(el);                                                 
1308
1309                         var results = [], 
1310                                 inputs = (_ju.isArray(el) || (el.length != null && !_ju.isString(el))) ? el : [ el ];
1311                                                 
1312                         for (var i = 0, j = inputs.length; i < j; i++) {
1313                                 var _el = _dom(inputs[i]), id = _getId(_el);
1314                                 p.source = _el;
1315
1316                 _updateOffset({ elId : id, timestamp:_suspendedAt });
1317                                 var e = _newEndpoint(p);
1318                                 if (p.parentAnchor) e.parentAnchor = p.parentAnchor;
1319                                 _ju.addToList(endpointsByElement, id, e);
1320                                 var myOffset = offsets[id], 
1321                                         myWH = sizes[id],
1322                                         anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e, timestamp:_suspendedAt }),
1323                                         endpointPaintParams = { anchorLoc : anchorLoc, timestamp:_suspendedAt };
1324                                 
1325                                 if (_suspendDrawing) endpointPaintParams.recalc = false;
1326                                 if (!_suspendDrawing) e.paint(endpointPaintParams);
1327                                 
1328                                 results.push(e);
1329                                 e._doNotDeleteOnDetach = true; // mark this as being added via addEndpoint.                             
1330                         }
1331                         
1332                         return results.length == 1 ? results[0] : results;
1333                 };
1334                 
1335                 
1336                 this.addEndpoints = function(el, endpoints, referenceParams) {
1337                         var results = [];
1338                         for ( var i = 0, j = endpoints.length; i < j; i++) {
1339                                 var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams);
1340                                 if (_ju.isArray(e))
1341                                         Array.prototype.push.apply(results, e);
1342                                 else results.push(e);
1343                         }
1344                         return results;
1345                 };
1346                 
1347                 this.animate = function(el, properties, options) {
1348                         options = options || {};
1349                         var ele = _gel(el), 
1350                                 id = _getId(el),
1351                                 stepFunction = jsPlumb.CurrentLibrary.dragEvents.step,
1352                                 completeFunction = jsPlumb.CurrentLibrary.dragEvents.complete;
1353
1354                         options[stepFunction] = _ju.wrap(options[stepFunction], function() {
1355                                 _currentInstance.repaint(id);
1356                         });
1357
1358                         // onComplete repaints, just to make sure everything looks good at the end of the animation.
1359                         options[completeFunction] = _ju.wrap(options[completeFunction], function() {
1360                                 _currentInstance.repaint(id);
1361                         });
1362
1363                         jsPlumb.CurrentLibrary.animate(ele, properties, options);
1364                 };              
1365                 
1366                 /**
1367                 * checks for a listener for the given condition, executing it if found, passing in the given value.
1368                 * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since
1369                 * firing click events etc is a bit different to what this does).  i thought about adding a "bindCondition"
1370                 * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these
1371                 * condition events anyway.
1372                 */
1373                 this.checkCondition = function(conditionName, value) {
1374                         var l = _currentInstance.getListener(conditionName),
1375                                 r = true;
1376                                 
1377                         if (l && l.length > 0) {
1378                                 try {
1379                                         for (var i = 0, j = l.length; i < j; i++) {
1380                                                 r = r && l[i](value); 
1381                                         }
1382                                 }
1383                                 catch (e) { 
1384                                         _ju.log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); 
1385                                 }
1386                         }
1387                         return r;
1388                 };
1389                 
1390                 /**
1391                  * checks a condition asynchronously: fires the event handler and passes the handler
1392                  * a 'proceed' function and a 'stop' function. The handler MUST execute one or other
1393                  * of these once it has made up its mind.
1394                  *
1395                  * Note that although this reads the listener list for the given condition, it
1396                  * does not loop through and hit each listener, because that, with asynchronous
1397                  * callbacks, would be messy. so it uses only the first listener registered.
1398                  */ 
1399                 this.checkASyncCondition = function(conditionName, value, proceed, stop) {
1400                         var l = _currentInstance.getListener(conditionName);
1401                                 
1402                         if (l && l.length > 0) {
1403                                 try {
1404                                         l[0](value, proceed, stop);                                     
1405                                 }
1406                                 catch (e) { 
1407                                         _ju.log(_currentInstance, "cannot asynchronously check condition [" + conditionName + "]" + e); 
1408                                 }
1409                         }       
1410                 };
1411
1412                 
1413                 this.connect = function(params, referenceParams) {
1414                         // prepare a final set of parameters to create connection with
1415                         var _p = _prepareConnectionParams(params, referenceParams), jpc;
1416                         // TODO probably a nicer return value if the connection was not made.  _prepareConnectionParams
1417                         // will return null (and log something) if either endpoint was full.  what would be nicer is to 
1418                         // create a dedicated 'error' object.
1419                         if (_p) {
1420                                 // create the connection.  it is not yet registered 
1421                                 jpc = _newConnection(_p);
1422                                 // now add it the model, fire an event, and redraw
1423                                 _finaliseConnection(jpc, _p);                                                                           
1424                         }
1425                         return jpc;
1426                 };              
1427                 
1428                 this.deleteEndpoint = function(object, doNotRepaintAfterwards) {
1429                         var _is = _currentInstance.setSuspendDrawing(true);
1430                         var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object;                  
1431                         if (endpoint) {         
1432                                 _currentInstance.deleteObject({
1433                                         endpoint:endpoint
1434                                 });
1435                         }
1436                         if(!_is) _currentInstance.setSuspendDrawing(false, doNotRepaintAfterwards);
1437                         return _currentInstance;                                                                        
1438                 };              
1439                 
1440                 this.deleteEveryEndpoint = function() {
1441                         var _is = _currentInstance.setSuspendDrawing(true);
1442                         for ( var id in endpointsByElement) {
1443                                 var endpoints = endpointsByElement[id];
1444                                 if (endpoints && endpoints.length) {
1445                                         for ( var i = 0, j = endpoints.length; i < j; i++) {
1446                                                 _currentInstance.deleteEndpoint(endpoints[i], true);
1447                                         }
1448                                 }
1449                         }                       
1450                         endpointsByElement = {};                        
1451                         endpointsByUUID = {};
1452                         _currentInstance.anchorManager.reset();
1453                         _currentInstance.dragManager.reset();                                                   
1454                         if(!_is) _currentInstance.setSuspendDrawing(false);
1455                         return _currentInstance;
1456                 };
1457
1458                 var fireDetachEvent = function(jpc, doFireEvent, originalEvent) {
1459             // may have been given a connection, or in special cases, an object
1460             var connType =  _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
1461                 argIsConnection = jpc.constructor == connType,
1462                 params = argIsConnection ? {
1463                     connection:jpc,
1464                                     source : jpc.source, target : jpc.target,
1465                                     sourceId : jpc.sourceId, targetId : jpc.targetId,
1466                                     sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
1467                 } : jpc;
1468
1469                         if (doFireEvent)
1470                                 _currentInstance.fire("connectionDetached", params, originalEvent);
1471                         
1472             _currentInstance.anchorManager.connectionDetached(params);
1473                 };      
1474
1475                 var fireMoveEvent = function(params, evt) {
1476                         _currentInstance.fire("connectionMoved", params, evt);
1477                 };
1478
1479                 this.unregisterEndpoint = function(endpoint) {
1480                         if (endpoint._jsPlumb.uuid) endpointsByUUID[endpoint._jsPlumb.uuid] = null;                             
1481                         _currentInstance.anchorManager.deleteEndpoint(endpoint);                        
1482                         // TODO at least replace this with a removeWithFunction call.                   
1483                         for (var e in endpointsByElement) {
1484                                 var endpoints = endpointsByElement[e];
1485                                 if (endpoints) {
1486                                         var newEndpoints = [];
1487                                         for (var i = 0, j = endpoints.length; i < j; i++)
1488                                                 if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]);
1489                                         
1490                                         endpointsByElement[e] = newEndpoints;
1491                                 }
1492                                 if(endpointsByElement[e].length <1){
1493                                         delete endpointsByElement[e];
1494                                 }
1495                         }
1496                 };
1497                                 
1498                 this.detach = function() {
1499
1500             if (arguments.length === 0) return;
1501             var connType =  _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
1502                 firstArgIsConnection = arguments[0].constructor == connType,
1503                 params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0],
1504                 fireEvent = (params.fireEvent !== false),
1505                 forceDetach = params.forceDetach,
1506                 conn = firstArgIsConnection ? arguments[0] : params.connection;
1507                                                     
1508                                 if (conn) {             
1509                     if (forceDetach || jsPlumbUtil.functionChain(true, false, [
1510                             [ conn.endpoints[0], "isDetachAllowed", [ conn ] ],    
1511                             [ conn.endpoints[1], "isDetachAllowed", [ conn ] ],
1512                             [ conn, "isDetachAllowed", [ conn ] ],
1513                             [ _currentInstance, "checkCondition", [ "beforeDetach", conn ] ] ])) {
1514                         
1515                         conn.endpoints[0].detach(conn, false, true, fireEvent); 
1516                     }
1517                 }
1518                 else {
1519                                         var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case.
1520                                         // test for endpoint uuids to detach
1521                                         if (_p.uuids) {
1522                                                 _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent);
1523                                         } else if (_p.sourceEndpoint && _p.targetEndpoint) {
1524                                                 _p.sourceEndpoint.detachFrom(_p.targetEndpoint);
1525                                         } else {
1526                                                 var sourceId = _getId(_dom(_p.source)),
1527                                                     targetId = _getId(_dom(_p.target));
1528                                                 _operation(sourceId, function(jpc) {
1529                                                     if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) {
1530                                                             if (_currentInstance.checkCondition("beforeDetach", jpc)) {
1531                                     jpc.endpoints[0].detach(jpc, false, true, fireEvent);
1532                                                                 }
1533                                                         }
1534                                                 });
1535                                         }
1536                                 }
1537                 };
1538
1539                 this.detachAllConnections = function(el, params) {
1540             params = params || {};
1541             el = _dom(el);
1542                         var id = _getId(el),
1543                 endpoints = endpointsByElement[id];
1544                         if (endpoints && endpoints.length) {
1545                                 for ( var i = 0, j = endpoints.length; i < j; i++) {
1546                                         endpoints[i].detachAll(params.fireEvent !== false);
1547                                 }
1548                         }
1549                         return _currentInstance;
1550                 };
1551
1552                 this.detachEveryConnection = function(params) {
1553             params = params || {};
1554             _currentInstance.doWhileSuspended(function() {
1555                                 for ( var id in endpointsByElement) {
1556                                         var endpoints = endpointsByElement[id];
1557                                         if (endpoints && endpoints.length) {
1558                                                 for ( var i = 0, j = endpoints.length; i < j; i++) {
1559                                                         endpoints[i].detachAll(params.fireEvent !== false);
1560                                                 }
1561                                         }
1562                                 }
1563                                 connections.splice(0);
1564                         });
1565                         return _currentInstance;
1566                 };
1567
1568                 /// not public.  but of course its exposed. how to change this.
1569                 this.deleteObject = function(params) {
1570                         var result = {
1571                                         endpoints : {}, 
1572                                         connections : {},
1573                                         endpointCount:0,
1574                                         connectionCount:0
1575                                 },
1576                                 fireEvent = params.fireEvent !== false,
1577                                 deleteAttachedObjects = params.deleteAttachedObjects !== false;
1578
1579                         var unravelConnection = function(connection) {
1580                                 if(connection != null && result.connections[connection.id] == null) {
1581                                         if (connection._jsPlumb != null) connection.setHover(false);
1582                                         result.connections[connection.id] = connection;
1583                                         result.connectionCount++;
1584                                         if (deleteAttachedObjects) {
1585                                                 for (var j = 0; j < connection.endpoints.length; j++) {
1586                                                         if (connection.endpoints[j]._deleteOnDetach)
1587                                                                 unravelEndpoint(connection.endpoints[j]);
1588                                                 }
1589                                         }                                       
1590                                 }
1591                         };
1592                         var unravelEndpoint = function(endpoint) {
1593                                 if(endpoint != null && result.endpoints[endpoint.id] == null) {
1594                                         if (endpoint._jsPlumb != null) endpoint.setHover(false);
1595                                         result.endpoints[endpoint.id] = endpoint;
1596                                         result.endpointCount++;
1597
1598                                         if (deleteAttachedObjects) {
1599                                                 for (var i = 0; i < endpoint.connections.length; i++) {
1600                                                         var c = endpoint.connections[i];
1601                                                         unravelConnection(c);                                           
1602                                                 }
1603                                         }
1604                                 }
1605                         };
1606
1607                         if (params.connection) 
1608                                 unravelConnection(params.connection);
1609                         else unravelEndpoint(params.endpoint);
1610
1611                         // loop through connections
1612                         for (var i in result.connections) {
1613                                 var c = result.connections[i];
1614                                 c.endpoints[0].detachFromConnection(c);
1615                                 c.endpoints[1].detachFromConnection(c);
1616                                 //_currentInstance.unregisterConnection(c);
1617                                 jsPlumbUtil.removeWithFunction(connections, function(_c) {
1618                                     return c.id == _c.id;
1619                                 });
1620                                 fireDetachEvent(c, fireEvent, params.originalEvent);
1621                                 c.cleanup();
1622                                 c.destroy();
1623                         }
1624
1625                         // loop through endpoints
1626                         for (var j in result.endpoints) {
1627                                 var e = result.endpoints[j];    
1628                                 _currentInstance.unregisterEndpoint(e);
1629                                 // FIRE some endpoint deleted event?
1630                                 e.cleanup();
1631                                 e.destroy();
1632                         }       
1633
1634                         return result;
1635                 };
1636  
1637                 this.draggable = function(el, options) {
1638                         var i,j,ele;
1639                         // allows for array or jquery/mootools selector
1640                         if (typeof el == 'object' && el.length) {
1641                                 for (i = 0, j = el.length; i < j; i++) {
1642                                         ele = _dom(el[i]);
1643                                         if (ele) _initDraggableIfNecessary(ele, true, options);
1644                                 }
1645                         } 
1646                         // allows for YUI selector
1647                         else if (el._nodes) {   // TODO this is YUI specific; really the logic should be forced
1648                                 // into the library adapters (for jquery and mootools aswell)
1649                                 for (i = 0, j = el._nodes.length; i < j; i++) {
1650                                         ele = _dom(el._nodes[i]);
1651                                         if (ele) _initDraggableIfNecessary(ele, true, options);
1652                                 }
1653                         }
1654                         else {                          
1655                                 ele = _dom(el);
1656                                 if (ele) _initDraggableIfNecessary(ele, true, options);
1657                         }
1658                         return _currentInstance;
1659                 };
1660
1661
1662                 // just a library-agnostic wrapper.
1663                 this.extend = function(o1, o2) {
1664                         return jsPlumb.CurrentLibrary.extend(o1, o2);
1665                 };
1666
1667                 // helpers for select/selectEndpoints
1668                 var _setOperation = function(list, func, args, selector) {
1669                                 for (var i = 0, j = list.length; i < j; i++) {
1670                                         list[i][func].apply(list[i], args);
1671                                 }       
1672                                 return selector(list);
1673                         },
1674                         _getOperation = function(list, func, args) {
1675                                 var out = [];
1676                                 for (var i = 0, j = list.length; i < j; i++) {                                  
1677                                         out.push([ list[i][func].apply(list[i], args), list[i] ]);
1678                                 }       
1679                                 return out;
1680                         },
1681                         setter = function(list, func, selector) {
1682                                 return function() {
1683                                         return _setOperation(list, func, arguments, selector);
1684                                 };
1685                         },
1686                         getter = function(list, func) {
1687                                 return function() {
1688                                         return _getOperation(list, func, arguments);
1689                                 };      
1690                         },
1691                         prepareList = function(input, doNotGetIds) {
1692                                 var r = [];
1693                                 if (input) {
1694                                         if (typeof input == 'string') {
1695                                                 if (input === "*") return input;
1696                                                 r.push(input);
1697                                         }
1698                                         else {
1699                                                 input = _gel(input);
1700                                                 if (doNotGetIds) r = input;
1701                                                 else { 
1702                                                         for (var i = 0, j = input.length; i < j; i++) 
1703                                                                 r.push(_info(input[i]).id);
1704                                                 }       
1705                                         }
1706                                 }
1707                                 return r;
1708                         },
1709                         filterList = function(list, value, missingIsFalse) {
1710                                 if (list === "*") return true;
1711                                 return list.length > 0 ? jsPlumbUtil.indexOf(list, value) != -1 : !missingIsFalse;
1712                         };
1713
1714                 // get some connections, specifying source/target/scope
1715                 this.getConnections = function(options, flat) {
1716                         if (!options) {
1717                                 options = {};
1718                         } else if (options.constructor == String) {
1719                                 options = { "scope": options };
1720                         }
1721                         var scope = options.scope || _currentInstance.getDefaultScope(),
1722                                 scopes = prepareList(scope, true),
1723                                 sources = prepareList(options.source),
1724                                 targets = prepareList(options.target),                  
1725                                 results = (!flat && scopes.length > 1) ? {} : [],
1726                                 _addOne = function(scope, obj) {
1727                                         if (!flat && scopes.length > 1) {
1728                                                 var ss = results[scope];
1729                                                 if (ss == null) {
1730                                                         ss = results[scope] = [];
1731                                                 }
1732                                                 ss.push(obj);
1733                                         } else results.push(obj);
1734                                 };
1735                         
1736                         for ( var j = 0, jj = connections.length; j < jj; j++) {
1737                                 var c = connections[j];
1738                                 if (filterList(scopes, c.scope) && filterList(sources, c.sourceId) && filterList(targets, c.targetId))
1739                                         _addOne(c.scope, c);
1740                         }
1741                         
1742                         return results;
1743                 };
1744                 
1745                 var _curryEach = function(list, executor) {
1746                                 return function(f) {
1747                                         for (var i = 0, ii = list.length; i < ii; i++) {
1748                                                 f(list[i]);
1749                                         }
1750                                         return executor(list);
1751                                 };              
1752                         },
1753                         _curryGet = function(list) {
1754                                 return function(idx) {
1755                                         return list[idx];
1756                                 };
1757                         };
1758                         
1759                 var _makeCommonSelectHandler = function(list, executor) {
1760             var out = {
1761                     length:list.length,
1762                                     each:_curryEach(list, executor),
1763                                     get:_curryGet(list)
1764                 },
1765                 setters = ["setHover", "removeAllOverlays", "setLabel", "addClass", "addOverlay", "removeOverlay", 
1766                            "removeOverlays", "showOverlay", "hideOverlay", "showOverlays", "hideOverlays", "setPaintStyle",
1767                            "setHoverPaintStyle", "setSuspendEvents", "setParameter", "setParameters", "setVisible", 
1768                            "repaint", "addType", "toggleType", "removeType", "removeClass", "setType", "bind", "unbind" ],
1769                 
1770                 getters = ["getLabel", "getOverlay", "isHover", "getParameter", "getParameters", "getPaintStyle",
1771                            "getHoverPaintStyle", "isVisible", "hasType", "getType", "isSuspendEvents" ],
1772                 i, ii;
1773             
1774             for (i = 0, ii = setters.length; i < ii; i++)
1775                 out[setters[i]] = setter(list, setters[i], executor);
1776             
1777             for (i = 0, ii = getters.length; i < ii; i++)
1778                 out[getters[i]] = getter(list, getters[i]);       
1779             
1780             return out;
1781                 };
1782                 
1783                 var     _makeConnectionSelectHandler = function(list) {
1784                         var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler);
1785                         return jsPlumb.CurrentLibrary.extend(common, {
1786                                 // setters                                                                      
1787                                 setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler),
1788                                 setReattach:setter(list, "setReattach", _makeConnectionSelectHandler),
1789                                 setConnector:setter(list, "setConnector", _makeConnectionSelectHandler),                        
1790                                 detach:function() {
1791                                         for (var i = 0, ii = list.length; i < ii; i++)
1792                                                 _currentInstance.detach(list[i]);
1793                                 },                              
1794                                 // getters
1795                                 isDetachable:getter(list, "isDetachable"),
1796                                 isReattach:getter(list, "isReattach")
1797                         });
1798                 };
1799                 
1800                 var     _makeEndpointSelectHandler = function(list) {
1801                         var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler);
1802                         return jsPlumb.CurrentLibrary.extend(common, {
1803                                 setEnabled:setter(list, "setEnabled", _makeEndpointSelectHandler),                              
1804                                 setAnchor:setter(list, "setAnchor", _makeEndpointSelectHandler),
1805                                 isEnabled:getter(list, "isEnabled"),
1806                                 detachAll:function() {
1807                                         for (var i = 0, ii = list.length; i < ii; i++)
1808                                                 list[i].detachAll();
1809                                 },
1810                                 "remove":function() {
1811                                         for (var i = 0, ii = list.length; i < ii; i++)
1812                                                 _currentInstance.deleteObject({endpoint:list[i]});
1813                                 }
1814                         });
1815                 };
1816                         
1817
1818                 this.select = function(params) {
1819                         params = params || {};
1820                         params.scope = params.scope || "*";
1821                         return _makeConnectionSelectHandler(params.connections || _currentInstance.getConnections(params, true));                                                       
1822                 };              
1823
1824                 this.selectEndpoints = function(params) {
1825                         params = params || {};
1826                         params.scope = params.scope || "*";
1827                         var noElementFilters = !params.element && !params.source && !params.target,                     
1828                                 elements = noElementFilters ? "*" : prepareList(params.element),
1829                                 sources = noElementFilters ? "*" : prepareList(params.source),
1830                                 targets = noElementFilters ? "*" : prepareList(params.target),
1831                                 scopes = prepareList(params.scope, true);
1832                         
1833                         var ep = [];
1834                         
1835                         for (var el in endpointsByElement) {
1836                                 var either = filterList(elements, el, true),
1837                                         source = filterList(sources, el, true),
1838                                         sourceMatchExact = sources != "*",
1839                                         target = filterList(targets, el, true),
1840                                         targetMatchExact = targets != "*"; 
1841                                         
1842                                 // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget.  
1843                                 if ( either || source  || target ) {
1844                                         inner:
1845                                         for (var i = 0, ii = endpointsByElement[el].length; i < ii; i++) {
1846                                                 var _ep = endpointsByElement[el][i];
1847                                                 if (filterList(scopes, _ep.scope, true)) {
1848                                                 
1849                                                         var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource),
1850                                                                 noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget);
1851                                                 
1852                                                         if (noMatchSource || noMatchTarget)                                                               
1853                                                                   continue inner; 
1854                                                                                                                 
1855                                                         ep.push(_ep);           
1856                                                 }
1857                                         }
1858                                 }                                       
1859                         }
1860                         
1861                         return _makeEndpointSelectHandler(ep);
1862                 };
1863
1864                 // get all connections managed by the instance of jsplumb.
1865                 this.getAllConnections = function() { return connections; };
1866                 this.getDefaultScope = function() { return DEFAULT_SCOPE; };
1867                 // get an endpoint by uuid.
1868                 this.getEndpoint = _getEndpoint;                                
1869                 // get endpoints for some element.
1870                 this.getEndpoints = function(el) { return endpointsByElement[_info(el).id]; };          
1871                 // gets the default endpoint type. used when subclassing. see wiki.
1872                 this.getDefaultEndpointType = function() { return jsPlumb.Endpoint; };          
1873                 // gets the default connection type. used when subclassing.  see wiki.
1874                 this.getDefaultConnectionType = function() { return jsPlumb.Connection; };
1875                 /*
1876                  * Gets an element's id, creating one if necessary. really only exposed
1877                  * for the lib-specific functionality to access; would be better to pass
1878                  * the current instance into the lib-specific code (even though this is
1879                  * a static call. i just don't want to expose it to the public API).
1880                  */
1881                 this.getId = _getId;
1882                 this.getOffset = function(id) { 
1883                         var o = offsets[id]; 
1884                         return _updateOffset({elId:id});
1885                 };
1886
1887                 this.getSelector = function() {
1888                         return jsPlumb.CurrentLibrary.getSelector.apply(null, arguments);
1889                 };
1890                 
1891                 // get the size of the element with the given id, perhaps from cache.
1892                 this.getSize = function(id) { 
1893                         var s = sizes[id]; 
1894                         if (!s) _updateOffset({elId:id});
1895                         return sizes[id];
1896                 };              
1897                 
1898                 this.appendElement = _appendElement;
1899                 
1900                 var _hoverSuspended = false;
1901                 this.isHoverSuspended = function() { return _hoverSuspended; };
1902                 this.setHoverSuspended = function(s) { _hoverSuspended = s; };
1903
1904                 var _isAvailable = function(m) {
1905                         return function() {
1906                                 return jsPlumbAdapter.isRenderModeAvailable(m);
1907                         };
1908                 };
1909                 this.isCanvasAvailable = _isAvailable("canvas");
1910                 this.isSVGAvailable = _isAvailable("svg");
1911                 this.isVMLAvailable = _isAvailable("vml");
1912
1913                 // set an element's connections to be hidden
1914                 this.hide = function(el, changeEndpoints) {
1915                         _setVisible(el, "none", changeEndpoints);
1916                         return _currentInstance;
1917                 };
1918                 
1919                 // exposed for other objects to use to get a unique id.
1920                 this.idstamp = _idstamp;
1921
1922                 this.connectorsInitialized = false;
1923                 var connectorTypes = [], rendererTypes = ["canvas", "svg", "vml"];
1924                 this.registerConnectorType = function(connector, name) {
1925                         connectorTypes.push([connector, name]);
1926                 };
1927                 
1928                 /**
1929                  * callback from the current library to tell us to prepare ourselves (attach
1930                  * mouse listeners etc; can't do that until the library has provided a bind method)              
1931                  */
1932                 this.init = function() {
1933                         var _oneType = function(renderer, name, fn) {
1934                                 jsPlumb.Connectors[renderer][name] = function() {
1935                                         fn.apply(this, arguments);
1936                                         jsPlumb.ConnectorRenderers[renderer].apply(this, arguments);            
1937                                 };
1938                                 jsPlumbUtil.extend(jsPlumb.Connectors[renderer][name], [ fn, jsPlumb.ConnectorRenderers[renderer]]);
1939                         };
1940
1941                         if (!jsPlumb.connectorsInitialized) {
1942                                 for (var i = 0; i < connectorTypes.length; i++) {
1943                                         for (var j = 0; j < rendererTypes.length; j++) {
1944                                                 _oneType(rendererTypes[j], connectorTypes[i][1], connectorTypes[i][0]);                                                                                         
1945                                         }
1946
1947                                 }
1948                                 jsPlumb.connectorsInitialized = true;
1949                         }
1950                         
1951                         if (!initialized) {                
1952                 _currentInstance.anchorManager = new jsPlumb.AnchorManager({jsPlumbInstance:_currentInstance});                
1953                                 _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode);  // calling the method forces the capability logic to be run.                                                                                                             
1954                                 initialized = true;
1955                                 _currentInstance.fire("ready", _currentInstance);
1956                         }
1957                 }.bind(this);           
1958                 
1959                 this.log = log;
1960                 this.jsPlumbUIComponent = jsPlumbUIComponent;           
1961
1962                 /*
1963                  * Creates an anchor with the given params.
1964                  * 
1965                  * 
1966                  * Returns: The newly created Anchor.
1967                  * Throws: an error if a named anchor was not found.
1968                  */
1969                 this.makeAnchor = function() {
1970                         var pp, _a = function(t, p) {
1971                                 if (jsPlumb.Anchors[t]) return new jsPlumb.Anchors[t](p);
1972                                 if (!_currentInstance.Defaults.DoNotThrowErrors)
1973                                         throw { msg:"jsPlumb: unknown anchor type '" + t + "'" };
1974                         };
1975                         if (arguments.length === 0) return null;
1976                         var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null;                        
1977                         // if it appears to be an anchor already...
1978                         if (specimen.compute && specimen.getOrientation) return specimen;  //TODO hazy here about whether it should be added or is already added somehow.
1979                         // is it the name of an anchor type?
1980                         else if (typeof specimen == "string") {
1981                                 newAnchor = _a(arguments[0], {elementId:elementId, jsPlumbInstance:_currentInstance});
1982                         }
1983                         // is it an array? it will be one of:
1984                         //              an array of [spec, params] - this defines a single anchor, which may be dynamic, but has parameters.
1985                         //              an array of arrays - this defines some dynamic anchors
1986                         //              an array of numbers - this defines a single anchor.                             
1987                         else if (_ju.isArray(specimen)) {
1988                                 if (_ju.isArray(specimen[0]) || _ju.isString(specimen[0])) {
1989                                         // if [spec, params] format
1990                                         if (specimen.length == 2 && _ju.isObject(specimen[1])) {
1991                                                 // if first arg is a string, its a named anchor with params
1992                                                 if (_ju.isString(specimen[0])) {
1993                                                         pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]);
1994                                                         newAnchor = _a(specimen[0], pp);
1995                                                 }
1996                                                 // otherwise first arg is array, second is params. we treat as a dynamic anchor, which is fine
1997                                                 // even if the first arg has only one entry. you could argue all anchors should be implicitly dynamic in fact.
1998                                                 else {
1999                                                         pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance, anchors:specimen[0]}, specimen[1]);
2000                                                         newAnchor = new jsPlumb.DynamicAnchor(pp);
2001                                                 }
2002                                         }
2003                                         else
2004                                                 newAnchor = new jsPlumb.DynamicAnchor({anchors:specimen, selector:null, elementId:elementId, jsPlumbInstance:jsPlumbInstance});
2005
2006                                 }
2007                                 else {
2008                                         var anchorParams = {
2009                                                 x:specimen[0], y:specimen[1],
2010                                                 orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0],
2011                                                 offsets : (specimen.length >= 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ],
2012                                                 elementId:elementId,
2013                         jsPlumbInstance:jsPlumbInstance,
2014                         cssClass:specimen.length == 7 ? specimen[6] : null
2015                                         };                                              
2016                                         newAnchor = new jsPlumb.Anchor(anchorParams);
2017                                         newAnchor.clone = function() { return new jsPlumb.Anchor(anchorParams); };                                                                                      
2018                                 }
2019                         }
2020                         
2021                         if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp();
2022                         return newAnchor;
2023                 };
2024
2025                 /**
2026                  * makes a list of anchors from the given list of types or coords, eg
2027                  * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ]
2028                  */
2029                 this.makeAnchors = function(types, elementId, jsPlumbInstance) {
2030                         var r = [];
2031                         for ( var i = 0, ii = types.length; i < ii; i++) {
2032                                 if (typeof types[i] == "string")
2033                                         r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance}));
2034                                 else if (_ju.isArray(types[i]))
2035                                         r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance));
2036                         }
2037                         return r;
2038                 };
2039
2040                 /**
2041                  * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor
2042                  * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will
2043                  * not need to provide this - i think). 
2044                  */
2045                 this.makeDynamicAnchor = function(anchors, anchorSelector) {
2046                         return new jsPlumb.DynamicAnchor({anchors:anchors, selector:anchorSelector, elementId:null, jsPlumbInstance:_currentInstance});
2047                 };
2048                 
2049 // --------------------- makeSource/makeTarget ---------------------------------------------- 
2050                 
2051                 var _targetEndpointDefinitions = {},
2052                         _targetEndpoints = {},
2053                         _targetEndpointsUnique = {},
2054                         _targetMaxConnections = {},
2055                         _setEndpointPaintStylesAndAnchor = function(ep, epIndex) {
2056                                 ep.paintStyle = ep.paintStyle ||
2057                                                                 _currentInstance.Defaults.EndpointStyles[epIndex] ||
2058                                     _currentInstance.Defaults.EndpointStyle ||
2059                                     jsPlumb.Defaults.EndpointStyles[epIndex] ||
2060                                     jsPlumb.Defaults.EndpointStyle;
2061                                 ep.hoverPaintStyle = ep.hoverPaintStyle ||
2062                                    _currentInstance.Defaults.EndpointHoverStyles[epIndex] ||
2063                                    _currentInstance.Defaults.EndpointHoverStyle ||
2064                                    jsPlumb.Defaults.EndpointHoverStyles[epIndex] ||
2065                                    jsPlumb.Defaults.EndpointHoverStyle;                            
2066
2067                                 ep.anchor = ep.anchor ||
2068                                 _currentInstance.Defaults.Anchors[epIndex] ||
2069                                 _currentInstance.Defaults.Anchor ||
2070                                 jsPlumb.Defaults.Anchors[epIndex] ||
2071                                 jsPlumb.Defaults.Anchor;                           
2072                                         
2073                                 ep.endpoint = ep.endpoint ||
2074                                                           _currentInstance.Defaults.Endpoints[epIndex] ||
2075                                                           _currentInstance.Defaults.Endpoint ||
2076                                                           jsPlumb.Defaults.Endpoints[epIndex] ||
2077                                                           jsPlumb.Defaults.Endpoint;
2078                         },
2079                         // TODO put all the source stuff inside one parent, keyed by id.
2080                         _sourceEndpointDefinitions = {},
2081                         _sourceEndpoints = {},
2082                         _sourceEndpointsUnique = {},
2083                         _sourcesEnabled = {},
2084                         _sourceTriggers = {},
2085                         _sourceMaxConnections = {},
2086                         _targetsEnabled = {},
2087                         selectorFilter = function(evt, _el, selector) {             
2088                 var t = evt.target || evt.srcElement, ok = false, 
2089                     sel = _currentInstance.getSelector(_el, selector);
2090                 for (var j = 0; j < sel.length; j++) {
2091                     if (sel[j] == t) {
2092                         ok = true;
2093                         break;
2094                     }
2095                 }
2096                 return ok;                  
2097                 };
2098
2099                 // see API docs
2100                 this.makeTarget = function(el, params, referenceParams) {                                               
2101                         
2102                         // put jsplumb ref into params without altering the params passed in
2103                         var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams);
2104                         jsPlumb.extend(p, params);
2105
2106                         // calculate appropriate paint styles and anchor from the params given                  
2107                         _setEndpointPaintStylesAndAnchor(p, 1);                               
2108
2109                         var jpcl = jsPlumb.CurrentLibrary,
2110                             targetScope = p.scope || _currentInstance.Defaults.Scope,
2111                             deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false),
2112                             maxConnections = p.maxConnections || -1,
2113                                 onMaxConnections = p.onMaxConnections,
2114
2115                                 _doOne = function(el) {
2116                                         
2117                                         // get the element's id and store the endpoint definition for it.  jsPlumb.connect calls will look for one of these,
2118                                         // and use the endpoint definition if found.
2119                                         // decode the info for this element (id and element)
2120                                         var elInfo = _info(el), 
2121                                                 elid = elInfo.id,
2122                                                 proxyComponent = new jsPlumbUIComponent(p),
2123                                                 dropOptions = jsPlumb.extend({}, p.dropOptions || {});
2124
2125                                         // store the definitions keyed against the element id.
2126                                         _targetEndpointDefinitions[elid] = p;
2127                                         _targetEndpointsUnique[elid] = p.uniqueEndpoint;
2128                                         _targetMaxConnections[elid] = maxConnections;
2129                                         _targetsEnabled[elid] = true;                           
2130
2131                                         var _drop = function() {
2132                                                 _currentInstance.currentlyDragging = false;
2133                                                 var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments),
2134                                                         targetCount = _currentInstance.select({target:elid}).length,
2135                                                         draggable = _gel(jpcl.getDragObject(arguments)),
2136                                                         id = _currentInstance.getAttribute(draggable, "dragId"),                                                                                
2137                                                         scope = _currentInstance.getAttribute(draggable, "originalScope"),
2138                                                         jpc = floatingConnections[id],
2139                                                         idx = jpc.endpoints[0].isFloating() ? 0 : 1,
2140                                                         // this is not necessarily correct. if the source is being dragged,
2141                                                         // then the source endpoint is actually the currently suspended endpoint.
2142                                                         source = jpc.endpoints[0],
2143                                                         _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {};                                   
2144                                                         
2145                                                 if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){
2146                                                         if (onMaxConnections) {
2147                                                                 // TODO here we still have the id of the floating element, not the
2148                                                                 // actual target.
2149                                                                 onMaxConnections({
2150                                                                         element:elInfo.el,
2151                                                                         connection:jpc
2152                                                                 }, originalEvent);
2153                                                         }
2154                                                         return false;
2155                                                 }
2156
2157                                                 // unlock the source anchor to allow it to refresh its position if necessary
2158                                                 source.anchor.locked = false;                                   
2159                                                                                         
2160                                                 // restore the original scope if necessary (issue 57)
2161                                                 if (scope) jpcl.setDragScope(draggable, scope);         
2162
2163                                                 // if no suspendedEndpoint and not pending, it is likely there was a drop on two 
2164                                                 // elements that are on top of each other. abort.
2165                                                 if (jpc.suspendedEndpoint == null && !jpc.pending)
2166                                                         return false;           
2167                                                 
2168                                                 // check if drop is allowed here.                                       
2169                                                 // if the source is being dragged then in fact
2170                                                 // the source and target ids to pass into the drop interceptor are
2171                                                 // source - elid
2172                                                 // target - jpc's targetId
2173                                                 // 
2174                                                 // otherwise the ids are
2175                                                 // source - jpc.sourceId
2176                                                 // target - elid
2177                                                 //
2178                                                 var _continue = proxyComponent.isDropAllowed(idx === 0 ? elid : jpc.sourceId, idx === 0 ? jpc.targetId : elid, jpc.scope, jpc, null);                                                   
2179
2180                                                 // reinstate any suspended endpoint; this just puts the connection back into
2181                                                 // a state in which it will report sensible values if someone asks it about
2182                                                 // its target.  we're going to throw this connection away shortly so it doesnt matter
2183                                                 // if we manipulate it a bit.
2184                                                 if (jpc.suspendedEndpoint) {
2185                                                         jpc[idx ? "targetId" : "sourceId"] = jpc.suspendedEndpoint.elementId;
2186                                                         jpc[idx ? "target" : "source"] = jpc.suspendedEndpoint.element;
2187                                                         jpc.endpoints[idx] = jpc.suspendedEndpoint;
2188                                                 }                                                                                                                                                                                                               
2189                                                 
2190                                                 if (_continue) {
2191                                                                                                                                         
2192                                                         // make a new Endpoint for the target, or get it from the cache if uniqueEndpoint
2193                             // is set.
2194                                                         var _el = jpcl.getElementObject(elInfo.el),
2195                                                                 newEndpoint = _targetEndpoints[elid];
2196
2197                             // if no cached endpoint, or there was one but it has been cleaned up
2198                             // (ie. detached), then create a new one.
2199                             if (newEndpoint == null || newEndpoint._jsPlumb == null)
2200                                 newEndpoint = _currentInstance.addEndpoint(_el, p);
2201
2202                                                         if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint;  // may of course just store what it just pulled out. that's ok.
2203                                                         // TODO test options to makeTarget to see if we should do this?
2204                                                         newEndpoint._doNotDeleteOnDetach = false; // reset.
2205                                                         newEndpoint._deleteOnDetach = true;
2206                                                                                                                                         
2207                                                         // if the anchor has a 'positionFinder' set, then delegate to that function to find
2208                                                         // out where to locate the anchor.
2209                                                         if (newEndpoint.anchor.positionFinder != null) {
2210                                                                 var dropPosition = jpcl.getUIPosition(arguments, _currentInstance.getZoom()),
2211                                                                 elPosition = _getOffset(_el, _currentInstance),
2212                                                                 elSize = _getSize(_el),
2213                                                                 ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams);
2214                                                                 newEndpoint.anchor.x = ap[0];
2215                                                                 newEndpoint.anchor.y = ap[1];
2216                                                                 // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to
2217                                                                 // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation 
2218                                                                 // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be
2219                                                                 // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which
2220                                                                 // the target is furthest away from the source.
2221                                                         }
2222                                                         
2223                                                         // change the target endpoint and target element information. really this should be 
2224                                                         // done on a method on connection
2225                                                         jpc[idx ? "target" : "source"] = newEndpoint.element;
2226                                                         jpc[idx ? "targetId" : "sourceId"] = newEndpoint.elementId;
2227                                                         jpc.endpoints[idx].detachFromConnection(jpc);
2228                                                         if (jpc.endpoints[idx]._deleteOnDetach)
2229                                                                 jpc.endpoints[idx].deleteAfterDragStop = true; // tell this endpoint to delet itself after drag stop.
2230                                                         // set new endpoint, and configure the settings for endpoints to delete on detach
2231                                                         newEndpoint.addConnection(jpc);
2232                                                         jpc.endpoints[idx] = newEndpoint;
2233                                                         jpc.deleteEndpointsOnDetach = deleteEndpointsOnDetach;                                          
2234
2235                                                         // inform the anchor manager to update its target endpoint for this connection.
2236                                                         // TODO refactor to make this a single method.
2237                                                         if (idx == 1)
2238                                                                 _currentInstance.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.suspendedElementId, jpc.targetId, jpc);
2239                                                         else
2240                                                                 _currentInstance.anchorManager.sourceChanged(jpc.suspendedEndpoint.elementId, jpc.sourceId, jpc);
2241
2242                                                         _finaliseConnection(jpc, null, originalEvent);
2243                                                         jpc.pending = false;
2244
2245                                                 }                               
2246                                                 // if not allowed to drop...
2247                                                 else {
2248                                                         // TODO this code is identical (pretty much) to what happens when a connection
2249                                                         // dragged from a normal endpoint is in this situation. refactor.
2250                                                         // is this an existing connection, and will we reattach?
2251                                                         // TODO also this assumes the source needs to detach - is that always valid?
2252                                                         if (jpc.suspendedEndpoint) {                                                    
2253                                                                 if (jpc.isReattach()) {
2254                                                                         jpc.setHover(false);
2255                                                                         jpc.floatingAnchorIndex = null;
2256                                                                         jpc.suspendedEndpoint.addConnection(jpc);
2257                                                                         _currentInstance.repaint(source.elementId);
2258                                                                 }
2259                                                                 else
2260                                                                         source.detach(jpc, false, true, true, originalEvent);  // otherwise, detach the connection and tell everyone about it.
2261                                                         }
2262                                                         
2263                                                 }                                                                                                               
2264                                         };
2265                                         
2266                                         // wrap drop events as needed and initialise droppable
2267                                         var dropEvent = jpcl.dragEvents.drop;
2268                                         dropOptions.scope = dropOptions.scope || targetScope;
2269                                         dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], _drop);                               
2270                                         jpcl.initDroppable(_gel(elInfo.el), dropOptions, true);
2271                                 };
2272                         
2273                         // YUI collection fix
2274                         el = _convertYUICollection(el);                 
2275                         // make an array if only given one element
2276                         var inputs = el.length && el.constructor != String ? el : [ el ];
2277                                                 
2278                         // register each one in the list.
2279                         for (var i = 0, ii = inputs.length; i < ii; i++) {                                                      
2280                                 _doOne(inputs[i]);
2281                         }
2282
2283                         return _currentInstance;
2284                 };
2285
2286                 // see api docs
2287                 this.unmakeTarget = function(el, doNotClearArrays) {
2288                         var info = _info(el);
2289
2290                         jsPlumb.CurrentLibrary.destroyDroppable(info.el);
2291                         // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from
2292                         // the element.  the effect will be to prevent it from behaving as a target, but it's not completely purged.
2293                         if (!doNotClearArrays) {
2294                                 delete _targetEndpointDefinitions[info.id];
2295                                 delete _targetEndpointsUnique[info.id];
2296                                 delete _targetMaxConnections[info.id];
2297                                 delete _targetsEnabled[info.id];                
2298                         }
2299
2300                         return _currentInstance;
2301                 };                                              
2302
2303             // see api docs
2304                 this.makeSource = function(el, params, referenceParams) {
2305                         var p = jsPlumb.extend({}, referenceParams);
2306                         jsPlumb.extend(p, params);
2307                         _setEndpointPaintStylesAndAnchor(p, 0);   
2308                         var jpcl = jsPlumb.CurrentLibrary,
2309                                 maxConnections = p.maxConnections || -1,
2310                                 onMaxConnections = p.onMaxConnections,
2311                                 _doOne = function(elInfo) {
2312                                         // get the element's id and store the endpoint definition for it.  jsPlumb.connect calls will look for one of these,
2313                                         // and use the endpoint definition if found.
2314                                         var elid = elInfo.id,
2315                                                 _el = _gel(elInfo.el),
2316                                                 parentElement = function() {
2317                                                         return p.parent == null ? null : p.parent === "parent" ? elInfo.el.parentNode : _dom(p.parent);
2318                                                 },
2319                                                 idToRegisterAgainst = p.parent != null ? _currentInstance.getId(parentElement()) : elid;
2320                                         
2321                                         _sourceEndpointDefinitions[idToRegisterAgainst] = p;
2322                                         _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint;
2323                                         _sourcesEnabled[idToRegisterAgainst] = true;
2324
2325                                         var stopEvent = jpcl.dragEvents.stop,
2326                                                 dragEvent = jpcl.dragEvents.drag,
2327                                                 dragOptions = jsPlumb.extend({ }, p.dragOptions || {}),
2328                                                 existingDrag = dragOptions.drag,
2329                                                 existingStop = dragOptions.stop,
2330                                                 ep = null,
2331                                                 endpointAddedButNoDragYet = false;
2332                                 
2333                                         _sourceMaxConnections[idToRegisterAgainst] = maxConnections;    
2334
2335                                         // set scope if its not set in dragOptions but was passed in in params
2336                                         dragOptions.scope = dragOptions.scope || p.scope;
2337
2338                                         dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], function() {
2339                                                 if (existingDrag) existingDrag.apply(this, arguments);
2340                                                 endpointAddedButNoDragYet = false;
2341                                         });
2342                                         
2343                                         dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent], function() { 
2344
2345                                                 if (existingStop) existingStop.apply(this, arguments);                                                          
2346                             _currentInstance.currentlyDragging = false;                                         
2347                                                 if (ep._jsPlumb != null) { // if not cleaned up...
2348
2349                                                         jpcl.unbind(ep.canvas, "mousedown"); 
2350                                                                         
2351                                                         // reset the anchor to the anchor that was initially provided. the one we were using to drag
2352                                                         // the connection was just a placeholder that was located at the place the user pressed the
2353                                                         // mouse button to initiate the drag.
2354                                                         var anchorDef = p.anchor || _currentInstance.Defaults.Anchor,
2355                                                                 oldAnchor = ep.anchor,
2356                                                                 oldConnection = ep.connections[0],
2357                                                                 newAnchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance),
2358                                                                 _el = ep.element;
2359
2360                                                         // if the anchor has a 'positionFinder' set, then delegate to that function to find
2361                                                         // out where to locate the anchor. issue 117.
2362                                                         if (newAnchor.positionFinder != null) {
2363                                                                 var elPosition = _getOffset(_el, _currentInstance),
2364                                                                         elSize = _getSize(_el),
2365                                                                         dropPosition = { left:elPosition.left + (oldAnchor.x * elSize[0]), top:elPosition.top + (oldAnchor.y * elSize[1]) },
2366                                                                         ap = newAnchor.positionFinder(dropPosition, elPosition, elSize, newAnchor.constructorParams);
2367
2368                                                                 newAnchor.x = ap[0];
2369                                                                 newAnchor.y = ap[1];
2370                                                         }
2371
2372                                                         ep.setAnchor(newAnchor, true);                                                                                                                                                                                  
2373                                                         
2374                                                         if (p.parent) {                                         
2375                                                                 var parent = parentElement();
2376                                                                 if (parent) {   
2377                                                                         var potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container;
2378                                                                         ep.setElement(parent, potentialParent);
2379                                                                 }
2380                                                         }                                               
2381                                                         
2382                                                         ep.repaint();                   
2383                                                         _currentInstance.repaint(ep.elementId);                                                                                                                                         
2384                                                         _currentInstance.repaint(oldConnection.targetId);
2385                                                 }                               
2386                                         });
2387                                         // when the user presses the mouse, add an Endpoint, if we are enabled.
2388                                         var mouseDownListener = function(e) {
2389
2390                                                 // if disabled, return.
2391                                                 if (!_sourcesEnabled[idToRegisterAgainst]) return;
2392                             
2393                             // if a filter was given, run it, and return if it says no.
2394                                                 if (p.filter) {
2395                                                         var evt = jpcl.getOriginalEvent(e),
2396                                                                 r = jsPlumbUtil.isString(p.filter) ? selectorFilter(evt, _el, p.filter) : p.filter(evt, _el);
2397                                                         
2398                                                         if (r === false) return;
2399                                                 }
2400                                                 
2401                                                 // if maxConnections reached
2402                                                 var sourceCount = _currentInstance.select({source:idToRegisterAgainst}).length;
2403                                                 if (_sourceMaxConnections[idToRegisterAgainst] >= 0 && sourceCount >= _sourceMaxConnections[idToRegisterAgainst]) {
2404                                                         if (onMaxConnections) {
2405                                                                 onMaxConnections({
2406                                                                         element:_el,
2407                                                                         maxConnections:maxConnections
2408                                                                 }, e);
2409                                                         }
2410                                                         return false;
2411                                                 }                                       
2412
2413                                                 // make sure we have the latest offset for this div 
2414                                                 var myOffsetInfo = _updateOffset({elId:elid}).o,
2415                                                         z = _currentInstance.getZoom(),         
2416                                                         x = ( ((e.pageX || e.page.x) / z) - myOffsetInfo.left) / myOffsetInfo.width, 
2417                                                     y = ( ((e.pageY || e.page.y) / z) - myOffsetInfo.top) / myOffsetInfo.height,
2418                                                     parentX = x, 
2419                                                     parentY = y;                                        
2420                                                                 
2421                                                 // if there is a parent, the endpoint will actually be added to it now, rather than the div
2422                                                 // that was the source.  in that case, we have to adjust the anchor position so it refers to
2423                                                 // the parent.
2424                                                 if (p.parent) {
2425                                                         var pEl = parentElement(), pId = _getId(pEl);
2426                                                         myOffsetInfo = _updateOffset({elId:pId}).o;
2427                                                         parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width; 
2428                                                     parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height;
2429                                                 }                                                                                       
2430                                                 
2431                                                 // we need to override the anchor in here, and force 'isSource', but we don't want to mess with
2432                                                 // the params passed in, because after a connection is established we're going to reset the endpoint
2433                                                 // to have the anchor we were given.
2434                                                 var tempEndpointParams = {};
2435                                                 jsPlumb.extend(tempEndpointParams, p);
2436                                                 tempEndpointParams.isSource = true;
2437                                                 tempEndpointParams.anchor = [x,y,0,0];
2438                                                 tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ];
2439                                                 tempEndpointParams.dragOptions = dragOptions;
2440                                                 // if a parent was given we need to turn that into a "container" argument.  this is, by default,
2441                                                 // the parent of the element we will move to, so parent of p.parent in this case.  however, if
2442                                                 // the user has specified a 'container' on the endpoint definition or on 
2443                                                 // the defaults, we should use that.
2444                                                 if (p.parent) {
2445                                                         var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container;
2446                                                         if (potentialParent)
2447                                                                 tempEndpointParams.container = potentialParent;
2448                                                         else
2449                                                                 tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(parentElement());
2450                                                 }
2451                                                 
2452                                                 ep = _currentInstance.addEndpoint(elid, tempEndpointParams);
2453
2454                                                 endpointAddedButNoDragYet = true;
2455                                                 // we set this to prevent connections from firing attach events before this function has had a chance
2456                                                 // to move the endpoint.
2457                                                 ep.endpointWillMoveAfterConnection = p.parent != null;
2458                                                 ep.endpointWillMoveTo = p.parent ? parentElement() : null;
2459
2460                                                 // TODO test options to makeSource to see if we should do this?
2461                                                 ep._doNotDeleteOnDetach = false; // reset.
2462                                                 ep._deleteOnDetach = true;
2463
2464                             var _delTempEndpoint = function() {
2465                                                         // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools
2466                                                         // it is fired even if dragging has occurred, in which case we would blow away a perfectly
2467                                                         // legitimate endpoint, were it not for this check.  the flag is set after adding an
2468                                                         // endpoint and cleared in a drag listener we set in the dragOptions above.
2469                                                         if(endpointAddedButNoDragYet) {
2470                                                                  endpointAddedButNoDragYet = false;
2471                                                                 _currentInstance.deleteEndpoint(ep);
2472                                 }
2473                                                 };
2474
2475                                                 _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint);
2476                             _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint);
2477                                                 
2478                                                 // and then trigger its mousedown event, which will kick off a drag, which will start dragging
2479                                                 // a new connection from this endpoint.
2480                                                 jpcl.trigger(ep.canvas, "mousedown", e);
2481                                                 
2482                                         };
2483                        
2484                         // register this on jsPlumb so that it can be cleared by a reset.
2485                         _currentInstance.registerListener(_el, "mousedown", mouseDownListener);
2486                         _sourceTriggers[elid] = mouseDownListener;
2487
2488                         // lastly, if a filter was provided, set it as a dragFilter on the element,
2489                         // to prevent the element drag function from kicking in when we want to
2490                         // drag a new connection
2491                         if (p.filter && jsPlumbUtil.isString(p.filter)) {
2492                                 jpcl.setDragFilter(_el, p.filter);
2493                         }
2494                                 };
2495                         
2496                         el = _convertYUICollection(el);                 
2497                         
2498                         var inputs = el.length && el.constructor != String ? el : [ el ];
2499                                                 
2500                         for (var i = 0, ii = inputs.length; i < ii; i++) {                      
2501                                 _doOne(_info(inputs[i]));
2502                         }
2503
2504                         return _currentInstance;
2505                 };
2506         
2507                 // see api docs         
2508                 this.unmakeSource = function(el, doNotClearArrays) {
2509                         var info = _info(el),
2510                                 mouseDownListener = _sourceTriggers[info.id];
2511                         
2512                         if (mouseDownListener) 
2513                                 _currentInstance.unregisterListener(info.el, "mousedown", mouseDownListener);
2514
2515                         if (!doNotClearArrays) {
2516                                 delete _sourceEndpointDefinitions[info.id];
2517                                 delete _sourceEndpointsUnique[info.id];
2518                                 delete _sourcesEnabled[info.id];
2519                                 delete _sourceTriggers[info.id];
2520                                 delete _sourceMaxConnections[info.id];
2521                         }
2522
2523                         return _currentInstance;
2524                 };
2525
2526                 // see api docs
2527                 this.unmakeEverySource = function() {
2528                         for (var i in _sourcesEnabled)
2529                                 _currentInstance.unmakeSource(i, true);
2530
2531                         _sourceEndpointDefinitions = {};
2532                         _sourceEndpointsUnique = {};
2533                         _sourcesEnabled = {};
2534                         _sourceTriggers = {};
2535                 };
2536                 
2537                 // see api docs
2538                 this.unmakeEveryTarget = function() {
2539                         for (var i in _targetsEnabled)
2540                                 _currentInstance.unmakeTarget(i, true);
2541                         
2542                         _targetEndpointDefinitions = {};
2543                         _targetEndpointsUnique = {};
2544                         _targetMaxConnections = {};
2545                         _targetsEnabled = {};
2546
2547                         return _currentInstance;
2548                 };                      
2549
2550                 // does the work of setting a source enabled or disabled.
2551                 var _setEnabled = function(type, el, state, toggle) {
2552                         var a = type == "source" ? _sourcesEnabled : _targetsEnabled;                                                                   
2553                         el = _convertYUICollection(el);
2554
2555                         if (_ju.isString(el)) a[el] = toggle ? !a[el] : state;
2556                         else if (el.length) {                           
2557                                 for (var i = 0, ii = el.length; i < ii; i++) {
2558                                         var info = _info(el[i]);
2559                                         a[info.id] = toggle ? !a[info.id] : state;
2560                                 }
2561                         }       
2562                         return _currentInstance;
2563                 };
2564
2565                 this.toggleSourceEnabled = function(el) {
2566                         _setEnabled("source", el, null, true);  
2567                         return _currentInstance.isSourceEnabled(el);
2568                 };
2569
2570                 this.setSourceEnabled = function(el, state) { return _setEnabled("source", el, state); };
2571                 this.isSource = function(el) { return _sourcesEnabled[_info(el).id] != null; };         
2572                 this.isSourceEnabled = function(el) { return _sourcesEnabled[_info(el).id] === true; };
2573
2574                 this.toggleTargetEnabled = function(el) {
2575                         _setEnabled("target", el, null, true);  
2576                         return _currentInstance.isTargetEnabled(el);
2577                 };
2578                 
2579                 this.isTarget = function(el) { return _targetsEnabled[_info(el).id] != null; };         
2580                 this.isTargetEnabled = function(el) { return _targetsEnabled[_info(el).id] === true; };
2581                 this.setTargetEnabled = function(el, state) { return _setEnabled("target", el, state); };
2582
2583 // --------------------- end makeSource/makeTarget ----------------------------------------------                               
2584                                 
2585                 this.ready = function(fn) {
2586                         _currentInstance.bind("ready", fn);
2587                 };
2588
2589                 // repaint some element's endpoints and connections
2590                 this.repaint = function(el, ui, timestamp) {
2591                         // support both lists...
2592                         if (typeof el == 'object' && el.length)
2593                                 for ( var i = 0, ii = el.length; i < ii; i++) {                 
2594                                         _draw(el[i], ui, timestamp);
2595                                 }
2596                         else // ...and single strings.                                                          
2597                                 _draw(el, ui, timestamp);
2598                                 
2599                         return _currentInstance;
2600                 };
2601
2602                 // repaint every endpoint and connection.
2603                 this.repaintEverything = function(clearEdits) { 
2604                         // TODO this timestamp causes continuous anchors to not repaint properly.
2605                         // fix this. do not just take out the timestamp. it runs a lot faster with 
2606                         // the timestamp included.
2607                         //var timestamp = null;
2608                         var timestamp = _timestamp();
2609                         for ( var elId in endpointsByElement) {
2610                                 _draw(elId, null, timestamp, clearEdits);                               
2611                         }
2612                         return _currentInstance;
2613                 };
2614
2615                 
2616                 this.removeAllEndpoints = function(el, recurse) {
2617             var _one = function(_el) {                                  
2618                 var info = _info(_el),
2619                     ebe = endpointsByElement[info.id],
2620                     i, ii;
2621
2622                 if (ebe) {
2623                     for ( i = 0, ii = ebe.length; i < ii; i++) 
2624                         _currentInstance.deleteEndpoint(ebe[i]);
2625                 }
2626                 delete endpointsByElement[info.id];
2627                 
2628                 if (recurse) {
2629                     if (info.el && info.el.nodeType != 3 && info.el.nodeType != 8 ) {
2630                         for ( i = 0, ii = info.el.childNodes.length; i < ii; i++) {
2631                             _one(info.el.childNodes[i]);
2632                         }
2633                     }
2634                 }
2635                 
2636             };
2637             _one(el);
2638                         return _currentInstance;
2639                 };
2640                     
2641         /**
2642         * Remove the given element, including cleaning up all endpoints registered for it.
2643         * This is exposed in the public API but also used internally by jsPlumb when removing the
2644         * element associated with a connection drag.
2645         */
2646         this.remove = function(el, doNotRepaint) {
2647                 var info = _info(el);           
2648             _currentInstance.doWhileSuspended(function() {
2649                 _currentInstance.removeAllEndpoints(info.id, true);
2650                 _currentInstance.dragManager.elementRemoved(info.id);
2651                 delete floatingConnections[info.id];     
2652                 _currentInstance.anchorManager.clearFor(info.id);                                               
2653                 _currentInstance.anchorManager.removeFloatingConnection(info.id);
2654             }, doNotRepaint === false);
2655             if(info.el) jsPlumb.CurrentLibrary.removeElement(info.el);
2656         };
2657
2658                 var _registeredListeners = {},
2659                         _unbindRegisteredListeners = function() {
2660                                 for (var i in _registeredListeners) {
2661                                         for (var j = 0, jj = _registeredListeners[i].length; j < jj; j++) {
2662                                                 var info = _registeredListeners[i][j];
2663                                                 jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener);
2664                                         }
2665                                 }
2666                                 _registeredListeners = {};
2667                         };
2668
2669         // internal register listener method.  gives us a hook to clean things up
2670         // with if the user calls jsPlumb.reset.
2671         this.registerListener = function(el, type, listener) {
2672             jsPlumb.CurrentLibrary.bind(el, type, listener);
2673             jsPlumbUtil.addToList(_registeredListeners, type, {el:el, event:type, listener:listener});
2674         };
2675
2676         this.unregisterListener = function(el, type, listener) {
2677                 jsPlumb.CurrentLibrary.unbind(el, type, listener);
2678                 jsPlumbUtil.removeWithFunction(_registeredListeners, function(rl) {
2679                         return rl.type == type && rl.listener == listener;
2680                 });
2681         };
2682                 
2683                 this.reset = function() {                       
2684                         _currentInstance.deleteEveryEndpoint();
2685                         _currentInstance.unbind();
2686                         _targetEndpointDefinitions = {};
2687                         _targetEndpoints = {};
2688                         _targetEndpointsUnique = {};
2689                         _targetMaxConnections = {};
2690                         _sourceEndpointDefinitions = {};
2691                         _sourceEndpoints = {};
2692                         _sourceEndpointsUnique = {};
2693                         _sourceMaxConnections = {};
2694                         connections.splice(0);
2695                         _unbindRegisteredListeners();
2696                         _currentInstance.anchorManager.reset();
2697                         if (!jsPlumbAdapter.headless)
2698                                 _currentInstance.dragManager.reset();
2699                 };
2700                 
2701
2702                 this.setDefaultScope = function(scope) {
2703                         DEFAULT_SCOPE = scope;
2704                         return _currentInstance;
2705                 };
2706
2707                 // sets whether or not some element should be currently draggable.
2708                 this.setDraggable = _setDraggable;
2709
2710                 // sets the id of some element, changing whatever we need to to keep track.
2711                 this.setId = function(el, newId, doNotSetAttribute) {
2712                         // 
2713                         var id;
2714
2715                         if (jsPlumbUtil.isString(el)) {
2716                                 id = el;                                
2717                         }
2718                         else {
2719                                 el = _dom(el);
2720                                 id = _currentInstance.getId(el);
2721                         }
2722
2723                         var sConns = _currentInstance.getConnections({source:id, scope:'*'}, true),
2724                                 tConns = _currentInstance.getConnections({target:id, scope:'*'}, true);
2725
2726                         newId = "" + newId;
2727
2728                         if (!doNotSetAttribute) {
2729                                 el = _dom(id);
2730                                 jsPlumbAdapter.setAttribute(el, "id", newId);
2731                         }
2732                         else
2733                                 el = _dom(newId);
2734
2735                         endpointsByElement[newId] = endpointsByElement[id] || [];
2736                         for (var i = 0, ii = endpointsByElement[newId].length; i < ii; i++) {
2737                                 endpointsByElement[newId][i].setElementId(newId);
2738                                 endpointsByElement[newId][i].setReferenceElement(el);
2739                         }
2740                         delete endpointsByElement[id];
2741
2742                         _currentInstance.anchorManager.changeId(id, newId);
2743                         if (!jsPlumbAdapter.headless)           
2744                                 _currentInstance.dragManager.changeId(id, newId);
2745
2746                         var _conns = function(list, epIdx, type) {
2747                                 for (var i = 0, ii = list.length; i < ii; i++) {
2748                                         list[i].endpoints[epIdx].setElementId(newId);
2749                                         list[i].endpoints[epIdx].setReferenceElement(el);
2750                                         list[i][type + "Id"] = newId;
2751                                         list[i][type] = el;
2752                                 }
2753                         };
2754                         _conns(sConns, 0, "source");
2755                         _conns(tConns, 1, "target");
2756
2757                         _currentInstance.repaint(newId);
2758                 };              
2759
2760                 this.setDebugLog = function(debugLog) {
2761                         log = debugLog;
2762                 };
2763                                         
2764                 this.setSuspendDrawing = function(val, repaintAfterwards) {
2765                         var curVal = _suspendDrawing;
2766                     _suspendDrawing = val;
2767                                 if (val) _suspendedAt = new Date().getTime(); else _suspendedAt = null;
2768                     if (repaintAfterwards) _currentInstance.repaintEverything();
2769                     return curVal;
2770                 };
2771                 
2772         // returns whether or not drawing is currently suspended.               
2773                 this.isSuspendDrawing = function() {
2774                         return _suspendDrawing;
2775                 };
2776             
2777         // return timestamp for when drawing was suspended.
2778         this.getSuspendedAt = function() { return _suspendedAt; };
2779
2780         /**
2781         * @doc function
2782         * @name jsPlumb.class:doWhileSuspended
2783         * @param {function} fn Function to run while suspended.
2784         * @param {boolean} doNotRepaintAfterwards If true, jsPlumb won't run a full repaint. Otherwise it will.
2785         * @description Suspends drawing, runs the given function, then re-enables drawing (and repaints, unless you tell it not to)
2786         */
2787         this.doWhileSuspended = function(fn, doNotRepaintAfterwards) {     
2788                 var _wasSuspended = _currentInstance.isSuspendDrawing();                
2789                 if (!_wasSuspended)
2790                                 _currentInstance.setSuspendDrawing(true);
2791                         try {
2792                                 fn();
2793                         }
2794                         catch (e) {
2795                                 _ju.log("Function run while suspended failed", e);
2796                         }                       
2797                         if (!_wasSuspended)
2798                                 _currentInstance.setSuspendDrawing(false, !doNotRepaintAfterwards);
2799         };
2800             
2801         this.updateOffset = _updateOffset;
2802         this.getOffset = function(elId) { return offsets[elId]; };
2803         this.getSize = function(elId) { return sizes[elId]; };            
2804         this.getCachedData = _getCachedData;
2805         this.timestamp = _timestamp;
2806                 
2807                 
2808                 
2809                 /**
2810                  * @doc function
2811                  * @name jsPlumb.class:setRenderMode
2812                  * @param {string} mode One of `jsPlumb.SVG, `jsPlumb.VML` or `jsPlumb.CANVAS`.
2813                  * @description Sets render mode.  jsPlumb will fall back to VML if it determines that
2814                  * what you asked for is not supported (and that VML is).  If you asked for VML but the browser does
2815                  * not support it, jsPlumb uses SVG.
2816                  * @return {string} The render mode that jsPlumb set, which of course may be different from that requested.
2817                  */
2818                 this.setRenderMode = function(mode) {                   
2819                         renderMode = jsPlumbAdapter.setRenderMode(mode);
2820                         var i, ii;
2821                         // only add this if the renderer is canvas; we dont want these listeners registered on te
2822                         // entire document otherwise.
2823                         if (renderMode == jsPlumb.CANVAS) {
2824                                 var bindOne = function(event) {
2825                         jsPlumb.CurrentLibrary.bind(document, event, function(e) {
2826                             if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) {
2827                                 // try connections first
2828                                 for (i = 0, ii = connections.length; i < ii; i++ ) {
2829                                 var t = connections[i].getConnector()[event](e);
2830                                 if (t) return;  
2831                             }
2832                                 for (var el in endpointsByElement) {
2833                                     var ee = endpointsByElement[el];
2834                                     for ( i = 0, ii = ee.length; i < ii; i++ ) {
2835                                         if (ee[i].endpoint[event] && ee[i].endpoint[event](e)) return;
2836                                     }
2837                                 }
2838                             }
2839                         });                                     
2840                                 };
2841                                 bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu");                               
2842                         }
2843
2844                         return renderMode;
2845                 };
2846                 
2847                 /**
2848                  * @doc function
2849                  * @name jsPlumb.class:getRenderMode
2850                  * @description Gets the current render mode for this instance of jsPlumb.
2851                  * @return {string} The current render mode - "canvas", "svg" or "vml".
2852                  */
2853                 this.getRenderMode = function() { return renderMode; };
2854                 
2855                 this.show = function(el, changeEndpoints) {
2856                         _setVisible(el, "block", changeEndpoints);
2857                         return _currentInstance;
2858                 };              
2859
2860                 /**
2861                  * gets some test hooks. nothing writable.
2862                  */
2863                 this.getTestHarness = function() {
2864                         return {
2865                                 endpointsByElement : endpointsByElement,  
2866                                 endpointCount : function(elId) {
2867                                         var e = endpointsByElement[elId];
2868                                         return e ? e.length : 0;
2869                                 },
2870                                 connectionCount : function(scope) {
2871                                         scope = scope || DEFAULT_SCOPE;
2872                                         var c = _currentInstance.getConnections({scope:scope});
2873                                         return c ? c.length : 0;
2874                                 },
2875                                 getId : _getId,
2876                                 makeAnchor:self.makeAnchor,
2877                                 makeDynamicAnchor:self.makeDynamicAnchor
2878                         };
2879                 };
2880                 
2881                 
2882                 // TODO: update this method to return the current state.
2883                 this.toggleVisible = _toggleVisible;
2884                 this.toggleDraggable = _toggleDraggable;                                                
2885                 this.addListener = this.bind;
2886                 
2887         /*
2888             helper method to take an xy location and adjust it for the parent's offset and scroll.
2889         */
2890                 this.adjustForParentOffsetAndScroll = function(xy, el) {
2891
2892                         var offsetParent = null, result = xy;
2893                         if (el.tagName.toLowerCase() === "svg" && el.parentNode) {
2894                                 offsetParent = el.parentNode;
2895                         }
2896                         else if (el.offsetParent) {
2897                                 offsetParent = el.offsetParent;                                 
2898                         }
2899                         if (offsetParent != null) {
2900                                 var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent, _currentInstance),
2901                                         so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop};                                       
2902
2903                                 // i thought it might be cool to do this:
2904                                 //      lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft;
2905                                 //      lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop;                                      
2906                                 // but i think it ignores margins.  my reasoning was that it's quicker to not hand off to some underlying                                       
2907                                 // library.
2908                                 
2909                                 result[0] = xy[0] - po.left + so.left;
2910                                 result[1] = xy[1] - po.top + so.top;
2911                         }
2912                 
2913                         return result;
2914                         
2915                 };
2916
2917                 if (!jsPlumbAdapter.headless) {
2918                         _currentInstance.dragManager = jsPlumbAdapter.getDragManager(_currentInstance);
2919                         _currentInstance.recalculateOffsets = _currentInstance.dragManager.updateOffsets;
2920             }       
2921                                     
2922     };
2923
2924     jsPlumbUtil.extend(jsPlumbInstance, jsPlumbUtil.EventGenerator, {
2925         setAttribute : function(el, a, v) {
2926                 jsPlumbAdapter.setAttribute(el, a, v);
2927         },
2928         getAttribute : function(el, a) {
2929                 return jsPlumbAdapter.getAttribute(jsPlumb.CurrentLibrary.getDOMElement(el), a);
2930         },      
2931         registerConnectionType : function(id, type) {
2932                 this._connectionTypes[id] = jsPlumb.extend({}, type);
2933         },      
2934         registerConnectionTypes : function(types) {
2935                 for (var i in types)
2936                         this._connectionTypes[i] = jsPlumb.extend({}, types[i]);
2937         },              
2938         registerEndpointType : function(id, type) {
2939                 this._endpointTypes[id] = jsPlumb.extend({}, type);
2940         },      
2941         registerEndpointTypes : function(types) {
2942                 for (var i in types)
2943                         this._endpointTypes[i] = jsPlumb.extend({}, types[i]);
2944         },      
2945         getType : function(id, typeDescriptor) {
2946                 return typeDescriptor ===  "connection" ? this._connectionTypes[id] : this._endpointTypes[id];
2947         },
2948         setIdChanged : function(oldId, newId) {
2949                 this.setId(oldId, newId, true);
2950         },
2951         // set parent: change the parent for some node and update all the registrations we need to.
2952         setParent : function(el, newParent) {
2953                 var jpcl = jsPlumb.CurrentLibrary,
2954                         _el = jpcl.getElementObject(el),
2955                         _dom = jpcl.getDOMElement(_el),
2956                         _id = this.getId(_dom),
2957                         _pel = jpcl.getElementObject(newParent),
2958                         _pdom = jpcl.getDOMElement(_pel),
2959                         _pid = this.getId(_pdom);
2960
2961                 _dom.parentNode.removeChild(_dom);
2962                 _pdom.appendChild(_dom);
2963                 this.dragManager.setParent(_el, _id, _pel, _pid);
2964         }
2965     });
2966
2967 // --------------------- static instance + AMD registration ------------------------------------------- 
2968         
2969 // create static instance and assign to window if window exists.        
2970         var jsPlumb = new jsPlumbInstance();
2971         // register on window if defined (lets us run on server)
2972         if (typeof window != 'undefined') window.jsPlumb = jsPlumb;     
2973         // add 'getInstance' method to static instance
2974         /**
2975         * @name jsPlumb.getInstance
2976         * @param {object} [_defaults] Optional default settings for the new instance.
2977         * @desc Gets a new instance of jsPlumb.
2978         */
2979         jsPlumb.getInstance = function(_defaults) {
2980                 var j = new jsPlumbInstance(_defaults);
2981                 j.init();
2982                 return j;
2983         };
2984 // maybe register static instance as an AMD module, and getInstance method too.
2985         if ( typeof define === "function") {
2986                 define( "jsplumb", [], function () { return jsPlumb; } );
2987                 define( "jsplumbinstance", [], function () { return jsPlumb.getInstance(); } );
2988         }
2989  // CommonJS 
2990         if (typeof exports !== 'undefined') {
2991       exports.jsPlumb = jsPlumb;
2992         }
2993         
2994         
2995 // --------------------- end static instance + AMD registration -------------------------------------------             
2996         
2997 })();