/demo/js/1.3.3/jquery.jsPlumb-1.3.3-all.js
http://jsplumb.googlecode.com/ · JavaScript · 5929 lines · 3395 code · 556 blank · 1978 comment · 895 complexity · 31ab9d70fbcb2c6a22c6ae14a849597c MD5 · raw file
Large files are truncated click here to view the full file
- /*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.3
- *
- * 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 - 2011 Simon Porritt (simon.porritt@gmail.com)
- *
- * http://jsplumb.org
- * http://code.google.com/p/jsplumb
- *
- * Triple licensed under the MIT, GPL2 and Beer 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;
- var 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.
- var vmlAvailable = !(canvasAvailable | svgAvailable);
-
- var _findIndex = function(a, v, b, s) {
- var _eq = function(o1, o2) {
- if (o1 === o2)
- return true;
- else if (typeof o1 == "object" && typeof o2 == "object") {
- var same = true;
- for ( var propertyName in o1) {
- if (!_eq(o1[propertyName], o2[propertyName])) {
- same = false;
- break;
- }
- }
- for ( var propertyName in o2) {
- if (!_eq(o2[propertyName], o1[propertyName])) {
- same = false;
- break;
- }
- }
- return same;
- }
- };
- for ( var i = +b || 0, l = a.length; i < l; i++) {
- if (_eq(a[i], v))
- return i;
- }
- return -1;
- };
-
- /**
- * helper method to add an item to a list, creating the list if it does
- * not yet exist.
- */
- var _addToList = function(map, key, value) {
- var l = map[key];
- if (l == null) {
- l = [];
- map[key] = l;
- }
- l.push(value);
- return l;
- };
- var _connectionBeingDragged = null;
- var _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)); },
- _log = function(jsp, msg) {
- if (jsp.logEnabled && typeof console != "undefined")
- console.log(msg);
- };
-
-
- /**
- * EventGenerator
- * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend.
- */
- var 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).
- * o riginalEvent - 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)
- _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 = {};
- }
- };
-
- };
-
- /*
- * 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.
- */
- var jsPlumbUIComponent = function(params) {
- var self = this, a = arguments, _hover = false;
- self._jsPlumb = params["_jsPlumb"];
- // 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.overlayPlacements = [],
- this.paintStyle = null,
- this.hoverPaintStyle = null;
-
- // 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) {
- _hover = hover;
- if (self.hoverPaintStyle != null) {
- self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle;
- self.repaint();
- // get the list of other affected elements. for a connection, its the endpoints. for an endpoint, its the connections! surprise.
- if (!ignoreAttachedElements)
- _updateAttachedElements(hover);
- }
- };
-
- this.isHover = function() {
- return _hover;
- };
-
- this.attachListeners = function(o, c) {
- var jpcl = jsPlumb.CurrentLibrary,
- events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup" ],
- eventFilters = { "mouseout":"mouseexit" },
- bindOne = function(evt) {
- var filteredEvent = eventFilters[evt] || evt;
- jpcl.bind(o, evt, function(ee) {
- c.fire(filteredEvent, c, ee);
- });
- };
- for (var i = 0; i < events.length; i++) {
- bindOne(events[i]);
- }
- };
-
- var _updateAttachedElements = function(state) {
- var affectedElements = self.getAttachedElements(); // implemented in subclasses
- if (affectedElements) {
- for (var i = 0; i < affectedElements.length; i++) {
- affectedElements[i].setHover(state, true); // tell the attached elements not to inform their own attached elements.
- }
- }
- };
- };
-
- 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"].
- * - *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".
- * - *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. Defaults to an empty list.
- * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1.
- * - *MouseEventsEnabled* Whether or not mouse events are enabled when using the canvas renderer. Defaults to true.
- * The idea of this is just to give people a way to prevent all the mouse listeners from activating if they know they won't need mouse events.
- * - *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 Canvas elements.
- * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories.
- */
- this.Defaults = {
- Anchor : "BottomCenter",
- Anchors : [ null, null ],
- Connector : "Bezier",
- DragOptions : { },
- DropOptions : { },
- Endpoint : "Dot",
- Endpoints : [ null, null ],
- EndpointStyle : { fillStyle : "#456" },
- EndpointStyles : [ null, null ],
- EndpointHoverStyle : null,
- EndpointHoverStyles : [ null, null ],
- HoverPaintStyle : null,
- LabelStyle : { color : "black" },
- LogEnabled : false,
- Overlays : [ ],
- MaxConnections : 1,
- MouseEventsEnabled : true,
- PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
- RenderMode : "canvas",
- Scope : "_jsPlumb_DefaultScope"
- };
- if (_defaults) jsPlumb.extend(this.Defaults, _defaults);
-
- this.logEnabled = this.Defaults.LogEnabled;
- EventGenerator.apply(this);
- var _bb = this.bind;
- this.bind = function(event, fn) {
- if ("ready" === event && initialized) fn();
- else _bb(event, fn);
- };
- var _currentInstance = this,
- 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 = {},
- _mouseEventsEnabled = this.Defaults.MouseEventsEnabled,
- _draggableByDefault = true,
- 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 jsPlumb.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);
- },
- /**
- * creates a timestamp, using milliseconds since 1970, but as a string.
- */
- _timestamp = function() { return "" + (new Date()).getTime(); },
-
- /**
- * 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.
- *
- * @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) {
- var id = _getAttribute(element, "id");
- var endpoints = endpointsByElement[id];
- if (!timestamp) timestamp = _timestamp();
- if (endpoints) {
- _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is
- // valid for one paint cycle.
- var myOffset = offsets[id], myWH = sizes[id];
- for ( var i = 0; i < endpoints.length; i++) {
- endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH });
- var l = endpoints[i].connections;
- for ( var j = 0; j < l.length; j++) {
- l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection.
- // then, check for dynamic endpoint; need to repaint it.
- var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0,
- otherEndpoint = l[j].endpoints[oIdx];
- if (otherEndpoint.anchor.isDynamic && !otherEndpoint.isFloating()) {
- _updateOffset( { elId : otherEndpoint.elementId, timestamp : timestamp });
- otherEndpoint.paint({ elementWithPrecedence:id });
- // all the connections for the other endpoint now need to be repainted
- for (var k = 0; k < otherEndpoint.connections.length; k++) {
- if (otherEndpoint.connections[k] !== l)
- otherEndpoint.connections[k].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp });
- }
- }
- }
- }
- }
- },
- /**
- * 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 ? _draggableByDefault : isDraggable;
- if (draggable) {
- if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) {
- var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions;
- options = jsPlumb.extend( {}, options); // make a copy.
- var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag'];
- var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop'];
- options[dragEvent] = _wrap(options[dragEvent], function() {
- var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments);
- _draw(element, ui);
- _addClass(element, "jsPlumb_dragged");
- });
- options[stopEvent] = _wrap(options[stopEvent], function() {
- var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments);
- _draw(element, ui);
- _removeClass(element, "jsPlumb_dragged");
- });
- var draggable = draggableStates[_getId(element)];
- options.disabled = draggable == null ? false : !draggable;
- jsPlumb.CurrentLibrary.initDraggable(element, options);
- }
- }
- },
-
- _newConnection = function(params) {
- var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection,
- endpointFunc = jsPlumb.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);
- _eventFireProxy("click", "click", con);
- _eventFireProxy("dblclick", "dblclick", con);
- return con;
- },
-
- _eventFireProxy = function(event, proxyEvent, obj) {
- obj.bind(event, function(originalObject, originalEvent) {
- _currentInstance.fire(proxyEvent, obj, originalEvent);
- });
- },
-
- _newEndpoint = function(params) {
- var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint;
- if (params.container)
- params.parent = params.container;
- else
- params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source);
- params["_jsPlumb"] = _currentInstance,
- ep = new endpointFunc(params);
- _eventFireProxy("click", "endpointClick", ep);
- _eventFireProxy("dblclick", "endpointDblClick", 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);
- },
- /**
- * helper method to remove an item from a list.
- */
- _removeFromList = function(map, key, value) {
- if (key != null) {
- var l = map[key];
- if (l != null) {
- var i = _findIndex(l, value);
- if (i >= 0) {
- delete (l[i]);
- l.splice(i, 1);
- return true;
- }
- }
- }
- return false;
- },
- /**
- * 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 ? _draggableByDefault : 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 == null) { // 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;
- }
- return offsets[elId];
- },
- /**
- * gets an id for the given element, creating and setting one if
- * necessary.
- */
- _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_" + _timestamp();
- _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: 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);
- _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);
- }
-
- 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);
- };
- /*
- 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) {
- var _p = jsPlumb.extend( {}, params);
- if (referenceParams) jsPlumb.extend(_p, referenceParams);
-
- 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.
- if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) {
- _log(_currentInstance, "could not add connection; source endpoint is full");
- return;
- }
- if (_p.targetEndpoint && _p.targetEndpoint.isFull()) {
- _log(_currentInstance, "could not add connection; target endpoint is full");
- return;
- }
-
- if (_p.target && !_p.target.endpoint) {
- var tid = _getId(_p.target),
- tep =_targetEndpointDefinitions[tid];
- var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) {
- if (tep[tepProperty]) {
- if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty];
- else if (_p[singlePropertyName]) {
- _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ];
- _p[singlePropertyName] = null;
- }
- else _p[pluralPropertyName] = [ null, tep[tepProperty] ];
- }
- };
- if (tep) {
- overrideOne("endpoint", "endpoints", "endpoint", tep);
- overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep);
- overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep);
- }
- }
-
- // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists
- // of multiple definitions is enough to tell jsPlumb you want it to be dynamic.
- if (_p.dynamicAnchors) {
- // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which
- // case we will use a different set for each element.
- var a = _p.dynamicAnchors.constructor == Array;
- var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source));
- var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target));
- _p.anchors = [sa,ta];
- }
- var jpc = _newConnection(_p);
- // add to list of connections (by scope).
- _addToList(connectionsByScope, jpc.scope, jpc);
- // fire an event
- _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]
- });
- // force a paint
- _draw(jpc.source);
- 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();
- _removeElement(endpoint.canvas, endpoint.parent);
- // remove from endpointsbyElement
- 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;
- }
- }
- delete 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) {
- _currentInstance.fire("jsPlumbConnectionDetached", {
- connection:jpc,
- source : jpc.source, target : jpc.target,
- sourceId : jpc.sourceId, targetId : jpc.targetId,
- sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
- });
- };
- /*
- Function: detach
- Detaches and then removes a <Connection>. Takes either (source, target) (the old way, maintained for backwards compatibility), or a params
- object with various possible values.
-
- Parameters:
- source - id or element object of the first element in the Connection.
- target - id or element object of the second element in the Connection.
- params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor
- target should be present; it should be the only argument to the method. See the docs for <Connection>'s constructor for information
- about the parameters allowed in the params object.
- Returns:
- true if successful, false if not.
- */
- this.detach = function(source, target) {
- if (arguments.length == 2) {
- var s = _getElementObject(source), sId = _getId(s);
- var t = _getElementObject(target), tId = _getId(t);
- _operation(sId, function(jpc) {
- if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) {
- _removeElements(jpc.connector.getDisplayElements(), jpc.parent);
- jpc.endpoints[0].removeConnection(jpc);
- jpc.endpoints[1].removeConnection(jpc);
- _removeFromList(connectionsByScope, jpc.scope, jpc);
- }
- });
- }
- // this is the new version of the method, taking a JS object like
- // the connect method does.
- else if (arguments.length == 1) {
- // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not.
- if (arguments[0].constructor == Connection) {
- arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]);
- }
- else if (arguments[0].connection) {
- arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]);
- }
- else {
- var _p = jsPlumb.extend( {}, source); // 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]));
- } else if (_p.sourceEndpoint && _p.targetEndpoint) {
- _p.sourceEndpoint.detachFrom(_p.targetEndpoint);
- } else {
- var sourceId = _getId(_p.source);
- var targetId = _getId(_p.target);
- _operation(sourceId, function(jpc) {
- if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) {
- _removeElements(jpc.connector.getDisplayElements(), jpc.parent);
- jpc.endpoints[0].removeConnection(jpc);
- jpc.endpoints[1].removeConnection(jpc);
- _removeFromList(connectionsByScope, jpc.scope, jpc);
- }
- });
- }
- }
- }
- };
- /*
- Function: detachAll
- Removes all an element's Connections.
-
- Parameters:
- el - either the id of the element, or a selector for the element.
-
- Returns:
- void
- */
- this.detachAllConnections = function(el) {
- var id = _getAttribute(el, "id");
- var endpoints = endpointsByElement[id];
- if (endpoints && endpoints.length) {
- for ( var i = 0; i < endpoints.length; i++) {
- endpoints[i].detachAll();
- }
- }
- };
-
- /**
- * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3.
- */
- this.detachAll = this.detachAllConnections;
- /*
- Function: detachEveryConnection
- Remove all Connections from all elements, but leaves Endpoints in place.
-
- Returns:
- void
-
- See Also:
- <removeEveryEndpoint>
- */
- this.detachEveryConnection = function() {
- for ( var id in endpointsByElement) {
- var endpoints = endpointsByElement[id];
- if (endpoints && endpoints.length) {
- for ( var i = 0; i < endpoints.length; i++) {
- endpoints[i].detachAll();
- }
- }
- }
- delete connectionsByScope;
- connectionsByScope = {};
- };
-
- /**
- * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3.
- */
- this.detachEverything = this.detachEveryConnection;
- /*
- Function: draggable
- Initialises the draggability of some element or elements. You should use this instead of you 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
- */
- 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 == St…