You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by sa...@apache.org on 2012/03/31 00:25:01 UTC
svn commit: r1307642 [37/40] - in
/incubator/airavata/trunk/scratch/xbaya-web: ./ components/
components/calculator/ components/logic/ core/ css/ data/
dynamic_components/ js/ js/jquery/ js/jquery/css/
js/jquery/css/ui-lightness/ js/jquery/css/ui-light...
Added: incubator/airavata/trunk/scratch/xbaya-web/js/jquery/jquery.jsPlumb-1.3.6-all.js
URL: http://svn.apache.org/viewvc/incubator/airavata/trunk/scratch/xbaya-web/js/jquery/jquery.jsPlumb-1.3.6-all.js?rev=1307642&view=auto
==============================================================================
--- incubator/airavata/trunk/scratch/xbaya-web/js/jquery/jquery.jsPlumb-1.3.6-all.js (added)
+++ incubator/airavata/trunk/scratch/xbaya-web/js/jquery/jquery.jsPlumb-1.3.6-all.js Fri Mar 30 22:24:45 2012
@@ -0,0 +1,8474 @@
+/*
+ * jsPlumb
+ *
+ * Title:jsPlumb 1.3.6
+ *
+ * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
+ * elements, or VML.
+ *
+ * This file contains the jsPlumb core code.
+ *
+ * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com)
+ *
+ * http://jsplumb.org
+ * http://github.com/sporritt/jsplumb
+ * http://code.google.com/p/jsplumb
+ *
+ * Dual licensed under the MIT and GPL2 licenses.
+ */
+
+;(function() {
+
+ /**
+ * Class:jsPlumb
+ * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to
+ * create and maintain Connections and Endpoints.
+ */
+
+ var canvasAvailable = !!document.createElement('canvas').getContext,
+ svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
+ // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is.
+ vmlAvailable = !(canvasAvailable | svgAvailable);
+
+ var _findWithFunction = function(a, f) {
+ if (a)
+ for (var i = 0; i < a.length; i++) if (f(a[i])) return i;
+ return -1;
+ },
+ _indexOf = function(l, v) {
+ return _findWithFunction(l, function(_v) { return _v == v; });
+ },
+ _removeWithFunction = function(a, f) {
+ var idx = _findWithFunction(a, f);
+ if (idx > -1) a.splice(idx, 1);
+ return idx != -1;
+ },
+ _remove = function(l, v) {
+ var idx = _indexOf(l, v);
+ if (idx > -1) l.splice(idx, 1);
+ return idx != -1;
+ },
+ // TODO support insert index
+ _addWithFunction = function(list, item, hashFunction) {
+ if (_findWithFunction(list, hashFunction) == -1) list.push(item);
+ },
+ _addToList = function(map, key, value) {
+ var l = map[key];
+ if (l == null) {
+ l = [], map[key] = l;
+ }
+ l.push(value);
+ return l;
+ };
+
+ // for those browsers that dont have it. they still don't have it! but at least they won't crash.
+ if (!window.console)
+ window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} };
+
+ /**
+ * helper method to add an item to a list, creating the list if it does
+ * not yet exist.
+ */
+ var _connectionBeingDragged = null,
+ _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); },
+ _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); },
+ _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); },
+ _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); },
+ _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); },
+ _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
+ _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); },
+ _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); },
+ _logEnabled = true,
+ _log = function() {
+ if (_logEnabled && typeof console != "undefined") {
+ try {
+ var msg = arguments[arguments.length - 1];
+ console.log(msg);
+ }
+ catch (e) {}
+ }
+ },
+ _group = function(g) { if (_logEnabled && typeof console != "undefined") console.group(g); },
+ _groupEnd = function(g) { if (_logEnabled && typeof console != "undefined") console.groupEnd(g); },
+ _time = function(t) { if (_logEnabled && typeof console != "undefined") console.time(t); },
+ _timeEnd = function(t) { if (_logEnabled && typeof console != "undefined") console.timeEnd(t); };
+
+ /**
+ * EventGenerator
+ * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend.
+ */
+ EventGenerator = function() {
+ var _listeners = {}, self = this;
+
+ // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to
+ // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event
+ // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready"
+ // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting
+ // to hear what other people think.
+ var eventsToDieOn = [ "ready" ];
+
+ /*
+ * Binds a listener to an event.
+ *
+ * Parameters:
+ * event - name of the event to bind to.
+ * listener - function to execute.
+ */
+ this.bind = function(event, listener) {
+ _addToList(_listeners, event, listener);
+ };
+ /*
+ * Fires an update for the given event.
+ *
+ * Parameters:
+ * event - event to fire
+ * value - value to pass to the event listener(s).
+ * originalEvent - the original event from the browser
+ */
+ this.fire = function(event, value, originalEvent) {
+ if (_listeners[event]) {
+ for ( var i = 0; i < _listeners[event].length; i++) {
+ // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this
+ // method will have the whole call stack available in the debugger.
+ //if (_findIndex(eventsToDieOn, event) != -1)
+ if (_findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1)
+ _listeners[event][i](value, originalEvent);
+ else {
+ // for events we don't want to die on, catch and log.
+ try {
+ _listeners[event][i](value, originalEvent);
+ } catch (e) {
+ _log("jsPlumb: fire failed for event " + event + " : " + e);
+ }
+ }
+ }
+ }
+ };
+ /*
+ * Clears either all listeners, or listeners for some specific event.
+ *
+ * Parameters:
+ * event - optional. constrains the clear to just listeners for this event.
+ */
+ this.clearListeners = function(event) {
+ if (event)
+ delete _listeners[event];
+ else {
+ delete _listeners;
+ _listeners = {};
+ }
+ };
+
+ this.getListener = function(forEvent) {
+ return _listeners[forEvent];
+ };
+ },
+
+ /**
+ * creates a timestamp, using milliseconds since 1970, but as a string.
+ */
+ _timestamp = function() { return "" + (new Date()).getTime(); },
+
+ /*
+ * Class:jsPlumbUIComponent
+ * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle,
+ * and also extends EventGenerator to provide the bind and fire methods.
+ */
+ jsPlumbUIComponent = function(params) {
+ var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix,
+ id = idPrefix + (new Date()).getTime();
+ self._jsPlumb = params["_jsPlumb"];
+ self.getId = function() { return id; };
+ self.tooltip = params.tooltip;
+ self.hoverClass = params.hoverClass;
+
+ // all components can generate events
+ EventGenerator.apply(this);
+ // 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();
+ self.constructor.apply(o, a);
+ return o;
+ };
+
+ this.getParameter = function(name) { return parameters[name]; },
+ this.getParameters = function() { return parameters; },
+ this.setParameter = function(name, value) { parameters[name] = value; },
+ this.setParameters = function(p) { parameters = p; },
+ this.overlayPlacements = [],
+ this.paintStyle = null,
+ this.hoverPaintStyle = null;
+
+ // user can supply a beforeDetach callback, which will be executed before a detach
+ // is performed; returning false prevents the detach.
+ var beforeDetach = params.beforeDetach;
+ this.isDetachAllowed = function(connection) {
+ var r = self._jsPlumb.checkCondition("beforeDetach", connection );
+ if (beforeDetach) {
+ try {
+ r = beforeDetach(connection);
+ }
+ catch (e) { _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.
+ var beforeDrop = params.beforeDrop;
+ this.isDropAllowed = function(sourceId, targetId, scope) {
+ var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope });
+ if (beforeDrop) {
+ try {
+ r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope });
+ }
+ catch (e) { _log("jsPlumb: beforeDrop callback failed", e); }
+ }
+ return r;
+ };
+
+ // helper method to update the hover style whenever it, or paintStyle, changes.
+ // we use paintStyle as the foundation and merge hoverPaintStyle over the
+ // top.
+ var _updateHoverStyle = function() {
+ if (self.paintStyle && self.hoverPaintStyle) {
+ var mergedHoverStyle = {};
+ jsPlumb.extend(mergedHoverStyle, self.paintStyle);
+ jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle);
+ delete self["hoverPaintStyle"];
+ // we want the fillStyle of paintStyle to override a gradient, if possible.
+ if (mergedHoverStyle.gradient && self.paintStyle.fillStyle)
+ delete mergedHoverStyle["gradient"];
+ self.hoverPaintStyle = mergedHoverStyle;
+ }
+ };
+
+ /*
+ * Sets the paint style and then repaints the element.
+ *
+ * Parameters:
+ * style - Style to use.
+ */
+ this.setPaintStyle = function(style, doNotRepaint) {
+ self.paintStyle = style;
+ self.paintStyleInUse = self.paintStyle;
+ _updateHoverStyle();
+ if (!doNotRepaint) self.repaint();
+ };
+
+ /*
+ * Sets the paint style to use when the mouse is hovering over the element. This is null by default.
+ * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace
+ * it. This is because people will most likely want to change just one thing when hovering, say the
+ * color for example, but leave the rest of the appearance the same.
+ *
+ * Parameters:
+ * style - Style to use when the mouse is hovering.
+ * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially.
+ */
+ this.setHoverPaintStyle = function(style, doNotRepaint) {
+ self.hoverPaintStyle = style;
+ _updateHoverStyle();
+ if (!doNotRepaint) self.repaint();
+ };
+
+ /*
+ * sets/unsets the hover state of this element.
+ *
+ * Parameters:
+ * hover - hover state boolean
+ * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops.
+ */
+ this.setHover = function(hover, ignoreAttachedElements, timestamp) {
+ // while dragging, we ignore these events. this keeps the UI from flashing and
+ // swishing and whatevering.
+ if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) {
+
+ _hover = hover;
+ if (self.hoverClass != null && self.canvas != null) {
+ if (hover)
+ jpcl.addClass(self.canvas, self.hoverClass);
+ else
+ jpcl.removeClass(self.canvas, self.hoverClass);
+ }
+ if (self.hoverPaintStyle != null) {
+ self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle;
+ timestamp = timestamp || _timestamp();
+ self.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 (self.getAttachedElements && !ignoreAttachedElements)
+ _updateAttachedElements(hover, _timestamp(), self);
+ }
+ };
+
+ this.isHover = function() { return _hover; };
+
+ var jpcl = jsPlumb.CurrentLibrary,
+ events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ],
+ eventFilters = { "mouseout":"mouseexit" },
+ bindOne = function(o, c, evt) {
+ var filteredEvent = eventFilters[evt] || evt;
+ jpcl.bind(o, evt, function(ee) {
+ c.fire(filteredEvent, c, ee);
+ });
+ },
+ unbindOne = function(o, evt) {
+ var filteredEvent = eventFilters[evt] || evt;
+ jpcl.unbind(o, evt);
+ };
+
+ this.attachListeners = function(o, c) {
+ for (var i = 0; i < events.length; i++) {
+ bindOne(o, c, events[i]);
+ }
+ };
+
+ var _updateAttachedElements = function(state, timestamp, sourceElement) {
+ var affectedElements = self.getAttachedElements(); // implemented in subclasses
+ if (affectedElements) {
+ for (var i = 0; i < affectedElements.length; i++) {
+ if (!sourceElement || sourceElement != affectedElements[i])
+ affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements.
+ }
+ }
+ };
+
+ this.reattachListenersForElement = function(o) {
+ if (arguments.length > 1) {
+ for (var i = 0; i < events.length; i++)
+ unbindOne(o, events[i]);
+ for (var i = 1; i < arguments.length; i++)
+ self.attachListeners(o, arguments[i]);
+ }
+ };
+ },
+
+ overlayCapableJsPlumbUIComponent = function(params) {
+ jsPlumbUIComponent.apply(this, arguments);
+ var self = this;
+ /*
+ * Property: overlays
+ * List of Overlays for this component.
+ */
+ this.overlays = [];
+
+ var processOverlay = function(o) {
+ var _newOverlay = null;
+ if (o.constructor == Array) { // 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:self, _jsPlumb:self._jsPlumb}, o[1]);
+ if (o.length == 3) jsPlumb.extend(p, o[2]);
+ _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p);
+ if (p.events) {
+ for (var evt in p.events) {
+ _newOverlay.bind(evt, p.events[evt]);
+ }
+ }
+ } else if (o.constructor == String) {
+ _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb});
+ } else {
+ _newOverlay = o;
+ }
+
+ self.overlays.push(_newOverlay);
+ },
+ calculateOverlaysToAdd = function(params) {
+ var defaultKeys = self.defaultOverlayKeys || [],
+ o = params.overlays,
+ checkKey = function(k) {
+ return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || [];
+ };
+
+ if (!o) o = [];
+
+ for (var i = 0; i < defaultKeys.length; i++)
+ o.unshift.apply(o, checkKey(defaultKeys[i]));
+
+ return o;
+ }
+
+ var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays;
+ if (_overlays) {
+ for (var i = 0; i < _overlays.length; i++) {
+ processOverlay(_overlays[i]);
+ }
+ }
+
+ // overlay finder helper method
+ var _getOverlayIndex = function(id) {
+ var idx = -1;
+ for (var i = 0; i < self.overlays.length; i++) {
+ if (id === self.overlays[i].id) {
+ idx = i;
+ break;
+ }
+ }
+ return idx;
+ };
+
+ /*
+ * Function: addOverlay
+ * Adds an Overlay to the Connection.
+ *
+ * Parameters:
+ * overlay - Overlay to add.
+ */
+ this.addOverlay = function(overlay) {
+ processOverlay(overlay);
+ self.repaint();
+ };
+
+ /*
+ * Function: getOverlay
+ * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter
+ * in to the Overlay's constructor arguments, and then use that to retrieve
+ * it via this method.
+ */
+ this.getOverlay = function(id) {
+ var idx = _getOverlayIndex(id);
+ return idx >= 0 ? self.overlays[idx] : null;
+ };
+
+ /*
+ * Function: hideOverlay
+ * Hides the overlay specified by the given id.
+ */
+ this.hideOverlay = function(id) {
+ var o = self.getOverlay(id);
+ if (o) o.hide();
+ };
+
+ /*
+ * Function: showOverlay
+ * Shows the overlay specified by the given id.
+ */
+ this.showOverlay = function(id) {
+ var o = self.getOverlay(id);
+ if (o) o.show();
+ };
+
+ /**
+ * Function: removeAllOverlays
+ * Removes all overlays from the Connection, and then repaints.
+ */
+ this.removeAllOverlays = function() {
+ self.overlays.splice(0, self.overlays.length);
+ self.repaint();
+ };
+
+ /**
+ * Function:removeOverlay
+ * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec.
+ * Parameters:
+ * overlayId - id of the overlay to remove.
+ */
+ this.removeOverlay = function(overlayId) {
+ var idx = _getOverlayIndex(overlayId);
+ if (idx != -1) {
+ var o = self.overlays[idx];
+ o.cleanup();
+ self.overlays.splice(idx, 1);
+ }
+ };
+
+ /**
+ * Function:removeOverlays
+ * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec.
+ * Parameters:
+ * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id.
+ */
+ this.removeOverlays = function() {
+ for (var i = 0; i < arguments.length; i++)
+ self.removeOverlay(arguments[i]);
+ };
+
+ // this is a shortcut helper method to let people add a label as
+ // overlay.
+ var _internalLabelOverlayId = "__label",
+ _makeLabelOverlay = function(params) {
+
+ var _params = {
+ cssClass:params.cssClass,
+ labelStyle : this.labelStyle,
+ id:_internalLabelOverlayId,
+ component:self,
+ _jsPlumb:self._jsPlumb
+ },
+ mergedParams = jsPlumb.extend(_params, params);
+
+ return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams );
+ };
+ if (params.label) {
+ var loc = params.labelLocation || self.defaultLabelLocation || 0.5,
+ labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle;
+ this.overlays.push(_makeLabelOverlay({
+ label:params.label,
+ location:loc,
+ labelStyle:labelStyle
+ }));
+ }
+
+ /*
+ * Function: setLabel
+ * Sets the Connection's label.
+ *
+ * Parameters:
+ * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }
+ */
+ this.setLabel = function(l) {
+ var lo = self.getOverlay(_internalLabelOverlayId);
+ if (!lo) {
+ var params = l.constructor == String || l.constructor == Function ? { label:l } : l;
+ lo = _makeLabelOverlay(params);
+ this.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);
+ }
+ }
+
+ self.repaint();
+ };
+
+ /*
+ Function:getLabel
+ Returns the label text for this component (or a function if you are labelling with a function).
+ This does not return the overlay itself; this is a convenience method which is a pair with
+ setLabel; together they allow you to add and access a Label Overlay without having to create the
+ Overlay object itself. For access to the underlying label overlay that jsPlumb has created,
+ use getLabelOverlay.
+ */
+ this.getLabel = function() {
+ var lo = self.getOverlay(_internalLabelOverlayId);
+ return lo != null ? lo.getLabel() : null;
+ };
+
+ /*
+ Function:getLabelOverlay
+ Returns the underlying internal label overlay, which will exist if you specified a label on
+ a connect or addEndpoint call, or have called setLabel at any stage.
+ */
+ this.getLabelOverlay = function() {
+ return self.getOverlay(_internalLabelOverlayId);
+ }
+ },
+
+ _bindListeners = function(obj, _self, _hoverFunction) {
+ obj.bind("click", function(ep, e) { _self.fire("click", _self, e); });
+ obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); });
+ obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); });
+ obj.bind("mouseenter", function(ep, e) {
+ if (!_self.isHover()) {
+ _hoverFunction(true);
+ _self.fire("mouseenter", _self, e);
+ }
+ });
+ obj.bind("mouseexit", function(ep, e) {
+ if (_self.isHover()) {
+ _hoverFunction(false);
+ _self.fire("mouseexit", _self, e);
+ }
+ });
+ };
+
+ var _jsPlumbInstanceIndex = 0,
+ getInstanceIndex = function() {
+ var i = _jsPlumbInstanceIndex + 1;
+ _jsPlumbInstanceIndex++;
+ return i;
+ };
+
+ var jsPlumbInstance = function(_defaults) {
+
+ /*
+ * Property: Defaults
+ *
+ * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information
+ * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults
+ * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb.
+ *
+ * Properties:
+ * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter".
+ * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"].
+ * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defults to true.
+ * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list.
+ * - *Connector* The default connector definition to use for all connections. Default is "Bezier".
+ * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element.
+ * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty.
+ * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty.
+ * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot".
+ * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list.
+ * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"].
+ * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456".
+ * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty.
+ * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null.
+ * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null.
+ * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null.
+ * - *LabelStyle* The default style to use for label overlays on connections.
+ * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false.
+ * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list.
+ * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1.
+ * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456".
+ * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG.
+ * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories.
+ */
+ this.Defaults = {
+ Anchor : "BottomCenter",
+ Anchors : [ null, null ],
+ ConnectionsDetachable : true,
+ ConnectionOverlays : [ ],
+ Connector : "Bezier",
+ Container : null,
+ 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" },
+ //Reattach:false,
+ RenderMode : "svg",
+ Scope : "jsPlumb_DefaultScope"
+ };
+ if (_defaults) jsPlumb.extend(this.Defaults, _defaults);
+
+ this.logEnabled = this.Defaults.LogEnabled;
+
+ EventGenerator.apply(this);
+ var _currentInstance = this,
+ _instanceIndex = getInstanceIndex(),
+ _bb = _currentInstance.bind,
+ _initialDefaults = {};
+
+ 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]);
+ };
+
+ /*
+ Function: importDefaults
+ Imports all the given defaults into this instance of jsPlumb.
+ */
+ _currentInstance.importDefaults = function(d) {
+ for (var i in d) {
+ _currentInstance.Defaults[i] = d[i];
+ }
+ };
+
+ /*
+ Function:restoreDefaults
+ Restores the default settings to "factory" values.
+ */
+ _currentInstance.restoreDefaults = function() {
+ _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults);
+ };
+
+ var log = null,
+ repaintFunction = function() {
+ jsPlumb.repaintEverything();
+ },
+ automaticRepaint = true,
+ repaintEverything = function() {
+ if (automaticRepaint)
+ repaintFunction();
+ },
+ resizeTimer = null,
+ initialized = false,
+ connectionsByScope = {},
+ /**
+ * 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 = {},
+ canvasList = [],
+ sizes = [],
+ //listeners = {}, // a map: keys are event types, values are lists of listeners.
+ DEFAULT_SCOPE = this.Defaults.Scope,
+ renderMode = null, // will be set in init()
+
+ /**
+ * helper method to add an item to a list, creating the list if it does
+ * not yet exist.
+ */
+ _addToList = function(map, key, value) {
+ var l = map[key];
+ if (l == null) {
+ l = [];
+ map[key] = l;
+ }
+ l.push(value);
+ return l;
+ },
+
+ /**
+ * 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 document body.
+ *
+ */
+ _appendElement = function(el, parent) {
+ if (_currentInstance.Defaults.Container)
+ jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container);
+ else if (!parent)
+ document.body.appendChild(el);
+ else
+ jsPlumb.CurrentLibrary.appendElement(el, parent);
+ },
+
+ _curIdStamp = 1,
+ _idstamp = function() { return "" + _curIdStamp++; },
+
+ /**
+ * 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;
+ },
+
+ _suspendDrawing = false,
+ /*
+ sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go.
+ it will save you a lot of time.
+ */
+ _setSuspendDrawing = function(val, repaintAfterwards) {
+ _suspendDrawing = val;
+ if (repaintAfterwards) _currentInstance.repaintEverything();
+ },
+
+ /**
+ * 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.
+ */
+ _draw = function(element, ui, timestamp) {
+ if (!_suspendDrawing) {
+ var id = _getAttribute(element, "id"),
+ repaintEls = _currentInstance.dragManager.getElementsForDraggable(id);
+
+ if (timestamp == null) timestamp = _timestamp();
+
+ _currentInstance.anchorManager.redraw(id, ui, timestamp);
+
+ if (repaintEls) {
+ for (var i in repaintEls) {
+ _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset);
+ }
+ }
+ }
+ },
+
+ /**
+ * 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;
+ if (element.constructor == Array) {
+ retVal = [];
+ for ( var i = 0; i < element.length; i++) {
+ var el = _getElementObject(element[i]), id = _getAttribute(el, "id");
+ retVal.push(fn(el, id)); // append return values to what we will return
+ }
+ } else {
+ var el = _getElementObject(element), id = _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.
+ */
+ _initDraggableIfNecessary = function(element, isDraggable, dragOptions) {
+ 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[dragEvent] = _wrap(options[dragEvent], function() {
+ var ui = jpcl.getUIPosition(arguments);
+ _draw(element, ui);
+ _addClass(element, "jsPlumb_dragged");
+ });
+ options[stopEvent] = _wrap(options[stopEvent], function() {
+ var ui = jpcl.getUIPosition(arguments);
+ _draw(element, ui);
+ _removeClass(element, "jsPlumb_dragged");
+ });
+ draggableStates[_getId(element)] = true;
+ var draggable = draggableStates[_getId(element)];
+ options.disabled = draggable == null ? false : !draggable;
+ jpcl.initDraggable(element, options, false);
+ _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 && _p.source.endpoint) _p.sourceEndpoint = _p.source;
+ if (_p.source && _p.target.endpoint) _p.targetEndpoint = _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()) {
+ _log(_currentInstance, "could not add connection; source endpoint is full");
+ return;
+ }
+
+ // target:
+ if (_p.targetEndpoint && _p.targetEndpoint.isFull()) {
+ _log(_currentInstance, "could not add connection; target endpoint is full");
+ return;
+ }
+
+ // 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; i < _p.sourceEndpoint.connectorOverlays.length; i++) {
+ _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]);
+ }
+ }
+
+ // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip.
+ _p.tooltip = params.tooltip;
+ if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip)
+ _p.tooltip = _p.sourceEndpoint.connectorTooltip;
+
+ // 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.
+ if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) {
+ var tid = _getId(_p.target),
+ tep =_targetEndpointDefinitions[tid],
+ existingUniqueEndpoint = _targetEndpoints[tid];
+
+ if (tep) {
+
+ var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep);
+ if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint;
+ _p.targetEndpoint = newEndpoint;
+ }
+ }
+
+ // same thing, but for source.
+ if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) {
+ var tid = _getId(_p.source),
+ tep = _sourceEndpointDefinitions[tid],
+ existingUniqueEndpoint = _sourceEndpoints[tid];
+
+ if (tep) {
+
+ var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep);
+ if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint;
+ _p.sourceEndpoint = newEndpoint;
+ }
+ }
+
+ return _p;
+ },
+
+ _newConnection = function(params) {
+ var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
+ endpointFunc = _currentInstance.Defaults.EndpointType || 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;
+ var con = new connectionFunc(params);
+ con.id = "con_" + _idstamp();
+ _eventFireProxy("click", "click", con);
+ _eventFireProxy("dblclick", "dblclick", con);
+ _eventFireProxy("contextmenu", "contextmenu", con);
+ return con;
+ },
+
+ /**
+ * adds the connection to the backing model, fires an event if necessary and then redraws
+ */
+ _finaliseConnection = function(jpc, params, originalEvent) {
+ params = params || {};
+ // add to list of connections (by scope).
+ if (!jpc.suspendedEndpoint)
+ _addToList(connectionsByScope, jpc.scope, jpc);
+ // fire an event
+ if (!params.doNotFireConnectionEvent && params.fireEvent !== false) {
+ _currentInstance.fire("jsPlumbConnection", {
+ connection:jpc,
+ source : jpc.source, target : jpc.target,
+ sourceId : jpc.sourceId, targetId : jpc.targetId,
+ sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
+ }, originalEvent);
+ }
+ // 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.
+ _currentInstance.anchorManager.newConnection(jpc);
+ // force a paint
+ _draw(jpc.source);
+ },
+
+ _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 || Endpoint;
+ params.parent = _getParentFromParams(params);
+ params["_jsPlumb"] = _currentInstance;
+ var ep = new endpointFunc(params);
+ ep.id = "ep_" + _idstamp();
+ _eventFireProxy("click", "endpointClick", ep);
+ _eventFireProxy("dblclick", "endpointDblClick", ep);
+ _eventFireProxy("contextmenu", "contextmenu", ep);
+ 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; i < endpoints.length; i++) {
+ for ( var j = 0; j < endpoints[i].connections.length; 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]);
+ }
+ }
+ },
+ /**
+ * perform an operation on all elements.
+ */
+ _operationOnAll = function(func) {
+ for ( var elId in endpointsByElement) {
+ _operation(elId, func);
+ }
+ },
+
+ /**
+ * helper to remove an element from the DOM.
+ */
+ _removeElement = function(element, parent) {
+ if (element != null && element.parentNode != null) {
+ element.parentNode.removeChild(element);
+ }
+ },
+ /**
+ * helper to remove a list of elements from the DOM.
+ */
+ _removeElements = function(elements, parent) {
+ for ( var i = 0; i < elements.length; i++)
+ _removeElement(elements[i], parent);
+ },
+ /**
+ * Sets whether or not the given element(s) should be draggable,
+ * regardless of what a particular plumb command may request.
+ *
+ * @param element
+ * May be a string, a element objects, or a list of
+ * strings/elements.
+ * @param draggable
+ * Whether or not the given element(s) should be draggable.
+ */
+ _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 id = _getAttribute(el, "id");
+ _operation(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 === 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).
+ *
+ * @param el
+ * 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.
+ *
+ * @param elId
+ * Id of the element in question
+ */
+ _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;
+ if (!recalc) {
+ if (timestamp && timestamp === offsetTimestamps[elId])
+ return offsets[elId];
+ }
+ if (recalc || !offset) { // if forced repaint or no offset
+ // available, we recalculate.
+ // get the current size and offset, and store them
+ var s = _getElementObject(elId);
+ if (s != null) {
+ sizes[elId] = _getSize(s);
+ offsets[elId] = _getOffset(s);
+ offsetTimestamps[elId] = timestamp;
+ }
+ } else {
+ offsets[elId] = offset;
+ if (sizes[elId] == null) {
+ var s = _getElementObject(elId);
+ if (s != null)
+ sizes[elId] = _getSize(s);
+ }
+ }
+
+ 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 offsets[elId];
+ },
+
+ // TODO comparison performance
+ _getCachedData = function(elId) {
+ var o = offsets[elId];
+ if (!o) o = _updateOffset({elId:elId});
+ 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) {
+ var ele = _getElementObject(element);
+ var id = _getAttribute(ele, "id");
+ if (!id || id == "undefined") {
+ // check if fixed uuid parameter is given
+ if (arguments.length == 2 && arguments[1] != undefined)
+ id = uuid;
+ else
+ id = "jsPlumb_" + _instanceIndex + "_" + _idstamp();
+ _setAttribute(ele, "id", id);
+ }
+ return id;
+ },
+
+ /**
+ * wraps one function with another, creating a placeholder for the
+ * wrapped function if it was null. this is used to wrap the various
+ * drag/drop event functions - to allow jsPlumb to be notified of
+ * important lifecycle events without imposing itself on the user's
+ * drag/drop functionality. TODO: determine whether or not we should
+ * support an error handler concept, if one of the functions fails.
+ *
+ * @param wrappedFunction original function to wrap; may be null.
+ * @param newFunction function to wrap the original with.
+ * @param returnOnThisValue Optional. Indicates that the wrappedFunction should
+ * not be executed if the newFunction returns a value matching 'returnOnThisValue'.
+ * note that this is a simple comparison and only works for primitives right now.
+ */
+ _wrap = function(wrappedFunction, newFunction, returnOnThisValue) {
+ wrappedFunction = wrappedFunction || function() { };
+ newFunction = newFunction || function() { };
+ return function() {
+ var r = null;
+ try {
+ r = newFunction.apply(this, arguments);
+ } catch (e) {
+ _log(_currentInstance, "jsPlumb function failed : " + e);
+ }
+ if (returnOnThisValue == null || (r !== returnOnThisValue)) {
+ try {
+ wrappedFunction.apply(this, arguments);
+ } catch (e) {
+ _log(_currentInstance, "wrapped function failed : " + e);
+ }
+ }
+ return r;
+ };
+ };
+
+ /*
+ * Property: connectorClass
+ * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is.
+ */
+ this.connectorClass = "_jsPlumb_connector";
+
+ /*
+ * Property: endpointClass
+ * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is.
+ */
+ this.endpointClass = "_jsPlumb_endpoint";
+
+ /*
+ * Property: overlayClass
+ * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is.
+ */
+ this.overlayClass = "_jsPlumb_overlay";
+
+ this.Anchors = {};
+
+ this.Connectors = {
+ "canvas":{},
+ "svg":{},
+ "vml":{}
+ };
+
+ this.Endpoints = {
+ "canvas":{},
+ "svg":{},
+ "vml":{}
+ };
+
+ this.Overlays = {
+ "canvas":{},
+ "svg":{},
+ "vml":{}
+ };
+
+// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *****************************************
+ /*
+ * Function: bind
+ * Bind to an event on jsPlumb.
+ *
+ * Parameters:
+ * event - the event to bind. Available events on jsPlumb are:
+ * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback.
+ * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback.
+ * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback.
+ * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback.
+ * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback.
+ * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback.
+ *
+ * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event.
+ */
+
+ /*
+ * Function: clearListeners
+ * Clears either all listeners, or listeners for some specific event.
+ *
+ * Parameters:
+ * event - optional. constrains the clear to just listeners for this event.
+ */
+
+// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***********************************************************
+
+ /*
+ Function: addClass
+
+ Helper method to abstract out differences in setting css classes on the different renderer types.
+ */
+ this.addClass = function(el, clazz) {
+ return jsPlumb.CurrentLibrary.addClass(el, clazz);
+ };
+
+ /*
+ Function: removeClass
+
+ Helper method to abstract out differences in setting css classes on the different renderer types.
+ */
+ this.removeClass = function(el, clazz) {
+ return jsPlumb.CurrentLibrary.removeClass(el, clazz);
+ };
+
+ /*
+ Function: hasClass
+
+ Helper method to abstract out differences in testing for css classes on the different renderer types.
+ */
+ this.hasClass = function(el, clazz) {
+ return jsPlumb.CurrentLibrary.hasClass(el, clazz);
+ };
+
+ /*
+ Function: addEndpoint
+
+ Adds an <Endpoint> to a given element or elements.
+
+ Parameters:
+
+ el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these.
+ params - Object containing Endpoint constructor arguments. For more information, see <Endpoint>.
+ referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some
+ shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in
+ this object are anything that 'params' can contain. See <Endpoint>.
+
+ Returns:
+ The newly created <Endpoint>, if el referred to a single element. Otherwise, an array of newly created <Endpoint>s.
+
+ See Also:
+ <addEndpoints>
+ */
+ 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 = el.length && el.constructor != String ? el : [ el ];
+
+ for (var i = 0; i < inputs.length; i++) {
+ var _el = _getElementObject(inputs[i]), id = _getId(_el);
+ p.source = _el;
+ _updateOffset({ elId : id });
+ var e = _newEndpoint(p);
+ if (p.parentAnchor) e.parentAnchor = p.parentAnchor;
+ _addToList(endpointsByElement, id, e);
+ var myOffset = offsets[id], myWH = sizes[id];
+ var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e });
+ e.paint({ anchorLoc : anchorLoc });
+ results.push(e);
+ _currentInstance.dragManager.endpointAdded(_el);
+ }
+
+ return results.length == 1 ? results[0] : results;
+ };
+
+ /*
+ Function: addEndpoints
+ Adds a list of <Endpoint>s to a given element or elements.
+
+ Parameters:
+ target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these.
+ endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See <Endpoint>'s constructor documentation.
+ referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements.
+
+ Returns:
+ List of newly created <Endpoint>s, one for each entry in the 'endpoints' argument.
+
+ See Also:
+ <addEndpoint>
+ */
+ this.addEndpoints = function(el, endpoints, referenceParams) {
+ var results = [];
+ for ( var i = 0; i < endpoints.length; i++) {
+ var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams);
+ if (e.constructor == Array)
+ Array.prototype.push.apply(results, e);
+ else results.push(e);
+ }
+ return results;
+ };
+
+ /*
+ Function: animate
+ This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating
+ the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as
+ the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI.
+
+ Parameters:
+ el - Element to animate. Either an id, or a selector representing the element.
+ properties - The 'properties' argument you want passed to the library's animate call.
+ options - The 'options' argument you want passed to the library's animate call.
+
+ Returns:
+ void
+ */
+ this.animate = function(el, properties, options) {
+ var ele = _getElementObject(el), id = _getAttribute(el, "id");
+ options = options || {};
+ var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step'];
+ var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete'];
+ options[stepFunction] = _wrap(options[stepFunction], function() {
+ _currentInstance.repaint(id);
+ });
+
+ // onComplete repaints, just to make sure everything looks good at the end of the animation.
+ options[completeFunction] = _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);
+ var r = true;
+ if (l && l.length > 0) {
+ try {
+ for (var i = 0 ; i < l.length; i++) {
+ r = r && l[i](value);
+ }
+ }
+ catch (e) {
+ _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e);
+ }
+ }
+ return r;
+ };
+
+ /*
+ Function: connect
+ Establishes a <Connection> between two elements (or <Endpoint>s, which are themselves registered to elements).
+
+ Parameters:
+ params - Object containing constructor arguments for the Connection. See <Connection>'s constructor documentation.
+ referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of
+ Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection.
+
+ Returns:
+ The newly created <Connection>.
+ */
+ this.connect = function(params, referenceParams) {
+ // prepare a final set of parameters to create connection with
+ var _p = _prepareConnectionParams(params, referenceParams);
+ // 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) {
+ // a connect call will delete its created endpoints on detach, unless otherwise specified.
+ // this is because the endpoints belong to this connection only, and are no use to
+ // anyone else, so they hang around like a bad smell.
+ if (_p.deleteEndpointsOnDetach == null)
+ _p.deleteEndpointsOnDetach = true;
+
+ // create the connection. it is not yet registered
+ var jpc = _newConnection(_p);
+ // now add it the model, fire an event, and redraw
+ _finaliseConnection(jpc, _p);
+ return jpc;
+ }
+ };
+
+ /*
+ Function: deleteEndpoint
+ Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too)
+
+ Parameters:
+ object - either an <Endpoint> object (such as from an addEndpoint call), or a String UUID.
+
+ Returns:
+ void
+ */
+ this.deleteEndpoint = function(object) {
+ var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object;
+ if (endpoint) {
+ var uuid = endpoint.getUuid();
+ if (uuid) endpointsByUUID[uuid] = null;
+ endpoint.detachAll();
+ _removeElements(endpoint.endpoint.getDisplayElements());
+ _currentInstance.anchorManager.deleteEndpoint(endpoint);
+ for (var e in endpointsByElement) {
+ var endpoints = endpointsByElement[e];
+ if (endpoints) {
+ var newEndpoints = [];
+ for (var i = 0; i < endpoints.length; i++)
+ if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]);
+
+ endpointsByElement[e] = newEndpoints;
+ }
+ }
+ _currentInstance.dragManager.endpointDeleted(endpoint);
+ }
+ };
+
+ /*
+ Function: deleteEveryEndpoint
+ Deletes every <Endpoint>, and their associated <Connection>s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference
+between this method and jsPlumb.reset).
+
+ Returns:
+ void
+ */
+ this.deleteEveryEndpoint = function() {
+ for ( var id in endpointsByElement) {
+ var endpoints = endpointsByElement[id];
+ if (endpoints && endpoints.length) {
+ for ( var i = 0; i < endpoints.length; i++) {
+ _currentInstance.deleteEndpoint(endpoints[i]);
+ }
+ }
+ }
+ delete endpointsByElement;
+ endpointsByElement = {};
+ delete endpointsByUUID;
+ endpointsByUUID = {};
+ };
+
+ var fireDetachEvent = function(jpc, doFireEvent) {
+ // 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("jsPlumbConnectionDetached", params);
+ _currentInstance.anchorManager.connectionDetached(params);
+ };
+
+ /**
+ fires an event to indicate an existing connection is being dragged.
+ */
+ var fireConnectionDraggingEvent = function(jpc) {
+ _currentInstance.fire("connectionDrag", jpc);
+ };
+
+ var fireConnectionDragStopEvent = function(jpc) {
+ _currentInstance.fire("connectionDragStop", jpc);
+ }
+
+
+ /*
+ Function: detach
+ Detaches and then removes a <Connection>. From 1.3.5 this method has been altered to remove support for
+ specifying Connections by various parameters; you can now pass in a Connection as the first argument and
+ an optional parameters object as a second argument. If you need the functionality this method provided
+ before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and
+ then iterate through them, calling this for each one.
+
+ Parameters:
+ connection - the <Connection> to detach
+ params - optional parameters to the detach call. valid values here are
+ fireEvent : defaults to false; indicates you want jsPlumb to fire a connection
+ detached event. The thinking behind this is that if you made a programmatic
+ call to detach an event, you probably don't need the callback.
+ forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered.
+
+ Returns:
+ true if successful, false if not.
+ */
+ 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,
+ connection = firstArgIsConnection ? arguments[0] : params.connection;
+
+ if (connection) {
+ if (forceDetach || (connection.isDetachAllowed(connection)
+ && connection.endpoints[0].isDetachAllowed(connection)
+ && connection.endpoints[1].isDetachAllowed(connection))) {
+ if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection))
+ connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method
+ }
+ }
+ 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(_p.source),
+ targetId = _getId(_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);
+ }
+ }
+ });
+ }
+ }
+ };
+
+ /*
+ Function: detachAllConnections
+ Removes all an element's Connections.
+
+ Parameters:
+ el - either the id of the element, or a selector for the element.
+ params - optional parameters. alowed values:
+ fireEvent : defaults to true, whether or not to fire the detach event.
+
+ Returns:
+ void
+ */
+ this.detachAllConnections = function(el, params) {
+ params = params || {};
+ el = _getElementObject(el);
+ var id = _getAttribute(el, "id"),
+ endpoints = endpointsByElement[id];
+ if (endpoints && endpoints.length) {
+ for ( var i = 0; i < endpoints.length; i++) {
+ endpoints[i].detachAll(params.fireEvent);
+ }
+ }
+ };
+
+ /*
+ Function: detachEveryConnection
+ Remove all Connections from all elements, but leaves Endpoints in place.
+
+ Parameters:
+ params - optional params object containing:
+ fireEvent : whether or not to fire detach events. defaults to true.
+
+
+ Returns:
+ void
+
+ See Also:
+ <removeEveryEndpoint>
+ */
+ this.detachEveryConnection = function(params) {
+ params = params || {};
+ for ( var id in endpointsByElement) {
+ var endpoints = endpointsByElement[id];
+ if (endpoints && endpoints.length) {
+ for ( var i = 0; i < endpoints.length; i++) {
+ endpoints[i].detachAll(params.fireEvent);
+ }
+ }
+ }
+ delete connectionsByScope;
+ connectionsByScope = {};
+ };
+
+
+ /*
+ Function: draggable
+ Initialises the draggability of some element or elements. You should use this instead of y
+ our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your
+ underlying library's drag method is always called from this method.
+
+ Parameters:
+ el - either an element id, a list of element ids, or a selector.
+ options - options to pass through to the underlying library
+
+ Returns:
+ void
+ */
+ // TODO it would be nice if this supported a selector string, instead of an id.
+ this.draggable = function(el, options) {
+ if (typeof el == 'object' && el.length) {
+ for ( var i = 0; i < el.length; i++) {
+ var ele = _getElementObject(el[i]);
+ if (ele) _initDraggableIfNecessary(ele, true, options);
+ }
+ }
+ 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 ( var i = 0; i < el._nodes.length; i++) {
+ var ele = _getElementObject(el._nodes[i]);
+ if (ele) _initDraggableIfNecessary(ele, true, options);
+ }
+ }
+ else {
+ var ele = _getElementObject(el);
+ if (ele) _initDraggableIfNecessary(ele, true, options);
+ }
+ };
+
+ /*
+ Function: extend
+ Wraps the underlying library's extend functionality.
+
+ Parameters:
+ o1 - object to extend
+ o2 - object to extend o1 with
+
+ Returns:
+ o1, extended with all properties from o2.
+ */
+ this.extend = function(o1, o2) {
+ return jsPlumb.CurrentLibrary.extend(o1, o2);
+ };
+
+ /*
+ * Function: getDefaultEndpointType
+ * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass.
+ * you would make a call like this in your class's constructor:
+ * jsPlumb.getDefaultEndpointType().apply(this, arguments);
+ *
+ * Returns:
+ * the default Endpoint function used by jsPlumb.
+ */
+ this.getDefaultEndpointType = function() {
+ return Endpoint;
+ };
+
+ /*
+ * Function: getDefaultConnectionType
+ * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass.
+ * you would make a call like this in your class's constructor:
+ * jsPlumb.getDefaultConnectionType().apply(this, arguments);
+ *
+ * Returns:
+ * the default Connection function used by jsPlumb.
+ */
+ this.getDefaultConnectionType = function() {
+ return Connection;
+ };
+
+ /*
+ * Function: getConnections
+ * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method,
+ * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the
+ * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }.
+ *
+ * Parameters
+ * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list
+ * of connections that are in the given scope.
+ * options - if the argument is a JS object, you can specify a finer-grained filter:
+ *
+ * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes.
+ * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source.
+ * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target.
+ *
+ */
+ this.getConnections = function(options) {
+ if (!options) {
+ options = {};
+ } else if (options.constructor == String) {
+ options = { "scope": options };
+ }
+ var prepareList = function(input) {
+ var r = [];
+ if (input) {
+ if (typeof input == 'string')
+ r.push(input);
+ else
+ r = input;
+ }
+ return r;
+ },
+ scope = options.scope || _currentInstance.getDefaultScope(),
+ scopes = prepareList(scope),
+ sources = prepareList(options.source),
+ targets = prepareList(options.target),
+ filter = function(list, value) {
+ return list.length > 0 ? _indexOf(list, value) != -1 : true;
+ },
+ results = scopes.length > 1 ? {} : [],
+ _addOne = function(scope, obj) {
+ if (scopes.length > 1) {
+ var ss = results[scope];
+ if (ss == null) {
+ ss = []; results[scope] = ss;
+ }
+ ss.push(obj);
+ } else results.push(obj);
+ };
+ for ( var i in connectionsByScope) {
+ if (filter(scopes, i)) {
+ for ( var j = 0; j < connectionsByScope[i].length; j++) {
+ var c = connectionsByScope[i][j];
+ if (filter(sources, c.sourceId) && filter(targets, c.targetId))
+ _addOne(i, c);
+ }
+ }
+ }
+ return results;
+ };
+
+ /*
+ * Function: getAllConnections
+ * Gets all connections, as a map of { scope -> [ connection... ] }.
+ */
+ this.getAllConnections = function() {
+ return connectionsByScope;
+ };
+
+ /*
+ * Function: getDefaultScope
+ * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a
+ * scope to an endpoint or connection allows you to support different
+ * types of connections in the same UI. but if you're only interested in
+ * one type of connection, you don't need to supply a scope. this method
+ * will probably be used by very few people; it's good for testing
+ * though.
+ */
+ this.getDefaultScope = function() {
+ return DEFAULT_SCOPE;
+ };
+
+ /*
+ Function: getEndpoint
+ Gets an Endpoint by UUID
+
+ Parameters:
+ uuid - the UUID for the Endpoint
+
+ Returns:
+ Endpoint with the given UUID, null if nothing found.
+ */
+ this.getEndpoint = _getEndpoint;
+
+ /**
+ * Function:getEndpoints
+ * Gets the list of Endpoints for a given selector, or element id.
+ * @param el
+ * @return
+ */
+ this.getEndpoints = function(el) {
+ return endpointsByElement[_getId(el)];
+ };
+
+ /*
+ * 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(spec) {
+ return jsPlumb.CurrentLibrary.getSelector(spec);
+ };
+
+ 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; };
+
+ /*
+ Function: hide
+ Sets an element's connections to be hidden.
+
+ Parameters:
+ el - either the id of the element, or a selector for the element.
+ changeEndpoints - whether not to also hide endpoints on the element. by default this is false.
+
+ Returns:
+ void
+ */
+ this.hide = function(el, changeEndpoints) {
+ _setVisible(el, "none", changeEndpoints);
+ };
+
+ // exposed for other objects to use to get a unique id.
+ this.idstamp = _idstamp;
+
+ /**
+ * 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)
+ * @return
+ */
+ this.init = function() {
+ if (!initialized) {
+ _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run.
+
+ var bindOne = function(event) {
+ jsPlumb.CurrentLibrary.bind(document, event, function(e) {
+ if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) {
+ // try connections first
+ for (var scope in connectionsByScope) {
+ var c = connectionsByScope[scope];
+ for (var i = 0; i < c.length; i++) {
+ var t = c[i].connector[event](e);
+ if (t) return;
+ }
+ }
+ for (var el in endpointsByElement) {
+ var ee = endpointsByElement[el];
+ for (var i = 0; i < ee.length; i++) {
+ if (ee[i].endpoint[event](e)) return;
+ }
+ }
+ }
+ });
+ };
+ bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu");
+
+ initialized = true;
+ _currentInstance.fire("ready");
+ }
+ };
+
+ this.log = log;
+ this.jsPlumbUIComponent = jsPlumbUIComponent;
+ this.EventGenerator = EventGenerator;
+
+ /*
+ * Creates an anchor with the given params.
+ *
+ *
+ * Returns: The newly created Anchor.
+ */
+ this.makeAnchor = function() {
+ if (arguments.length == 0) return null;
+ var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null;
+ if (!jsPlumbInstance)
+ throw "NO JSPLUMB SET";
+ // 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 = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance});
+ }
+ // is it an array? it will be one of:
+ // an array of [name, params] - this defines a single anchor
+ // an array of arrays - this defines some dynamic anchors
+ // an array of numbers - this defines a single anchor.
+ else if (specimen.constructor == Array) {
+ if (specimen[0].constructor == Array || specimen[0].constructor == String) {
+ if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) {
+ var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]);
+ newAnchor = jsPlumb.Anchors[specimen[0]](pp);
+ }
+ else
+ newAnchor = new DynamicAnchor(specimen, null, elementId);
+ }
+ 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
+ };
+ newAnchor = new Anchor(anchorParams);
+ newAnchor.clone = function() { return new 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; i < types.length; i++) {
+ if (typeof types[i] == "string")
+ r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance}));
+ else if (types[i].constructor == Array)
+ 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 DynamicAnchor(anchors, anchorSelector);
+ };
+
+ /**
+ * Function: makeTarget
+ * Makes some DOM element a Connection target, allowing you to drag connections to it
+ * without having to register any Endpoints on it first. When a Connection is established,
+ * the endpoint spec that was passed in to this method is used to create a suitable
+ * Endpoint (the default will be used if you do not provide one).
+ *
+ * Parameters:
+ * el - string id or element selector for the element to make a target.
+ * params - JS object containing parameters:
+ * endpoint optional. specification of an endpoint to create when a connection is created.
+ * scope optional. scope for the drop zone.
+ * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition.
+ * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete
+ * any Endpoints created by a connection to this target if
+ * the connection is subsequently detached. this will not
+ * remove Endpoints that have had more Connections attached
+ * to them after they were created.
+ *
+ *
+ */
+ var _targetEndpointDefinitions = {},
+ _targetEndpoints = {},
+ _targetEndpointsUnique = {};
+ var _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;
+ };
+ this.makeTarget = function(el, params, referenceParams) {
+
+ var p = jsPlumb.extend({}, referenceParams);
+ jsPlumb.extend(p, params);
+ _setEndpointPaintStylesAndAnchor(p, 1);
+ var jpcl = jsPlumb.CurrentLibrary,
+ targetScope = p.scope || _currentInstance.Defaults.Scope,
+ deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false),
+ _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.
+ var elid = _getId(_el);
+ _targetEndpointDefinitions[elid] = p;
+ _targetEndpointsUnique[elid] = p.uniqueEndpoint;
+
+ var dropOptions = jsPlumb.extend({}, p.dropOptions || {}),
+ _drop = function() {
+ _currentInstance.currentlyDragging = false;
+ var draggable = _getElementObject(jpcl.getDragObject(arguments)),
+ id = _getAttribute(draggable, "dragId"),
+ // restore the original scope if necessary (issue 57)
+ scope = _getAttribute(draggable, "originalScope"),
+ jpc = floatingConnections[id],
+ source = jpc.endpoints[0],
+ _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {};
+
+ // unlock the source anchor to allow it to refresh its position if necessary
+ source.anchor.locked = false;
+
+ if (scope) jpcl.setDragScope(draggable, scope);
+
+ // check if drop is allowed here.
+ var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope);
+
+ // regardless of whether the connection is ok, reconfigure the existing connection to
+ // point at the current info. we need this to be correct for the detach event that will follow.
+ // clear the source endpoint from the list to detach. we will detach this connection at this
+ // point, but we want to keep the source endpoint. the target is a floating endpoint and should
+ // be removed. TODO need to figure out whether this code can result in endpoints kicking around
+ // when they shouldnt be. like is this a full detach of a connection? can it be?
+ if (jpc.endpointsToDeleteOnDetach) {
+ if (source === jpc.endpointsToDeleteOnDetach[0])
+ jpc.endpointsToDeleteOnDetach[0] = null;
+ else if (source === jpc.endpointsToDeleteOnDetach[1])
+ jpc.endpointsToDeleteOnDetach[1] = 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.targetId = jpc.suspendedEndpoint.elementId;
+ jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId);
+ jpc.endpoints[1] = jpc.suspendedEndpoint;
+ }
+
+ if (_continue) {
+
+ // detach this connection from the source.
+ source.detach(jpc, false, true, false);//source.endpointWillMoveAfterConnection);
+
+ // make a new Endpoint for the target
+ //var newEndpoint = _currentInstance.addEndpoint(_el, _endpoint);
+
+ var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p);
+ if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok.
+
+ // 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),
+ elPosition = jpcl.getOffset(_el),
+ elSize = jpcl.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.
+ }
+ var c = _currentInstance.connect({
+ source:source,
+ target:newEndpoint,
+ scope:scope,
+ previousConnection:jpc,
+ container:jpc.parent,
+ deleteEndpointsOnDetach:deleteEndpointsOnDetach,
+ // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the
+ // given endpoint will actually transfer from the element it is currently attached to to some other
+ // element after a connection has been established. in that case, we do not want to fire the
+ // connection event, since it will have the wrong data in it; makeSource will do it for us.
+ // this is controlled by the 'parent' parameter on a makeSource call.
+ doNotFireConnectionEvent:source.endpointWillMoveAfterConnection
+ });
+ if (deleteEndpointsOnDetach)
+ c.endpointsToDeleteOnDetach = [ source, newEndpoint ];
+
+ c.repaint();
+ }
+ // if not allowed to drop...
+ else {
+ // TODO this code is identical (pretty much) to what happens when a connection
+ // dragged from a normal endpoint is in this situation. refactor.
+ // is this an existing connection, and will we reattach?
+ if (jpc.suspendedEndpoint) {
+ if (source.isReattach) {
+ jpc.setHover(false);
+ jpc.floatingAnchorIndex = null;
+ jpc.suspendedEndpoint.addConnection(jpc);
+ _currentInstance.repaint(source.elementId);
+ }
+ else
+ source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it.
+ }
+
+ }
+ };
+
+ var dropEvent = jpcl.dragEvents['drop'];
+ dropOptions["scope"] = dropOptions["scope"] || targetScope;
+ dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop);
+
+ jpcl.initDroppable(_el, dropOptions, true);
+ };
+
+ el = _convertYUICollection(el);
+
+ var inputs = el.length && el.constructor != String ? el : [ el ];
+
+ for (var i = 0; i < inputs.length; i++) {
+ _doOne(_getElementObject(inputs[i]));
+ }
+ };
+
+ /**
+ * helper method to make a list of elements drop targets.
+ * @param els
+ * @param params
+ * @param referenceParams
+ * @return
+ */
+ this.makeTargets = function(els, params, referenceParams) {
+ for ( var i = 0; i < els.length; i++) {
+ _currentInstance.makeTarget(els[i], params, referenceParams);
+ }
+ };
+
+ /**
+ * Function: makeSource
+ * Makes some DOM element a Connection source, allowing you to drag connections from it
+ * without having to register any Endpoints on it first. When a Connection is established,
+ * the endpoint spec that was passed in to this method is used to create a suitable
+ * Endpoint (the default will be used if you do not provide one).
+ *
[... 6223 lines stripped ...]