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