diff --git a/beautifulmind/mindmap/static/mindmap/js/lib/jquery.collisioncheck-1.1.js b/beautifulmind/mindmap/static/mindmap/js/lib/jquery.collisioncheck-1.1.js
deleted file mode 100644
index e01f549..0000000
--- a/beautifulmind/mindmap/static/mindmap/js/lib/jquery.collisioncheck-1.1.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-* Collision Check Plugin v1.1
-* Copyright (c) Constantin Groß, 48design.de
-* v1.2 rewrite with thanks to Daniel
-*
-* @requires jQuery v1.3.2
-* @description Checks single or groups of objects (divs, images or any other block element) for collission / overlapping
-* @returns an object collection with all colliding / overlapping html objects
-*
-* Dual licensed under the MIT and GPL licenses:
-* http://www.opensource.org/licenses/mit-license.php
-* http://www.gnu.org/licenses/gpl.html
-*
-*/
-(function($) {
- $.fn.collidesWith = function(elements) {
- var rects = this;
- var checkWith = $(elements);
- var c = $([]);
-
- if (!rects || !checkWith) { return false; }
-
- rects.each(function() {
- var rect = $(this);
-
- // define minimum and maximum coordinates
- var rectOff = rect.offset();
- var rectMinX = rectOff.left;
- var rectMinY = rectOff.top;
- var rectMaxX = rectMinX + rect.outerWidth();
- var rectMaxY = rectMinY + rect.outerHeight();
-
- checkWith.not(rect).each(function() {
- var otherRect = $(this);
- var otherRectOff = otherRect.offset();
- var otherRectMinX = otherRectOff.left;
- var otherRectMinY = otherRectOff.top;
- var otherRectMaxX = otherRectMinX + otherRect.outerWidth();
- var otherRectMaxY = otherRectMinY + otherRect.outerHeight();
-
- // check for intersection
- if ( rectMinX >= otherRectMaxX ||
- rectMaxX <= otherRectMinX ||
- rectMinY >= otherRectMaxY ||
- rectMaxY <= otherRectMinY ) {
- return true; // no intersection, continue each-loop
- } else {
- // intersection found, add only once
- if(c.length == c.not(this).length) { c.push(this); }
- }
- });
- });
- // return collection
- return c;
- }
-})(jQuery);
\ No newline at end of file
diff --git a/beautifulmind/mindmap/static/mindmap/js/lib/jquery.color.js b/beautifulmind/mindmap/static/mindmap/js/lib/jquery.color.js
deleted file mode 100644
index 1e5c2da..0000000
--- a/beautifulmind/mindmap/static/mindmap/js/lib/jquery.color.js
+++ /dev/null
@@ -1,664 +0,0 @@
-/*
- * jQuery Color Animations v@VERSION
- * http://jquery.org/
- *
- * Copyright 2011 John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Date: @DATE
- */
-
-(function( jQuery, undefined ){
- var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color outlineColor".split(" "),
-
- // plusequals test for += 100 -= 100
- rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
- // a set of RE's that can match strings and generate color tuples.
- stringParsers = [{
- re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
- parse: function( execResult ) {
- return [
- execResult[ 1 ],
- execResult[ 2 ],
- execResult[ 3 ],
- execResult[ 4 ]
- ];
- }
- }, {
- re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
- parse: function( execResult ) {
- return [
- 2.55 * execResult[1],
- 2.55 * execResult[2],
- 2.55 * execResult[3],
- execResult[ 4 ]
- ];
- }
- }, {
- re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,
- parse: function( execResult ) {
- return [
- parseInt( execResult[ 1 ], 16 ),
- parseInt( execResult[ 2 ], 16 ),
- parseInt( execResult[ 3 ], 16 )
- ];
- }
- }, {
- re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,
- parse: function( execResult ) {
- return [
- parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
- parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
- parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
- ];
- }
- }, {
- re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
- space: "hsla",
- parse: function( execResult ) {
- return [
- execResult[1],
- execResult[2] / 100,
- execResult[3] / 100,
- execResult[4]
- ];
- }
- }],
-
- // jQuery.Color( )
- color = jQuery.Color = function( color, green, blue, alpha ) {
- return new jQuery.Color.fn.parse( color, green, blue, alpha );
- },
- spaces = {
- rgba: {
- cache: "_rgba",
- props: {
- red: {
- idx: 0,
- type: "byte",
- empty: true
- },
- green: {
- idx: 1,
- type: "byte",
- empty: true
- },
- blue: {
- idx: 2,
- type: "byte",
- empty: true
- },
- alpha: {
- idx: 3,
- type: "percent",
- def: 1
- }
- }
- },
- hsla: {
- cache: "_hsla",
- props: {
- hue: {
- idx: 0,
- type: "degrees",
- empty: true
- },
- saturation: {
- idx: 1,
- type: "percent",
- empty: true
- },
- lightness: {
- idx: 2,
- type: "percent",
- empty: true
- }
- }
- }
- },
- propTypes = {
- "byte": {
- floor: true,
- min: 0,
- max: 255
- },
- "percent": {
- min: 0,
- max: 1
- },
- "degrees": {
- mod: 360,
- floor: true
- }
- },
- rgbaspace = spaces.rgba.props,
- support = color.support = {},
-
- // colors = jQuery.Color.names
- colors,
-
- // local aliases of functions called often
- each = jQuery.each;
-
- spaces.hsla.props.alpha = rgbaspace.alpha;
-
- function clamp( value, prop, alwaysAllowEmpty ) {
- var type = propTypes[ prop.type ] || {},
- allowEmpty = prop.empty || alwaysAllowEmpty;
-
- if ( allowEmpty && value == null ) {
- return null;
- }
- if ( prop.def && value == null ) {
- return prop.def;
- }
- if ( type.floor ) {
- value = ~~value;
- } else {
- value = parseFloat( value );
- }
- if ( value == null || isNaN( value ) ) {
- return prop.def;
- }
- if ( type.mod ) {
- value = value % type.mod;
- // -10 -> 350
- return value < 0 ? type.mod + value : value;
- }
-
- // for now all property types without mod have min and max
- return type.min > value ? type.min : type.max < value ? type.max : value;
- }
-
- function stringParse( string ) {
- var inst = color(),
- rgba = inst._rgba = [];
-
- string = string.toLowerCase();
-
- each( stringParsers, function( i, parser ) {
- var match = parser.re.exec( string ),
- values = match && parser.parse( match ),
- parsed,
- spaceName = parser.space || "rgba",
- cache = spaces[ spaceName ].cache;
-
-
- if ( values ) {
- parsed = inst[ spaceName ]( values );
-
- // if this was an rgba parse the assignment might happen twice
- // oh well....
- inst[ cache ] = parsed[ cache ];
- rgba = inst._rgba = parsed._rgba;
-
- // exit each( stringParsers ) here because we matched
- return false;
- }
- });
-
- // Found a stringParser that handled it
- if ( rgba.length !== 0 ) {
-
- // if this came from a parsed string, force "transparent" when alpha is 0
- // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
- if ( Math.max.apply( Math, rgba ) === 0 ) {
- jQuery.extend( rgba, colors.transparent );
- }
- return inst;
- }
-
- // named colors / default - filter back through parse function
- if ( string = colors[ string ] ) {
- return string;
- }
- }
-
- color.fn = color.prototype = {
- constructor: color,
- parse: function( red, green, blue, alpha ) {
- if ( red === undefined ) {
- this._rgba = [ null, null, null, null ];
- return this;
- }
- if ( red instanceof jQuery || red.nodeType ) {
- red = red instanceof jQuery ? red.css( green ) : jQuery( red ).css( green );
- green = undefined;
- }
-
- var inst = this,
- type = jQuery.type( red ),
- rgba = this._rgba = [],
- source;
-
- // more than 1 argument specified - assume ( red, green, blue, alpha )
- if ( green !== undefined ) {
- red = [ red, green, blue, alpha ];
- type = "array";
- }
-
- if ( type === "string" ) {
- return this.parse( stringParse( red ) || colors._default );
- }
-
- if ( type === "array" ) {
- each( rgbaspace, function( key, prop ) {
- rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
- });
- return this;
- }
-
- if ( type === "object" ) {
- if ( red instanceof color ) {
- each( spaces, function( spaceName, space ) {
- if ( red[ space.cache ] ) {
- inst[ space.cache ] = red[ space.cache ].slice();
- }
- });
- } else {
- each( spaces, function( spaceName, space ) {
- each( space.props, function( key, prop ) {
- var cache = space.cache;
-
- // if the cache doesn't exist, and we know how to convert
- if ( !inst[ cache ] && space.to ) {
-
- // if the value was null, we don't need to copy it
- // if the key was alpha, we don't need to copy it either
- if ( red[ key ] == null || key === "alpha") {
- return;
- }
- inst[ cache ] = space.to( inst._rgba );
- }
-
- // this is the only case where we allow nulls for ALL properties.
- // call clamp with alwaysAllowEmpty
- inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
- });
- });
- }
- return this;
- }
- },
- is: function( compare ) {
- var is = color( compare ),
- same = true,
- myself = this;
-
- each( spaces, function( _, space ) {
- var isCache = is[ space.cache ],
- localCache;
- if (isCache) {
- localCache = myself[ space.cache ] || space.to && space.to( myself._rgba ) || [];
- each( space.props, function( _, prop ) {
- if ( isCache[ prop.idx ] != null ) {
- same = ( isCache[ prop.idx ] == localCache[ prop.idx ] );
- return same;
- }
- });
- }
- return same;
- });
- return same;
- },
- _space: function() {
- var used = [],
- inst = this;
- each( spaces, function( spaceName, space ) {
- if ( inst[ space.cache ] ) {
- used.push( spaceName );
- }
- });
- return used.pop();
- },
- transition: function( other, distance ) {
- var end = color( other ),
- spaceName = end._space(),
- space = spaces[ spaceName ],
- start = this[ space.cache ] || space.to( this._rgba ),
- result = start.slice();
-
- end = end[ space.cache ];
- each( space.props, function( key, prop ) {
- var index = prop.idx,
- startValue = start[ index ],
- endValue = end[ index ],
- type = propTypes[ prop.type ] || {};
-
- // if null, don't override start value
- if ( endValue === null ) {
- return;
- }
- // if null - use end
- if ( startValue === null ) {
- result[ index ] = endValue;
- } else {
- if ( type.mod ) {
- if ( endValue - startValue > type.mod / 2 ) {
- startValue += type.mod;
- } else if ( startValue - endValue > type.mod / 2 ) {
- startValue -= type.mod;
- }
- }
- result[ prop.idx ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
- }
- });
- return this[ spaceName ]( result );
- },
- blend: function( opaque ) {
- // if we are already opaque - return ourself
- if ( this._rgba[ 3 ] === 1 ) {
- return this;
- }
-
- var rgb = this._rgba.slice(),
- a = rgb.pop(),
- blend = color( opaque )._rgba;
-
- return color( jQuery.map( rgb, function( v, i ) {
- return ( 1 - a ) * blend[ i ] + a * v;
- }));
- },
- toRgbaString: function() {
- var prefix = "rgba(",
- rgba = jQuery.map( this._rgba, function( v, i ) {
- return v == null ? ( i > 2 ? 1 : 0 ) : v;
- });
-
- if ( rgba[ 3 ] === 1 ) {
- rgba.pop();
- prefix = "rgb(";
- }
-
- return prefix + rgba.join(",") + ")";
- },
- toHslaString: function() {
- var prefix = "hsla(",
- hsla = jQuery.map( this.hsla(), function( v, i ) {
- if ( v == null ) {
- v = i > 2 ? 1 : 0;
- }
-
- // catch 1 and 2
- if ( i && i < 3 ) {
- v = Math.round( v * 100 ) + "%";
- }
- return v;
- });
-
- if ( hsla[ 3 ] == 1 ) {
- hsla.pop();
- prefix = "hsl(";
- }
- return prefix + hsla.join(",") + ")";
- },
- toHexString: function( includeAlpha ) {
- var rgba = this._rgba.slice(),
- alpha = rgba.pop();
-
- if ( includeAlpha ) {
- rgba.push( ~~( alpha * 255 ) );
- }
-
- return "#" + jQuery.map( rgba, function( v, i ) {
-
- // default to 0 when nulls exist
- v = ( v || 0 ).toString( 16 );
- return v.length == 1 ? "0" + v : v;
- }).join("");
- },
- toString: function() {
- return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
- }
- };
- color.fn.parse.prototype = color.fn;
-
- // hsla conversions adapted from:
- // http://www.google.com/codesearch/p#OAMlx_jo-ck/src/third_party/WebKit/Source/WebCore/inspector/front-end/Color.js&d=7&l=193
-
- function hue2rgb( p, q, h ) {
- h = ( h + 1 ) % 1;
- if ( h * 6 < 1 ) {
- return p + (q - p) * 6 * h;
- }
- if ( h * 2 < 1) {
- return q;
- }
- if ( h * 3 < 2 ) {
- return p + (q - p) * ((2/3) - h) * 6;
- }
- return p;
- }
-
- spaces.hsla.to = function ( rgba ) {
- if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
- return [ null, null, null, rgba[ 3 ] ];
- }
- var r = rgba[ 0 ] / 255,
- g = rgba[ 1 ] / 255,
- b = rgba[ 2 ] / 255,
- a = rgba[ 3 ],
- max = Math.max( r, g, b ),
- min = Math.min( r, g, b ),
- diff = max - min,
- add = max + min,
- l = add * 0.5,
- h, s;
-
- if ( min === max ) {
- h = 0;
- } else if ( r === max ) {
- h = ( 60 * ( g - b ) / diff ) + 360;
- } else if ( g === max ) {
- h = ( 60 * ( b - r ) / diff ) + 120;
- } else {
- h = ( 60 * ( r - g ) / diff ) + 240;
- }
-
- if ( l === 0 || l === 1 ) {
- s = l;
- } else if ( l <= 0.5 ) {
- s = diff / add;
- } else {
- s = diff / ( 2 - add );
- }
- return [ Math.round(h) % 360, s, l, a == null ? 1 : a ];
- };
-
- spaces.hsla.from = function ( hsla ) {
- if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
- return [ null, null, null, hsla[ 3 ] ];
- }
- var h = hsla[ 0 ] / 360,
- s = hsla[ 1 ],
- l = hsla[ 2 ],
- a = hsla[ 3 ],
- q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
- p = 2 * l - q,
- r, g, b;
-
- return [
- Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
- Math.round( hue2rgb( p, q, h ) * 255 ),
- Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
- a
- ];
- };
-
-
- each( spaces, function( spaceName, space ) {
- var props = space.props,
- cache = space.cache,
- to = space.to,
- from = space.from;
-
- // makes rgba() and hsla()
- color.fn[ spaceName ] = function( value ) {
-
- // generate a cache for this space if it doesn't exist
- if ( to && !this[ cache ] ) {
- this[ cache ] = to( this._rgba );
- }
- if ( value === undefined ) {
- return this[ cache ].slice();
- }
-
- var type = jQuery.type( value ),
- arr = ( type === "array" || type === "object" ) ? value : arguments,
- local = this[ cache ].slice(),
- ret;
-
- each( props, function( key, prop ) {
- var val = arr[ type === "object" ? key : prop.idx ];
- if ( val == null ) {
- val = local[ prop.idx ];
- }
- local[ prop.idx ] = clamp( val, prop );
- });
-
- if ( from ) {
- ret = color( from( local ) );
- ret[ cache ] = local;
- return ret;
- } else {
- return color( local );
- }
- };
-
- // makes red() green() blue() alpha() hue() saturation() lightness()
- each( props, function( key, prop ) {
- // alpha is included in more than one space
- if ( color.fn[ key ] ) {
- return;
- }
- color.fn[ key ] = function( value ) {
- var vtype = jQuery.type( value ),
- fn = ( key === 'alpha' ? ( this._hsla ? 'hsla' : 'rgba' ) : spaceName ),
- local = this[ fn ](),
- cur = local[ prop.idx ],
- match;
-
- if ( vtype === "undefined" ) {
- return cur;
- }
-
- if ( vtype === "function" ) {
- value = value.call( this, cur );
- vtype = jQuery.type( value );
- }
- if ( value == null && prop.empty ) {
- return this;
- }
- if ( vtype === "string" ) {
- match = rplusequals.exec( value );
- if ( match ) {
- value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
- }
- }
- local[ prop.idx ] = value;
- return this[ fn ]( local );
- };
- });
- });
-
- // add .fx.step functions
- each( stepHooks, function( i, hook ) {
- jQuery.cssHooks[ hook ] = {
- set: function( elem, value ) {
- var parsed;
-
- if ( jQuery.type( value ) !== 'string' || ( parsed = stringParse( value ) ) )
- {
- value = color( parsed || value );
- if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
- var backgroundColor,
- curElem = hook === "backgroundColor" ? elem.parentNode : elem;
- do {
- backgroundColor = jQuery.curCSS( curElem, "backgroundColor" );
- } while (
- ( backgroundColor === "" || backgroundColor === "transparent" ) &&
- ( curElem = curElem.parentNode ) &&
- curElem.style
- );
-
- value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
- backgroundColor :
- "_default" );
- }
-
- value = value.toRgbaString();
- }
- elem.style[ hook ] = value;
- }
- };
- jQuery.fx.step[ hook ] = function( fx ) {
- if ( !fx.colorInit ) {
- fx.start = color( fx.elem, hook );
- fx.end = color( fx.end );
- fx.colorInit = true;
- }
- jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
- };
- });
-
- // detect rgba support
- jQuery(function() {
- var div = document.createElement( "div" ),
- div_style = div.style;
-
- div_style.cssText = "background-color:rgba(1,1,1,.5)";
- support.rgba = div_style.backgroundColor.indexOf( "rgba" ) > -1;
- });
-
- // Some named colors to work with
- // From Interface by Stefan Petre
- // http://interface.eyecon.ro/
- colors = jQuery.Color.names = {
- aqua: "#00ffff",
- azure: "#f0ffff",
- beige: "#f5f5dc",
- black: "#000000",
- blue: "#0000ff",
- brown: "#a52a2a",
- cyan: "#00ffff",
- darkblue: "#00008b",
- darkcyan: "#008b8b",
- darkgrey: "#a9a9a9",
- darkgreen: "#006400",
- darkkhaki: "#bdb76b",
- darkmagenta: "#8b008b",
- darkolivegreen: "#556b2f",
- darkorange: "#ff8c00",
- darkorchid: "#9932cc",
- darkred: "#8b0000",
- darksalmon: "#e9967a",
- darkviolet: "#9400d3",
- fuchsia: "#ff00ff",
- gold: "#ffd700",
- green: "#008000",
- indigo: "#4b0082",
- khaki: "#f0e68c",
- lightblue: "#add8e6",
- lightcyan: "#e0ffff",
- lightgreen: "#90ee90",
- lightgrey: "#d3d3d3",
- lightpink: "#ffb6c1",
- lightyellow: "#ffffe0",
- lime: "#00ff00",
- magenta: "#ff00ff",
- maroon: "#800000",
- navy: "#000080",
- olive: "#808000",
- orange: "#ffa500",
- pink: "#ffc0cb",
- purple: "#800080",
- violet: "#800080",
- red: "#ff0000",
- silver: "#c0c0c0",
- white: "#ffffff",
- yellow: "#ffff00",
- transparent: [ null, null, null, 0 ],
- _default: "#ffffff"
- };
-})( jQuery );
diff --git a/beautifulmind/mindmap/static/mindmap/js/lib/jquery.jsPlumb-1.3.8-all.js b/beautifulmind/mindmap/static/mindmap/js/lib/jquery.jsPlumb-1.3.8-all.js
deleted file mode 100644
index 0e5c873..0000000
--- a/beautifulmind/mindmap/static/mindmap/js/lib/jquery.jsPlumb-1.3.8-all.js
+++ /dev/null
@@ -1,8761 +0,0 @@
-/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.8
- *
- * 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"),
- // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser
- vmlAvailable = function() {
- if(vmlAvailable.vml == undefined) {
- var a = document.body.appendChild(document.createElement('div'));
- a.innerHTML = '';
- var b = a.firstChild;
- b.style.behavior = "url(#default#VML)";
- vmlAvailable.vml = b ? typeof b.adj == "object": true;
- a.parentNode.removeChild(a);
- }
- return vmlAvailable.vml;
- };
-
- 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;
- },
- /**
- an isArray function that even works across iframes...see here:
-
- http://tobyho.com/2011/01/28/checking-types-in-javascript/
-
- i was originally using "a.constructor == Array" as a test.
- */
- _isArray = function(a) {
- return Object.prototype.toString.call(a) === "[object Array]";
- },
- _isString = function(s) {
- return typeof s === "string";
- },
- _isObject = function(o) {
- return Object.prototype.toString.call(o) === "[object Object]";
- };
-
- // 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(){} };
-
- 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 (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax
- // there's also a three arg version:
- // ["Arrow", { width:50 }, {location:0.7}]
- // which merges the 3rd arg into the 2nd.
- var type = o[0],
- // make a copy of the object so as not to mess up anyone else's reference...
- p = jsPlumb.extend({component: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:getOverlays
- * Gets all the overlays for this component.
- */
- this.getOverlays = function() {
- return self.overlays;
- };
-
- /*
- * Function: hideOverlay
- * Hides the overlay specified by the given id.
- */
- this.hideOverlay = function(id) {
- var o = self.getOverlay(id);
- if (o) o.hide();
- };
-
- this.hideOverlays = function() {
- for (var i = 0; i < self.overlays.length; i++)
- self.overlays[i].hide();
- };
-
- /*
- * Function: showOverlay
- * Shows the overlay specified by the given id.
- */
- this.showOverlay = function(id) {
- var o = self.getOverlay(id);
- if (o) o.show();
- };
-
- this.showOverlays = function() {
- for (var i = 0; i < self.overlays.length; i++)
- self.overlays[i].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 (_isArray(element)) {
- 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[startEvent] = _wrap(options[startEvent], function() {
- _currentInstance.setHoverSuspended(true);
- });
-
- 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");
- _currentInstance.setHoverSuspended(false);
- });
- 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) {
- // check for max connections??
- var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep);
- if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint;
- _p.targetEndpoint = newEndpoint;
- newEndpoint._makeTargetCreator = true;
- }
- }
-
- // 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__
- *
- * where "index in instance" is a monotonically increasing integer that starts at 0,
- * for each instance. this method is used not only to assign ids to elements that do not
- * have them but also to connections and endpoints.
- */
- _getId = function(element, uuid, doNotCreateIfNotFound) {
- 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 if (arguments.length == 1 || (arguments.length == 3 && arguments[2]))
- 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 ***********************************************************
-
-
-// --------------------------- jsPLumbInstance public API ---------------------------------------------------------
-
- /*
- 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 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 .
- 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 .
-
- Returns:
- The newly created , if el referred to a single element. Otherwise, an array of newly created s.
-
- See Also:
-
- */
- 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 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 '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 s, one for each entry in the 'endpoints' argument.
-
- See Also:
-
- */
- 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 (_isArray(e))
- 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 between two elements (or s, which are themselves registered to elements).
-
- Parameters:
- params - Object containing constructor arguments for the Connection. See '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 .
- */
- 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 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 , and their associated 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, originalEvent) {
- // may have been given a connection, or in special cases, an object
- var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
- argIsConnection = jpc.constructor == connType,
- params = argIsConnection ? {
- connection:jpc,
- source : jpc.source, target : jpc.target,
- sourceId : jpc.sourceId, targetId : jpc.targetId,
- sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
- } : jpc;
-
- if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent);
- _currentInstance.anchorManager.connectionDetached(params);
- },
- /**
- fires an event to indicate an existing connection is being dragged.
- */
- fireConnectionDraggingEvent = function(jpc) {
- _currentInstance.fire("connectionDrag", jpc);
- },
- fireConnectionDragStopEvent = function(jpc) {
- _currentInstance.fire("connectionDragStop", jpc);
- };
-
-
- /*
- Function: detach
- Detaches and then removes a . 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 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:
-
- */
- 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 your
- 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;
- };
-
- // helpers for select/selectEndpoints
- var _setOperation = function(list, func, args, selector) {
- for (var i = 0; i < list.length; i++) {
- list[i][func].apply(list[i], args);
- }
- return selector(list);
- },
- _getOperation = function(list, func, args) {
- var out = [];
- for (var i = 0; i < list.length; i++) {
- out.push([ list[i][func].apply(list[i], args), list[i] ]);
- }
- return out;
- },
- setter = function(list, func, selector) {
- return function() {
- return _setOperation(list, func, arguments, selector);
- };
- },
- getter = function(list, func) {
- return function() {
- return _getOperation(list, func, arguments);
- };
- };
-
- /*
- * 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. use '*' for all scopes.
- * 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.
- * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope).
- *
- */
- this.getConnections = function(options, flat) {
- if (!options) {
- options = {};
- } else if (options.constructor == String) {
- options = { "scope": options };
- }
- var prepareList = function(input) {
- var r = [];
- if (input) {
- if (typeof input == 'string') {
- if (input === "*") return input;
- 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) {
- if (list === "*") return true;
- return list.length > 0 ? _indexOf(list, value) != -1 : true;
- },
- results = (!flat && scopes.length > 1) ? {} : [],
- _addOne = function(scope, obj) {
- if (!flat && scopes.length > 1) {
- var ss = results[scope];
- if (ss == null) {
- ss = []; results[scope] = ss;
- }
- 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;
- };
-
- var _makeConnectionSelectHandler = function(list) {
- //var
- return {
- // setters
- setHover:setter(list, "setHover", _makeConnectionSelectHandler),
- removeAllOverlays:setter(list, "removeAllOverlays", _makeConnectionSelectHandler),
- setLabel:setter(list, "setLabel", _makeConnectionSelectHandler),
- addOverlay:setter(list, "addOverlay", _makeConnectionSelectHandler),
- removeOverlay:setter(list, "removeOverlay", _makeConnectionSelectHandler),
- removeOverlays:setter(list, "removeOverlays", _makeConnectionSelectHandler),
- showOverlay:setter(list, "showOverlay", _makeConnectionSelectHandler),
- hideOverlay:setter(list, "hideOverlay", _makeConnectionSelectHandler),
- showOverlays:setter(list, "showOverlays", _makeConnectionSelectHandler),
- hideOverlays:setter(list, "hideOverlays", _makeConnectionSelectHandler),
- setPaintStyle:setter(list, "setPaintStyle", _makeConnectionSelectHandler),
- setHoverPaintStyle:setter(list, "setHoverPaintStyle", _makeConnectionSelectHandler),
- setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler),
- setConnector:setter(list, "setConnector", _makeConnectionSelectHandler),
- setParameter:setter(list, "setParameter", _makeConnectionSelectHandler),
- setParameters:setter(list, "setParameters", _makeConnectionSelectHandler),
-
- detach:function() {
- for (var i = 0; i < list.length; i++)
- _currentInstance.detach(list[i]);
- },
-
- // getters
- getLabel:getter(list, "getLabel"),
- getOverlay:getter(list, "getOverlay"),
- isHover:getter(list, "isHover"),
- isDetachable:getter(list, "isDetachable"),
- getParameter:getter(list, "getParameter"),
- getParameters:getter(list, "getParameters"),
-
- // util
- length:list.length,
- each:function(f) {
- for (var i = 0; i < list.length; i++) {
- f(list[i]);
- }
- return _makeConnectionSelectHandler(list);
- },
- get:function(idx) {
- return list[idx];
- }
-
- };
- };
-
- this.select = function(params) {
- params = params || {};
- params.scope = params.scope || "*";
- var c = _currentInstance.getConnections(params, true);
- return _makeConnectionSelectHandler(c);
- };
-
- /*
- * 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; };
-
- this.isCanvasAvailable = function() { return canvasAvailable; };
- this.isSVGAvailable = function() { return svgAvailable; };
- this.isVMLAvailable = vmlAvailable;
-
- /*
- 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 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 (_isArray(specimen)) {
- if (_isArray(specimen[0]) || _isString(specimen[0])) {
- if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) {
- 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 (_isArray(types[i]))
- r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance));
- }
- return r;
- };
-
- /**
- * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor
- * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will
- * not need to provide this - i think).
- */
- this.makeDynamicAnchor = function(anchors, anchorSelector) {
- return new 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 = {},
- _targetMaxConnections = {},
- _setEndpointPaintStylesAndAnchor = function(ep, epIndex) {
- ep.paintStyle = ep.paintStyle ||
- _currentInstance.Defaults.EndpointStyles[epIndex] ||
- _currentInstance.Defaults.EndpointStyle ||
- jsPlumb.Defaults.EndpointStyles[epIndex] ||
- jsPlumb.Defaults.EndpointStyle;
- ep.hoverPaintStyle = ep.hoverPaintStyle ||
- _currentInstance.Defaults.EndpointHoverStyles[epIndex] ||
- _currentInstance.Defaults.EndpointHoverStyle ||
- jsPlumb.Defaults.EndpointHoverStyles[epIndex] ||
- jsPlumb.Defaults.EndpointHoverStyle;
-
- ep.anchor = ep.anchor ||
- _currentInstance.Defaults.Anchors[epIndex] ||
- _currentInstance.Defaults.Anchor ||
- jsPlumb.Defaults.Anchors[epIndex] ||
- jsPlumb.Defaults.Anchor;
-
- ep.endpoint = ep.endpoint ||
- _currentInstance.Defaults.Endpoints[epIndex] ||
- _currentInstance.Defaults.Endpoint ||
- jsPlumb.Defaults.Endpoints[epIndex] ||
- jsPlumb.Defaults.Endpoint;
- };
- this.makeTarget = function(el, params, referenceParams) {
-
- var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams);
- jsPlumb.extend(p, params);
- _setEndpointPaintStylesAndAnchor(p, 1);
- var jpcl = jsPlumb.CurrentLibrary,
- targetScope = p.scope || _currentInstance.Defaults.Scope,
- deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false),
- maxConnections = p.maxConnections || -1,
- _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,
- _targetMaxConnections[elid] = maxConnections,
- proxyComponent = new jsPlumbUIComponent(p);
-
- var dropOptions = jsPlumb.extend({}, p.dropOptions || {}),
- _drop = function() {
-
- var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments),
- targetCount = _currentInstance.select({target:elid}).length;
-
- if (_targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){
- console.log("target element " + elid + " is full.");
- return false;
- }
-
- _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);
- var _continue = proxyComponent.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.
- newEndpoint._makeTargetCreator = true;
-
- // if the anchor has a 'positionFinder' set, then delegate to that function to find
- // out where to locate the anchor.
- if (newEndpoint.anchor.positionFinder != null) {
- var dropPosition = jpcl.getUIPosition(arguments),
- 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
- });
-
- // delete the original target endpoint. but only want to do this if the endpoint was created
- // automatically and has no other connections.
- if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2)
- _currentInstance.deleteEndpoint(jpc.endpoints[1]);
-
- 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, originalEvent); // 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).
- *
- * Parameters:
- * el - string id or element selector for the element to make a source.
- * params - JS object containing parameters:
- * endpoint optional. specification of an endpoint to create when a connection is created.
- * parent optional. the element to add Endpoints to when a Connection is established. if you omit this,
- * Endpoints will be added to 'el'.
- * scope optional. scope for the connections dragged from this element.
- * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition.
- * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete
- * any Endpoints created by a connection from this source if
- * the connection is subsequently detached. this will not
- * remove Endpoints that have had more Connections attached
- * to them after they were created.
- *
- *
- */
- var _sourceEndpointDefinitions = {},
- _sourceEndpoints = {},
- _sourceEndpointsUnique = {};
-
- this.makeSource = function(el, params, referenceParams) {
- var p = jsPlumb.extend({}, referenceParams);
- jsPlumb.extend(p, params);
- _setEndpointPaintStylesAndAnchor(p, 0);
- var jpcl = jsPlumb.CurrentLibrary,
- _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),
- parent = p.parent,
- idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid;
-
- _sourceEndpointDefinitions[idToRegisterAgainst] = p;
- _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint;
-
- var stopEvent = jpcl.dragEvents["stop"],
- dragEvent = jpcl.dragEvents["drag"],
- dragOptions = jsPlumb.extend({ }, p.dragOptions || {}),
- existingDrag = dragOptions.drag,
- existingStop = dragOptions.stop,
- ep = null,
- endpointAddedButNoDragYet = false;
-
- // set scope if its not set in dragOptions but was passed in in params
- dragOptions["scope"] = dragOptions["scope"] || p.scope;
-
- dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() {
- if (existingDrag) existingDrag.apply(this, arguments);
- endpointAddedButNoDragYet = false;
- });
-
- dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() {
- if (existingStop) existingStop.apply(this, arguments);
-
- //_currentlyDown = false;
- _currentInstance.currentlyDragging = false;
-
- if (ep.connections.length == 0)
- _currentInstance.deleteEndpoint(ep);
- else {
-
- jpcl.unbind(ep.canvas, "mousedown");
-
- // reset the anchor to the anchor that was initially provided. the one we were using to drag
- // the connection was just a placeholder that was located at the place the user pressed the
- // mouse button to initiate the drag.
- var anchorDef = p.anchor || _currentInstance.Defaults.Anchor,
- oldAnchor = ep.anchor,
- oldConnection = ep.connections[0];
-
- ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance);
-
- if (p.parent) {
- var parent = jpcl.getElementObject(p.parent);
- if (parent) {
- var currentId = ep.elementId;
-
- ep.setElement(parent);
- ep.endpointWillMoveAfterConnection = false;
- _currentInstance.anchorManager.rehomeEndpoint(currentId, parent);
- oldConnection.previousConnection = null;
- // remove from connectionsByScope
- _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) {
- return c.id === oldConnection.id;
- });
- _currentInstance.anchorManager.connectionDetached({
- sourceId:oldConnection.sourceId,
- targetId:oldConnection.targetId,
- connection:oldConnection
- });
- _finaliseConnection(oldConnection);
- }
- }
-
- ep.repaint();
- _currentInstance.repaint(ep.elementId);
- _currentInstance.repaint(oldConnection.targetId);
-
- }
- });
- // when the user presses the mouse, add an Endpoint
- var mouseDownListener = function(e) {
- // make sure we have the latest offset for this div
- var myOffsetInfo = _updateOffset({elId:elid});
-
- var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width,
- y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height,
- parentX = x,
- parentY = y;
-
-
- // if there is a parent, the endpoint will actually be added to it now, rather than the div
- // that was the source. in that case, we have to adjust the anchor position so it refers to
- // the parent.
- if (p.parent) {
- var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent),
- pId = _getId(pEl);
- myOffsetInfo = _updateOffset({elId:pId});
- parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width,
- parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height;
- }
-
- // we need to override the anchor in here, and force 'isSource', but we don't want to mess with
- // the params passed in, because after a connection is established we're going to reset the endpoint
- // to have the anchor we were given.
- var tempEndpointParams = {};
- jsPlumb.extend(tempEndpointParams, p);
- tempEndpointParams.isSource = true;
- tempEndpointParams.anchor = [x,y,0,0];
- tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ];
- tempEndpointParams.dragOptions = dragOptions;
- // if a parent was given we need to turn that into a "container" argument. this is, by default,
- // the parent of the element we will move to, so parent of p.parent in this case. however, if
- // the user has specified a 'container' on the endpoint definition or on
- // the defaults, we should use that.
- if (p.parent) {
- var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container;
- if (potentialParent)
- tempEndpointParams.container = potentialParent;
- else
- tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent);
- }
-
- ep = _currentInstance.addEndpoint(elid, tempEndpointParams);
-
- endpointAddedButNoDragYet = true;
- // we set this to prevent connections from firing attach events before this function has had a chance
- // to move the endpoint.
- ep.endpointWillMoveAfterConnection = p.parent != null;
- ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null;
-
- var _delTempEndpoint = function() {
- // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools
- // it is fired even if dragging has occurred, in which case we would blow away a perfectly
- // legitimate endpoint, were it not for this check. the flag is set after adding an
- // endpoint and cleared in a drag listener we set in the dragOptions above.
- if(endpointAddedButNoDragYet) {
- _currentInstance.deleteEndpoint(ep);
- }
- };
-
- _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint);
- _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint);
-
- // and then trigger its mousedown event, which will kick off a drag, which will start dragging
- // a new connection from this endpoint.
- jpcl.trigger(ep.canvas, "mousedown", e);
- };
-
- // register this on jsPlumb so that it can be cleared by a reset.
- _currentInstance.registerListener(_el, "mousedown", mouseDownListener);
- };
-
- 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 connection sources.
- * @param els
- * @param params
- * @param referenceParams
- * @return
- */
- this.makeSources = function(els, params, referenceParams) {
- for ( var i = 0; i < els.length; i++) {
- _currentInstance.makeSource(els[i], params, referenceParams);
- }
- };
-
- /*
- Function: ready
- Helper method to bind a function to jsPlumb's ready event.
- */
- this.ready = function(fn) {
- _currentInstance.bind("ready", fn);
- },
-
- /*
- Function: repaint
- Repaints an element and its connections. This method gets new sizes for the elements before painting anything.
-
- Parameters:
- el - either the id of the element or a selector representing the element.
-
- Returns:
- void
-
- See Also:
-
- */
- this.repaint = function(el) {
- var _processElement = function(el) { _draw(_getElementObject(el)); };
- // support both lists...
- if (typeof el == 'object')
- for ( var i = 0; i < el.length; i++) _processElement(el[i]);
- else // ...and single strings.
- _processElement(el);
- };
-
- /*
- Function: repaintEverything
- Repaints all connections.
-
- Returns:
- void
-
- See Also:
-
- */
- this.repaintEverything = function() {
- for ( var elId in endpointsByElement) {
- _draw(_getElementObject(elId), null, null);
- }
- };
-
- /*
- Function: removeAllEndpoints
- Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes.
-
- Parameters:
- el - either an element id, or a selector for an element.
-
- Returns:
- void
-
- See Also:
-
- */
- this.removeAllEndpoints = function(el) {
- var elId = _getAttribute(el, "id"),
- ebe = endpointsByElement[elId];
- if (ebe) {
- for ( var i = 0; i < ebe.length; i++)
- _currentInstance.deleteEndpoint(ebe[i]);
- }
- endpointsByElement[elId] = [];
- };
-
- /*
- Removes every Endpoint in this instance of jsPlumb.
- @deprecated use deleteEveryEndpoint instead
- */
- this.removeEveryEndpoint = this.deleteEveryEndpoint;
-
- /*
- Removes the given Endpoint from the given element.
- @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant).
- */
- this.removeEndpoint = function(el, endpoint) {
- _currentInstance.deleteEndpoint(endpoint);
- };
-
- var _registeredListeners = {},
- _unbindRegisteredListeners = function() {
- for (var i in _registeredListeners) {
- for (var j = 0; j < _registeredListeners[i].length; j++) {
- var info = _registeredListeners[i][j];
- jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener);
- }
- }
- _registeredListeners = {};
- };
-
- // internal register listener method. gives us a hook to clean things up
- // with if the user calls jsPlumb.reset.
- this.registerListener = function(el, type, listener) {
- jsPlumb.CurrentLibrary.bind(el, type, listener);
- _addToList(_registeredListeners, type, {el:el, event:type, listener:listener});
- };
-
- /*
- Function:reset
- Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this.
- */
- this.reset = function() {
- _currentInstance.deleteEveryEndpoint();
- _currentInstance.clearListeners();
- _targetEndpointDefinitions = {};
- _targetEndpoints = {};
- _targetEndpointsUnique = {};
- _targetMaxConnections = {};
- _sourceEndpointDefinitions = {};
- _sourceEndpoints = {};
- _sourceEndpointsUnique = {};
- _unbindRegisteredListeners();
- _currentInstance.anchorManager.reset();
- _currentInstance.dragManager.reset();
- };
-
- /*
- Function: setAutomaticRepaint
- Sets/unsets automatic repaint on window resize.
-
- Parameters:
- value - whether or not to automatically repaint when the window is resized.
-
- Returns: void
- *
- this.setAutomaticRepaint = function(value) {
- automaticRepaint = value;
- };*/
-
- /*
- * Function: setDefaultScope
- * Sets 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. 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 just instructs jsPlumb
- * to use a different key for the default scope.
- *
- * Parameters:
- * scope - scope to set as default.
- */
- this.setDefaultScope = function(scope) {
- DEFAULT_SCOPE = scope;
- };
-
- /*
- * Function: setDraggable
- * Sets whether or not a given element is
- * draggable, regardless of what any jsPlumb command may request.
- *
- * Parameters:
- * el - either the id for the element, or a selector representing the element.
- *
- * Returns:
- * void
- */
- this.setDraggable = _setDraggable;
-
- /*
- * Function: setId
- * Changes the id of some element, adjusting all connections and endpoints
- *
- * Parameters:
- * el - a selector, a DOM element, or a string.
- * newId - string.
- */
- this.setId = function(el, newId, doNotSetAttribute) {
-
- var id = el.constructor == String ? el : _currentInstance.getId(el),
- sConns = _currentInstance.getConnections({source:id, scope:'*'}, true),
- tConns = _currentInstance.getConnections({target:id, scope:'*'}, true);
-
- newId = "" + newId;
-
- if (!doNotSetAttribute) {
- el = jsPlumb.CurrentLibrary.getElementObject(id);
- jsPlumb.CurrentLibrary.setAttribute(el, "id", newId);
- }
-
- el = jsPlumb.CurrentLibrary.getElementObject(newId);
-
-
- endpointsByElement[newId] = endpointsByElement[id] || [];
- for (var i = 0; i < endpointsByElement[newId].length; i++) {
- endpointsByElement[newId][i].elementId = newId;
- endpointsByElement[newId][i].element = el;
- endpointsByElement[newId][i].anchor.elementId = newId;
- }
- delete endpointsByElement[id];
-
- _currentInstance.anchorManager.changeId(id, newId);
-
- var _conns = function(list, epIdx, type) {
- for (var i = 0; i < list.length; i++) {
- list[i].endpoints[epIdx].elementId = newId;
- list[i].endpoints[epIdx].element = el;
- list[i][type + "Id"] = newId;
- list[i][type] = el;
- }
- };
- _conns(sConns, 0, "source");
- _conns(tConns, 1, "target");
- };
-
- /*
- * Function: setIdChanged
- * Notify jsPlumb that the element with oldId has had its id changed to newId.
- */
- this.setIdChanged = function(oldId, newId) {
- _currentInstance.setId(oldId, newId, true);
- };
-
- this.setDebugLog = function(debugLog) {
- log = debugLog;
- };
-
- /*
- * Function: setRepaintFunction
- * Sets the function to fire when the window size has changed and a repaint was fired.
- *
- * Parameters:
- * f - Function to execute.
- *
- * Returns: void
- */
- this.setRepaintFunction = function(f) {
- repaintFunction = f;
- };
-
- /*
- * Function: setSuspendDrawing
- * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register;
- * it will save you a lot of time.
- */
- this.setSuspendDrawing = _setSuspendDrawing;
-
- /*
- * Constant for use with the setRenderMode method
- */
- this.CANVAS = "canvas";
-
- /*
- * Constant for use with the setRenderMode method
- */
- this.SVG = "svg";
-
- this.VML = "vml";
-
- /*
- * Function: setRenderMode
- * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that
- * what you asked for is not supported (and that VML is). If you asked for VML but the browser does
- * not support it, jsPlumb uses SVG.
- *
- * Returns:
- * the render mode that jsPlumb set, which of course may be different from that requested.
- */
- this.setRenderMode = function(mode) {
- if (mode)
- mode = mode.toLowerCase();
- else
- return;
- if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML");
- // now test we actually have the capability to do this.
- if (mode === jsPlumb.CANVAS && canvasAvailable)
- renderMode = jsPlumb.CANVAS;
- else if (mode === jsPlumb.SVG && svgAvailable)
- renderMode = jsPlumb.SVG;
- else if (vmlAvailable())
- renderMode = jsPlumb.VML;
-
- return renderMode;
- };
-
- this.getRenderMode = function() { return renderMode; };
-
- /*
- * Function: show
- * Sets an element's connections to be visible.
- *
- * Parameters:
- * el - either the id of the element, or a selector for the element.
- * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on
- * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible.
- *
- * Returns:
- * void
- */
- this.show = function(el, changeEndpoints) {
- _setVisible(el, "block", changeEndpoints);
- };
-
- /*
- * Function: sizeCanvas
- * Helper to size a canvas. You would typically use
- * this when writing your own Connector or Endpoint implementation.
- *
- * Parameters:
- * x - [int] x position for the Canvas origin
- * y - [int] y position for the Canvas origin
- * w - [int] width of the canvas
- * h - [int] height of the canvas
- *
- * Returns:
- * void
- */
- this.sizeCanvas = function(canvas, x, y, w, h) {
- if (canvas) {
- canvas.style.height = h + "px";
- canvas.height = h;
- canvas.style.width = w + "px";
- canvas.width = w;
- canvas.style.left = x + "px";
- canvas.style.top = y + "px";
- }
- };
-
- /**
- * gets some test hooks. nothing writable.
- */
- this.getTestHarness = function() {
- return {
- endpointsByElement : endpointsByElement,
- endpointCount : function(elId) {
- var e = endpointsByElement[elId];
- return e ? e.length : 0;
- },
- connectionCount : function(scope) {
- scope = scope || DEFAULT_SCOPE;
- var c = connectionsByScope[scope];
- return c ? c.length : 0;
- },
- //findIndex : _findIndex,
- getId : _getId,
- makeAnchor:self.makeAnchor,
- makeDynamicAnchor:self.makeDynamicAnchor
- };
- };
-
- /**
- * Toggles visibility of an element's connections. kept for backwards
- * compatibility
- */
- this.toggle = _toggleVisible;
-
- /*
- * Function: toggleVisible
- * Toggles visibility of an element's Connections.
- *
- * Parameters:
- * el - either the element's id, or a selector representing the element.
- * changeEndpoints - whether or not to also toggle the endpoints on the element.
- *
- * Returns:
- * void, but should be updated to return the current state
- */
- // TODO: update this method to return the current state.
- this.toggleVisible = _toggleVisible;
-
- /*
- * Function: toggleDraggable
- * Toggles draggability (sic?) of an element's Connections.
- *
- * Parameters:
- * el - either the element's id, or a selector representing the element.
- *
- * Returns:
- * The current draggable state.
- */
- this.toggleDraggable = _toggleDraggable;
-
- /*
- * Function: unload
- * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element.
- *
- * Returns:
- * void
- */
- this.unload = function() {
- // this used to do something, but it turns out that what it did was nothing.
- // now it exists only for backwards compatibility.
- };
-
- /*
- * Helper method to wrap an existing function with one of
- * your own. This is used by the various implementations to wrap event
- * callbacks for drag/drop etc; it allows jsPlumb to be transparent in
- * its handling of these things. If a user supplies their own event
- * callback, for anything, it will always be called.
- */
- this.wrap = _wrap;
- this.addListener = this.bind;
-
- var adjustForParentOffsetAndScroll = function(xy, el) {
-
- var offsetParent = null, result = xy;
- if (el.tagName.toLowerCase() === "svg" && el.parentNode) {
- offsetParent = el.parentNode;
- }
- else if (el.offsetParent) {
- offsetParent = el.offsetParent;
- }
- if (offsetParent != null) {
- var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent),
- so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop};
-
-
- // i thought it might be cool to do this:
- // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft;
- // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop;
- // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying
- // library.
-
- result[0] = xy[0] - po.left + so.left;
- result[1] = xy[1] - po.top + so.top;
- }
-
- return result;
-
- };
-
- /**
- * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user
- * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"),
- * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the
- * creation of Anchors without user intervention.
- */
- var Anchor = function(params) {
- var self = this;
- this.x = params.x || 0;
- this.y = params.y || 0;
- this.elementId = params.elementId;
- var orientation = params.orientation || [ 0, 0 ];
- var lastTimestamp = null, lastReturnValue = null;
- this.offsets = params.offsets || [ 0, 0 ];
- self.timestamp = null;
- this.compute = function(params) {
- var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp;
-
- if (timestamp && timestamp === self.timestamp)
- return lastReturnValue;
-
- lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ];
-
- // adjust loc if there is an offsetParent
- lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas);
-
- self.timestamp = timestamp;
- return lastReturnValue;
- };
-
- this.getOrientation = function(_endpoint) { return orientation; };
-
- this.equals = function(anchor) {
- if (!anchor) return false;
- var ao = anchor.getOrientation();
- var o = this.getOrientation();
- return this.x == anchor.x && this.y == anchor.y
- && this.offsets[0] == anchor.offsets[0]
- && this.offsets[1] == anchor.offsets[1]
- && o[0] == ao[0] && o[1] == ao[1];
- };
-
- this.getCurrentLocation = function() { return lastReturnValue; };
- };
-
- /**
- * An Anchor that floats. its orientation is computed dynamically from
- * its position relative to the anchor it is floating relative to. It is used when creating
- * a connection through drag and drop.
- *
- * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly.
- */
- var FloatingAnchor = function(params) {
-
- // this is the anchor that this floating anchor is referenced to for
- // purposes of calculating the orientation.
- var ref = params.reference,
- // the canvas this refers to.
- refCanvas = params.referenceCanvas,
- size = _getSize(_getElementObject(refCanvas)),
-
- // these are used to store the current relative position of our
- // anchor wrt the reference anchor. they only indicate
- // direction, so have a value of 1 or -1 (or, very rarely, 0). these
- // values are written by the compute method, and read
- // by the getOrientation method.
- xDir = 0, yDir = 0,
- // temporary member used to store an orientation when the floating
- // anchor is hovering over another anchor.
- orientation = null,
- _lastResult = null;
-
- // set these to 0 each; they are used by certain types of connectors in the loopback case,
- // when the connector is trying to clear the element it is on. but for floating anchor it's not
- // very important.
- this.x = 0; this.y = 0;
-
- this.isFloating = true;
-
- this.compute = function(params) {
- var xy = params.xy, element = params.element,
- result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy.
-
- // adjust loc if there is an offsetParent
- result = adjustForParentOffsetAndScroll(result, element.canvas);
-
- _lastResult = result;
- return result;
- };
-
- this.getOrientation = function(_endpoint) {
- if (orientation) return orientation;
- else {
- var o = ref.getOrientation(_endpoint);
- // here we take into account the orientation of the other
- // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come
- // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense.
- return [ Math.abs(o[0]) * xDir * -1,
- Math.abs(o[1]) * yDir * -1 ];
- }
- };
-
- /**
- * notification the endpoint associated with this anchor is hovering
- * over another anchor; we want to assume that anchor's orientation
- * for the duration of the hover.
- */
- this.over = function(anchor) {
- orientation = anchor.getOrientation();
- };
-
- /**
- * notification the endpoint associated with this anchor is no
- * longer hovering over another anchor; we should resume calculating
- * orientation as we normally do.
- */
- this.out = function() { orientation = null; };
-
- this.getCurrentLocation = function() { return _lastResult; };
- };
-
- /*
- * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles
- * through at compute time to find the one that is located closest to
- * the center of the target element, and returns that Anchor's compute
- * method result. this causes endpoints to follow each other with
- * respect to the orientation of their target elements, which is a useful
- * feature for some applications.
- *
- */
- var DynamicAnchor = function(anchors, anchorSelector, elementId) {
- this.isSelective = true;
- this.isDynamic = true;
- var _anchors = [], self = this,
- _convert = function(anchor) {
- return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance);
- };
- for (var i = 0; i < anchors.length; i++)
- _anchors[i] = _convert(anchors[i]);
- this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); };
- this.getAnchors = function() { return _anchors; };
- this.locked = false;
- var _curAnchor = _anchors.length > 0 ? _anchors[0] : null,
- _curIndex = _anchors.length > 0 ? 0 : -1,
- self = this,
-
- // helper method to calculate the distance between the centers of the two elements.
- _distance = function(anchor, cx, cy, xy, wh) {
- var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]);
- return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2));
- },
-
- // default method uses distance between element centers. you can provide your own method in the dynamic anchor
- // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays:
- // xy - xy loc of the anchor's element
- // wh - anchor's element's dimensions
- // txy - xy loc of the element of the other anchor in the connection
- // twh - dimensions of the element of the other anchor in the connection.
- // anchors - the list of selectable anchors
- _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) {
- var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2);
- var minIdx = -1, minDist = Infinity;
- for ( var i = 0; i < anchors.length; i++) {
- var d = _distance(anchors[i], cx, cy, xy, wh);
- if (d < minDist) {
- minIdx = i + 0;
- minDist = d;
- }
- }
- return anchors[minIdx];
- };
-
- this.compute = function(params) {
- var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh;
- // if anchor is locked or an opposite element was not given, we
- // maintain our state. anchor will be locked
- // if it is the source of a drag and drop.
- if (self.locked || txy == null || twh == null)
- return _curAnchor.compute(params);
- else
- params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute.
-
- _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors);
- self.x = _curAnchor.x;
- self.y = _curAnchor.y;
-
- return _curAnchor.compute(params);
- };
-
- this.getCurrentLocation = function() {
- return _curAnchor != null ? _curAnchor.getCurrentLocation() : null;
- };
-
- this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; };
- this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); };
- this.out = function() { if (_curAnchor != null) _curAnchor.out(); };
- };
-
- /*
- manages anchors for all elements.
- */
- // "continuous" anchors: anchors that pick their location based on how many connections the given element has.
- // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has
- // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that
- // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else
- // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to
- // be called as few times as possible.
- var continuousAnchors = {},
- continuousAnchorLocations = {},
- continuousAnchorOrientations = {},
- Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" },
-
- // TODO this functions uses a crude method of determining orientation between two elements.
- // 'diagonal' should be chosen when the angle of the line between the two centers is around
- // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees.
- calculateOrientation = function(sourceId, targetId, sd, td) {
-
- if (sourceId === targetId) return {
- orientation:Orientation.IDENTITY,
- a:["top", "top"]
- };
-
- var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)),
- theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)),
- h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) ||
- (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)),
- v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) ||
- (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom));
-
- if (! (h || v)) {
- var a = null, rls = false, rrs = false, sortValue = null;
- if (td.left > sd.left && td.top > sd.top)
- a = ["right", "top"];
- else if (td.left > sd.left && sd.top > td.top)
- a = [ "top", "left"];
- else if (td.left < sd.left && td.top < sd.top)
- a = [ "top", "right"];
- else if (td.left < sd.left && td.top > sd.top)
- a = ["left", "top" ];
-
- return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 };
- }
- else if (h) return {
- orientation:Orientation.HORIZONTAL,
- a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"],
- theta:theta, theta2:theta2
- }
- else return {
- orientation:Orientation.VERTICAL,
- a:sd.left < td.left ? ["right", "left"] : ["left", "right"],
- theta:theta, theta2:theta2
- }
- },
- placeAnchorsOnLine = function(desc, elementDimensions, elementPosition,
- connections, horizontal, otherMultiplier, reverse) {
- var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1);
-
- for (var i = 0; i < connections.length; i++) {
- var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0];
- if (reverse)
- val = elementDimensions[horizontal ? 0 : 1] - val;
-
- var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0],
- dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1];
-
- a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]);
- }
-
- return a;
- },
- standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 },
- currySort = function(reverseAngles) {
- return function(a,b) {
- var r = true;
- if (reverseAngles) {
- if (a[0][0] < b[0][0])
- r = true;
- else
- r = a[0][1] > b[0][1];
- }
- else {
- if (a[0][0] > b[0][0])
- r= true;
- else
- r =a[0][1] > b[0][1];
- }
- return r === false ? -1 : 1;
- };
- },
- leftSort = function(a,b) {
- // first get adjusted values
- var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0],
- p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0];
- if (p1 > p2) return 1;
- else return a[0][1] > b[0][1] ? 1 : -1;
- },
- edgeSortFunctions = {
- "top":standardEdgeSort,
- "right":currySort(true),
- "bottom":currySort(true),
- "left":leftSort
- },
- _sortHelper = function(_array, _fn) {
- return _array.sort(_fn);
- },
- placeAnchors = function(elementId, _anchorLists) {
- var sS = sizes[elementId], sO = offsets[elementId],
- placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) {
- if (unsortedConnections.length > 0) {
- var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen
- reverse = desc === "right" || desc === "top",
- anchors = placeAnchorsOnLine(desc, elementDimensions,
- elementPosition, sc,
- isHorizontal, otherMultiplier, reverse );
-
- // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it.
- var _setAnchorLocation = function(endpoint, anchorPos) {
- var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas);
- continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ];
- continuousAnchorOrientations[endpoint.id] = orientation;
- };
-
- for (var i = 0; i < anchors.length; i++) {
- var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId;
- if (weAreSource)
- _setAnchorLocation(c.endpoints[0], anchors[i]);
- else if (weAreTarget)
- _setAnchorLocation(c.endpoints[1], anchors[i]);
- }
- }
- };
-
- placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]);
- placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]);
- placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]);
- placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]);
- },
- AnchorManager = function() {
- var _amEndpoints = {},
- connectionsByElementId = {},
- self = this,
- anchorLists = {};
-
- this.reset = function() {
- _amEndpoints = {};
- connectionsByElementId = {};
- anchorLists = {};
- };
- this.newConnection = function(conn) {
- var sourceId = conn.sourceId, targetId = conn.targetId,
- ep = conn.endpoints,
- doRegisterTarget = true,
- registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
-
- if ((sourceId == targetId) && otherAnchor.isContinuous){
- // remove the target endpoint's canvas. we dont need it.
- jsPlumb.CurrentLibrary.removeElement(ep[1].canvas);
- doRegisterTarget = false;
- }
- _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]);
- };
-
- registerConnection(0, ep[0], ep[0].anchor, targetId, conn);
- if (doRegisterTarget)
- registerConnection(1, ep[1], ep[1].anchor, sourceId, conn);
- };
- this.connectionDetached = function(connInfo) {
- var connection = connInfo.connection || connInfo;
- var sourceId = connection.sourceId,
- targetId = connection.targetId,
- ep = connection.endpoints,
- removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
- if (otherAnchor.constructor == FloatingAnchor) {
- // no-op
- }
- else {
- _removeWithFunction(connectionsByElementId[elId], function(_c) {
- return _c[0].id == c.id;
- });
- }
- };
-
- removeConnection(1, ep[1], ep[1].anchor, sourceId, connection);
- removeConnection(0, ep[0], ep[0].anchor, targetId, connection);
-
- // remove from anchorLists
- var sEl = connection.sourceId,
- tEl = connection.targetId,
- sE = connection.endpoints[0].id,
- tE = connection.endpoints[1].id,
- _remove = function(list, eId) {
- if (list) { // transient anchors dont get entries in this list.
- var f = function(e) { return e[4] == eId; };
- _removeWithFunction(list["top"], f);
- _removeWithFunction(list["left"], f);
- _removeWithFunction(list["bottom"], f);
- _removeWithFunction(list["right"], f);
- }
- };
-
- _remove(anchorLists[sEl], sE);
- _remove(anchorLists[tEl], tE);
- self.redraw(sEl);
- self.redraw(tEl);
- };
- this.add = function(endpoint, elementId) {
- _addToList(_amEndpoints, elementId, endpoint);
- };
- this.changeId = function(oldId, newId) {
- connectionsByElementId[newId] = connectionsByElementId[oldId];
- _amEndpoints[newId] = _amEndpoints[oldId];
- delete connectionsByElementId[oldId];
- delete _amEndpoints[oldId];
- };
- this.getConnectionsFor = function(elementId) {
- return connectionsByElementId[elementId] || [];
- };
- this.getEndpointsFor = function(elementId) {
- return _amEndpoints[elementId] || [];
- };
- this.deleteEndpoint = function(endpoint) {
- _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) {
- return e.id == endpoint.id;
- });
- };
- this.clearFor = function(elementId) {
- delete _amEndpoints[elementId];
- _amEndpoints[elementId] = [];
- };
- // updates the given anchor list by either updating an existing anchor's info, or adding it. this function
- // also removes the anchor from its previous list, if the edge it is on has changed.
- // all connections found along the way (those that are connected to one of the faces this function
- // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint
- // them wthout having to calculate anything else about them.
- var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) {
- // first try to find the exact match, but keep track of the first index of a matching element id along the way.s
- var exactIdx = -1,
- firstMatchingElIdx = -1,
- endpoint = conn.endpoints[idx],
- endpointId = endpoint.id,
- oIdx = [1,0][idx],
- values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ],
- listToAddTo = lists[edgeId],
- listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null;
-
- if (listToRemoveFrom) {
- var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId });
- if (rIdx != -1) {
- listToRemoveFrom.splice(rIdx, 1);
- // get all connections from this list
- for (var i = 0; i < listToRemoveFrom.length; i++) {
- _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id });
- _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id });
- }
- }
- }
-
- for (var i = 0; i < listToAddTo.length; i++) {
- if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1)
- firstMatchingElIdx = i;
- _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id });
- _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id });
- }
- if (exactIdx != -1) {
- listToAddTo[exactIdx] = values;
- }
- else {
- var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly.
- listToAddTo.splice(insertIdx, 0, values);
- }
-
- // store this for next time.
- endpoint._continuousAnchorEdge = edgeId;
- };
- this.redraw = function(elementId, ui, timestamp, offsetToUI) {
- // get all the endpoints for this element
- var ep = _amEndpoints[elementId] || [],
- endpointConnections = connectionsByElementId[elementId] || [],
- connectionsToPaint = [],
- endpointsToPaint = [],
- anchorsToUpdate = [];
-
- timestamp = timestamp || _timestamp();
- // offsetToUI are values that would have been calculated in the dragManager when registering
- // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been
- // registered as draggable.
- offsetToUI = offsetToUI || {left:0, top:0};
- if (ui) {
- ui = {
- left:ui.left + offsetToUI.left,
- top:ui.top + offsetToUI.top
- }
- }
-
- _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp });
- // valid for one paint cycle.
- var myOffset = offsets[elementId],
- myWH = sizes[elementId],
- orientationCache = {};
-
- // actually, first we should compute the orientation of this element to all other elements to which
- // this element is connected with a continuous anchor (whether both ends of the connection have
- // a continuous anchor or just one)
- //for (var i = 0; i < continuousAnchorConnections.length; i++) {
- for (var i = 0; i < endpointConnections.length; i++) {
- var conn = endpointConnections[i][0],
- sourceId = conn.sourceId,
- targetId = conn.targetId,
- sourceContinuous = conn.endpoints[0].anchor.isContinuous,
- targetContinuous = conn.endpoints[1].anchor.isContinuous;
-
- if (sourceContinuous || targetContinuous) {
- var oKey = sourceId + "_" + targetId,
- oKey2 = targetId + "_" + sourceId,
- o = orientationCache[oKey],
- oIdx = conn.sourceId == elementId ? 1 : 0;
-
- if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] };
- if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] };
-
- if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp });
- if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp });
-
- var td = _getCachedData(targetId),
- sd = _getCachedData(sourceId);
-
- if (targetId == sourceId && (sourceContinuous || targetContinuous)) {
- // here we may want to improve this by somehow determining the face we'd like
- // to put the connector on. ideally, when drawing, the face should be calculated
- // by determining which face is closest to the point at which the mouse button
- // was released. for now, we're putting it on the top face.
- _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint)
- }
- else {
- if (!o) {
- o = calculateOrientation(sourceId, targetId, sd.o, td.o);
- orientationCache[oKey] = o;
- // this would be a performance enhancement, but the computed angles need to be clamped to
- //the (-PI/2 -> PI/2) range in order for the sorting to work properly.
- /* orientationCache[oKey2] = {
- orientation:o.orientation,
- a:[o.a[1], o.a[0]],
- theta:o.theta + Math.PI,
- theta2:o.theta2 + Math.PI
- };*/
- }
- if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint);
- if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint);
- }
-
- if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; });
- if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; });
- _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; });
- if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1))
- _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; });
- }
- }
-
- // now place all the continuous anchors we need to;
- for (var i = 0; i < anchorsToUpdate.length; i++) {
- placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]);
- }
-
- // now that continuous anchors have been placed, paint all the endpoints for this element
- // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next
- // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way.
- for (var i = 0; i < ep.length; i++) {
- ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH });
- }
- // ... and any other endpoints we came across as a result of the continuous anchors.
- for (var i = 0; i < endpointsToPaint.length; i++) {
- endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH });
- }
-
- // paint all the standard and "dynamic connections", which are connections whose other anchor is
- // static and therefore does need to be recomputed; we make sure that happens only one time.
-
- // TODO we could have compiled a list of these in the first pass through connections; might save some time.
- for (var i = 0; i < endpointConnections.length; i++) {
- var otherEndpoint = endpointConnections[i][1];
- if (otherEndpoint.anchor.constructor == DynamicAnchor) {
- otherEndpoint.paint({ elementWithPrecedence:elementId });
- _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].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] !== endpointConnections[i][0])
- _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; });
- }
- } else if (otherEndpoint.anchor.constructor == Anchor) {
- _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; });
- }
- }
- // paint current floating connection for this element, if there is one.
- var fc = floatingConnections[elementId];
- if (fc)
- fc.paint({timestamp:timestamp, recalc:false, elId:elementId});
-
- // paint all the connections
- for (var i = 0; i < connectionsToPaint.length; i++) {
- connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false});
- }
- };
- this.rehomeEndpoint = function(currentId, element) {
- var eps = _amEndpoints[currentId] || [], //,
- elementId = _currentInstance.getId(element);
- for (var i = 0; i < eps.length; i++) {
- self.add(eps[i], elementId);
- }
- eps.splice(0, eps.length);
- };
- };
- _currentInstance.anchorManager = new AnchorManager();
- _currentInstance.continuousAnchorFactory = {
- get:function(params) {
- var existing = continuousAnchors[params.elementId];
- if (!existing) {
- existing = {
- type:"Continuous",
- compute : function(params) {
- return continuousAnchorLocations[params.element.id] || [0,0];
- },
- getCurrentLocation : function(endpoint) {
- return continuousAnchorLocations[endpoint.id] || [0,0];
- },
- getOrientation : function(endpoint) {
- return continuousAnchorOrientations[endpoint.id] || [0,0];
- },
- isDynamic : true,
- isContinuous : true
- };
- continuousAnchors[params.elementId] = existing;
- }
- return existing;
- }
- };
-
- /**
- Manages dragging for some instance of jsPlumb.
-
- */
- var DragManager = function() {
-
- var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {};
-
- /**
- register some element as draggable. right now the drag init stuff is done elsewhere, and it is
- possible that will continue to be the case.
- */
- this.register = function(el) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- var id = _currentInstance.getId(el),
- domEl = jsPlumb.CurrentLibrary.getDOMElement(el);
- if (!_draggables[id]) {
- _draggables[id] = el;
- _dlist.push(el);
- _delements[id] = {};
- }
-
- // look for child elements that have endpoints and register them against this draggable.
- var _oneLevel = function(p) {
- var pEl = jsPlumb.CurrentLibrary.getElementObject(p),
- pOff = jsPlumb.CurrentLibrary.getOffset(pEl);
-
- for (var i = 0; i < p.childNodes.length; i++) {
- if (p.childNodes[i].nodeType != 3) {
- var cEl = jsPlumb.CurrentLibrary.getElementObject(p.childNodes[i]),
- cid = _currentInstance.getId(cEl, null, true);
- if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) {
- var cOff = jsPlumb.CurrentLibrary.getOffset(cEl);
- _delements[id][cid] = {
- id:cid,
- offset:{
- left:cOff.left - pOff.left,
- top:cOff.top - pOff.top
- }
- };
- }
- }
- }
- };
-
- _oneLevel(domEl);
- };
-
- /**
- notification that an endpoint was added to the given el. we go up from that el's parent
- node, looking for a parent that has been registered as a draggable. if we find one, we add this
- el to that parent's list of elements to update on drag (if it is not there already)
- */
- this.endpointAdded = function(el) {
- var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el),
- p = c.parentNode, done = p == b;
-
- _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1;
-
- while (p != b) {
- var pid = _currentInstance.getId(p);
- if (_draggables[pid]) {
- var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jsPlumb.CurrentLibrary.getOffset(pEl);
-
- if (_delements[pid][id] == null) {
- var cLoc = jsPlumb.CurrentLibrary.getOffset(el);
- _delements[pid][id] = {
- id:id,
- offset:{
- left:cLoc.left - pLoc.left,
- top:cLoc.top - pLoc.top
- }
- };
- }
- break;
- }
- p = p.parentNode;
- }
- };
-
- this.endpointDeleted = function(endpoint) {
- if (_elementsWithEndpoints[endpoint.elementId]) {
- _elementsWithEndpoints[endpoint.elementId]--;
- if (_elementsWithEndpoints[endpoint.elementId] <= 0) {
- for (var i in _delements) {
- delete _delements[i][endpoint.elementId];
- }
- }
- }
- };
-
- this.getElementsForDraggable = function(id) {
- return _delements[id];
- };
-
- this.reset = function() {
- _draggables = {};
- _dlist = [];
- _delements = {};
- _elementsWithEndpoints = {};
- };
-
- };
- _currentInstance.dragManager = new DragManager();
-
-
-
- /*
- * Class: Connection
- * The connecting line between two Endpoints.
- */
- /*
- * Function: Connection
- * Connection constructor.
- *
- * Parameters:
- * source - either an element id, a selector for an element, or an Endpoint.
- * target - either an element id, a selector for an element, or an Endpoint
- * scope - scope descriptor for this connection. optional.
- * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this.
- * endpoint - Optional. Endpoint definition to use for both ends of the connection.
- * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters.
- * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection.
- * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters.
- * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here.
- * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null).
- * overlays - Optional array of Overlay definitions to appear on this Connection.
- * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this.
- */
- var Connection = function(params) {
- var self = this, visible = true;
- self.idPrefix = "_jsplumb_c_";
- self.defaultLabelLocation = 0.5;
- self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"];
- this.parent = params.parent;
- overlayCapableJsPlumbUIComponent.apply(this, arguments);
- // ************** get the source and target and register the connection. *******************
-
- /**
- Function:isVisible
- Returns whether or not the Connection is currently visible.
- */
- this.isVisible = function() { return visible; };
- /**
- Function: setVisible
- Sets whether or not the Connection should be visible.
-
- Parameters:
- visible - boolean indicating desired visible state.
- */
- this.setVisible = function(v) {
- visible = v;
- self[v ? "showOverlays" : "hideOverlays"]();
- if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none";
- };
-
- /**
- Property: source
- The source element for this Connection.
- */
- this.source = _getElementObject(params.source);
- /**
- Property:target
- The target element for this Connection.
- */
- this.target = _getElementObject(params.target);
- // sourceEndpoint and targetEndpoint override source/target, if they are present. but
- // source is not overridden if the Endpoint has declared it is not the final target of a connection;
- // instead we use the source that the Endpoint declares will be the final source element.
- if (params.sourceEndpoint) {
- this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement();
- }
- if (params.targetEndpoint) this.target = params.targetEndpoint.getElement();
-
- // if a new connection is the result of moving some existing connection, params.previousConnection
- // will have that Connection in it. listeners for the jsPlumbConnection event can look for that
- // member and take action if they need to.
- self.previousConnection = params.previousConnection;
-
- var _cost = params.cost;
- self.getCost = function() { return _cost; };
- self.setCost = function(c) { _cost = c; };
-
- var _bidirectional = params.bidirectional === false ? false : true;
- self.isBidirectional = function() { return _bidirectional; };
-
- /*
- * Property: sourceId
- * Id of the source element in the connection.
- */
- this.sourceId = _getAttribute(this.source, "id");
- /*
- * Property: targetId
- * Id of the target element in the connection.
- */
- this.targetId = _getAttribute(this.target, "id");
-
- /**
- * implementation of abstract method in EventGenerator
- * @return list of attached elements. in our case, a list of Endpoints.
- */
- this.getAttachedElements = function() {
- return self.endpoints;
- };
-
- /*
- * Property: scope
- * Optional scope descriptor for the connection.
- */
- this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints.
- /*
- * Property: endpoints
- * Array of [source, target] Endpoint objects.
- */
- this.endpoints = [];
- this.endpointStyles = [];
- // wrapped the main function to return null if no input given. this lets us cascade defaults properly.
- var _makeAnchor = function(anchorParams, elementId) {
- if (anchorParams)
- return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance);
- },
- prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) {
- if (existing) {
- self.endpoints[index] = existing;
- existing.addConnection(self);
- } else {
- if (!params.endpoints) params.endpoints = [ null, null ];
- var ep = params.endpoints[index]
- || params.endpoint
- || _currentInstance.Defaults.Endpoints[index]
- || jsPlumb.Defaults.Endpoints[index]
- || _currentInstance.Defaults.Endpoint
- || jsPlumb.Defaults.Endpoint;
-
- if (!params.endpointStyles) params.endpointStyles = [ null, null ];
- if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ];
- var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
- // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified.
- if (es.fillStyle == null && connectorPaintStyle != null)
- es.fillStyle = connectorPaintStyle.strokeStyle;
-
- // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does:
- //*
- if (es.outlineColor == null && connectorPaintStyle != null)
- es.outlineColor = connectorPaintStyle.outlineColor;
- if (es.outlineWidth == null && connectorPaintStyle != null)
- es.outlineWidth = connectorPaintStyle.outlineWidth;
- //*/
-
- var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle;
- // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure?
- if (connectorHoverPaintStyle != null) {
- if (ehs == null) ehs = {};
- if (ehs.fillStyle == null) {
- ehs.fillStyle = connectorHoverPaintStyle.strokeStyle;
- }
- }
- var a = params.anchors ? params.anchors[index] :
- params.anchor ? params.anchor :
- _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) ||
- _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) ||
- _makeAnchor(_currentInstance.Defaults.Anchor, elementId) ||
- _makeAnchor(jsPlumb.Defaults.Anchor, elementId),
- u = params.uuids ? params.uuids[index] : null,
- e = _newEndpoint({
- paintStyle : es,
- hoverPaintStyle:ehs,
- endpoint : ep,
- connections : [ self ],
- uuid : u,
- anchor : a,
- source : element,
- scope : params.scope,
- container:params.container,
- reattach:params.reattach,
- detachable:params.detachable
- });
- self.endpoints[index] = e;
-
-
- if (params.drawEndpoints === false) e.setVisible(false, true, true);
-
- return e;
- }
- };
-
- var eS = prepareEndpoint(params.sourceEndpoint,
- 0,
- params,
- self.source,
- self.sourceId,
- params.paintStyle,
- params.hoverPaintStyle);
- if (eS) _addToList(endpointsByElement, this.sourceId, eS);
-
- // if there were no endpoints supplied and the source element is the target element, we will reuse the source
- // endpoint that was just created.
- var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint,
- eT = prepareEndpoint(existingTargetEndpoint,
- 1,
- params,
- self.target,
- self.targetId,
- params.paintStyle,
- params.hoverPaintStyle);
- if (eT) _addToList(endpointsByElement, this.targetId, eT);
- // if scope not set, set it to be the scope for the source endpoint.
- if (!this.scope) this.scope = this.endpoints[0].scope;
-
- // if delete endpoints on detach, keep a record of just exactly which endpoints they are.
- if (params.deleteEndpointsOnDetach)
- self.endpointsToDeleteOnDetach = [eS, eT];
-
- var _detachable = _currentInstance.Defaults.ConnectionsDetachable;
- if (params.detachable === false) _detachable = false;
- if(self.endpoints[0].connectionsDetachable === false) _detachable = false;
- if(self.endpoints[1].connectionsDetachable === false) _detachable = false;
-
- // inherit connectin cost if it was set on source endpoint
- if (_cost == null) _cost = self.endpoints[0].getConnectionCost();
- // inherit bidirectional flag if set no source endpoint
- if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional();
-
- /*
- Function: isDetachable
- Returns whether or not this connection can be detached from its target/source endpoint. by default this
- is false; use it in conjunction with the 'reattach' parameter.
- */
- this.isDetachable = function() {
- return _detachable === true;
- };
-
- /*
- Function: setDetachable
- Sets whether or not this connection is detachable.
- */
- this.setDetachable = function(detachable) {
- _detachable = detachable === true;
- };
-
- // merge all the parameters objects into the connection. parameters set
- // on the connection take precedence; then target endpoint params, then
- // finally source endpoint params.
- // TODO jsPlumb.extend could be made to take more than two args, and it would
- // apply the second through nth args in order.
- var _p = jsPlumb.extend({}, this.endpoints[0].getParameters());
- jsPlumb.extend(_p, this.endpoints[1].getParameters());
- jsPlumb.extend(_p, self.getParameters());
- self.setParameters(_p);
-
- // override setHover to pass it down to the underlying connector
- var _sh = self.setHover;
-
- self.setHover = function() {
- self.connector.setHover.apply(self.connector, arguments);
- _sh.apply(self, arguments);
- };
-
- var _internalHover = function(state) {
- if (_connectionBeingDragged == null) {
- self.setHover(state, false);
- }
- };
-
- /*
- * Function: setConnector
- * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same
- * thing that you would set as the 'connector' property on a jsPlumb.connect call.
- *
- * Parameters:
- * connector - Connector definition
- */
- this.setConnector = function(connector, doNotRepaint) {
- if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent);
- var connectorArgs = {
- _jsPlumb:self._jsPlumb,
- parent:params.parent,
- cssClass:params.cssClass,
- container:params.container,
- tooltip:self.tooltip
- };
- if (_isString(connector))
- this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand.
- else if (_isArray(connector))
- this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs));
- self.canvas = self.connector.canvas;
- // binds mouse listeners to the current connector.
- _bindListeners(self.connector, self, _internalHover);
- if (!doNotRepaint) self.repaint();
- };
- /*
- * Property: connector
- * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc)
- */
-
- self.setConnector(this.endpoints[0].connector ||
- this.endpoints[1].connector ||
- params.connector ||
- _currentInstance.Defaults.Connector ||
- jsPlumb.Defaults.Connector, true);
-
- this.setPaintStyle(this.endpoints[0].connectorStyle ||
- this.endpoints[1].connectorStyle ||
- params.paintStyle ||
- _currentInstance.Defaults.PaintStyle ||
- jsPlumb.Defaults.PaintStyle, true);
-
- this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle ||
- this.endpoints[1].connectorHoverStyle ||
- params.hoverPaintStyle ||
- _currentInstance.Defaults.HoverPaintStyle ||
- jsPlumb.Defaults.HoverPaintStyle, true);
-
- this.paintStyleInUse = this.paintStyle;
-
-
- this.moveParent = function(newParent) {
- var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas);
- jpcl.removeElement(self.connector.canvas, curParent);
- jpcl.appendElement(self.connector.canvas, newParent);
- if (self.connector.bgCanvas) {
- jpcl.removeElement(self.connector.bgCanvas, curParent);
- jpcl.appendElement(self.connector.bgCanvas, newParent);
- }
- // this only applies for DOMOverlays
- for (var i = 0; i < self.overlays.length; i++) {
- if (self.overlays[i].isAppendedAtTopLevel) {
- jpcl.removeElement(self.overlays[i].canvas, curParent);
- jpcl.appendElement(self.overlays[i].canvas, newParent);
- if (self.overlays[i].reattachListeners)
- self.overlays[i].reattachListeners(self.connector);
- }
- }
- if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners.
- self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this
- };
-
-// ***************************** PLACEHOLDERS FOR NATURAL DOCS *************************************************
- /*
- * Function: bind
- * Bind to an event on the Connection.
- *
- * Parameters:
- * event - the event to bind. Available events on a Connection are:
- * - *click* : notification that a Connection was clicked.
- * - *dblclick* : notification that a Connection was double clicked.
- * - *mouseenter* : notification that the mouse is over a Connection.
- * - *mouseexit* : notification that the mouse exited a Connection.
- *
- * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event.
- */
-
- /*
- * Function: setPaintStyle
- * Sets the Connection's paint style and then repaints the Connection.
- *
- * Parameters:
- * style - Style to use.
- */
-
- /*
- * Function: setHoverPaintStyle
- * Sets the paint style to use when the mouse is hovering over the Connection. 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 Connection will not be repainted. useful when setting things up initially.
- */
-
- /*
- * Function: setHover
- * Sets/unsets the hover state of this Connection.
- *
- * 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.
- */
-
-// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS *************************************************
-
- _updateOffset( { elId : this.sourceId });
- _updateOffset( { elId : this.targetId });
-
- // paint the endpoints
- var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId],
- otherOffset = offsets[this.targetId],
- otherWH = sizes[this.targetId],
- initialTimestamp = _timestamp(),
- anchorLoc = this.endpoints[0].anchor.compute( {
- xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0],
- elementId:this.endpoints[0].elementId,
- txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1],
- timestamp:initialTimestamp
- });
-
- this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp });
-
- anchorLoc = this.endpoints[1].anchor.compute( {
- xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1],
- elementId:this.endpoints[1].elementId,
- txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0],
- timestamp:initialTimestamp
- });
- this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp });
-
- /*
- * Paints the Connection. Not exposed for public usage.
- *
- * Parameters:
- * elId - Id of the element that is in motion.
- * ui - current library's event system ui object (present if we came from a drag to get here).
- * recalc - whether or not to recalculate all anchors etc before painting.
- * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again.
- */
- this.paint = function(params) {
- params = params || {};
- var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp,
- // if the moving object is not the source we must transpose the two references.
- swap = false,
- tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId,
- tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
-
- var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }),
- targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved.
-
- var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx],
- sAnchorP = sE.anchor.getCurrentLocation(sE),
- tAnchorP = tE.anchor.getCurrentLocation(tE);
-
- /* paint overlays*/
- var maxSize = 0;
- for ( var i = 0; i < self.overlays.length; i++) {
- var o = self.overlays[i];
- if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector));
- }
-
- var dim = this.connector.compute(sAnchorP, tAnchorP,
- this.endpoints[sIdx], this.endpoints[tIdx],
- this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor,
- self.paintStyleInUse.lineWidth, maxSize,
- sourceInfo,
- targetInfo);
-
- self.connector.paint(dim, self.paintStyleInUse);
-
- /* paint overlays*/
- for ( var i = 0; i < self.overlays.length; i++) {
- var o = self.overlays[i];
- if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim);
- }
- };
-
- /*
- * Function: repaint
- * Repaints the Connection.
- */
- this.repaint = function(params) {
- params = params || {};
- var recalc = !(params.recalc === false);
- this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp });
- };
-
- };
-
-// ENDPOINT HELPER FUNCTIONS
- var _makeConnectionDragHandler = function(placeholder) {
- var stopped = false;
- return {
-
- drag : function() {
- if (stopped) {
- stopped = false;
- return true;
- }
- var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments),
- el = placeholder.element;
- if (el) {
- jsPlumb.CurrentLibrary.setOffset(el, _ui);
- _draw(_getElementObject(el), _ui);
- }
- },
- stopDrag : function() {
- stopped = true;
- }
- };
- };
-
- var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) {
- var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas });
-
- //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not
- // adding the floating endpoint as a droppable. that makes more sense anyway!
-
- return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" });
- };
-
- /**
- * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns
- * both the element id and a selector for the element.
- */
- var _makeDraggablePlaceholder = function(placeholder, parent) {
- var n = document.createElement("div");
- n.style.position = "absolute";
- var placeholderDragElement = _getElementObject(n);
- _appendElement(n, parent);
- var id = _getId(placeholderDragElement);
- _updateOffset( { elId : id });
- // create and assign an id, and initialize the offset.
- placeholder.id = id;
- placeholder.element = placeholderDragElement;
- };
-
- /*
- * Class: Endpoint
- *
- * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1
- * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't
- * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you
- * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or
- * 'target' Endpoints for Connections.
- *
- *
- */
-
- /*
- * Function: Endpoint
- *
- * Endpoint constructor.
- *
- * Parameters:
- * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions.
- * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ].
- * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop).
- * paintStyle - endpoint style, a js object. may be null.
- * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null.
- * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required.
- * canvas - canvas element to use. may be, and most often is, null.
- * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this.
- * connections - optional list of Connections to configure the Endpoint with.
- * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false.
- * maxConnections - integer; defaults to 1. a value of -1 means no upper limit.
- * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null.
- * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null.
- * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null.
- * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ].
- * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint.
- * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false.
- * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null.
- * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted.
- */
- var Endpoint = function(params) {
- var self = this;
- self.idPrefix = "_jsplumb_e_";
- self.defaultLabelLocation = [ 0.5, 0.5 ];
- self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"];
- this.parent = params.parent;
- overlayCapableJsPlumbUIComponent.apply(this, arguments);
- params = params || {};
-
-// ***************************** PLACEHOLDERS FOR NATURAL DOCS *************************************************
- /*
- * Function: bind
- * Bind to an event on the Endpoint.
- *
- * Parameters:
- * event - the event to bind. Available events on an Endpoint are:
- * - *click* : notification that a Endpoint was clicked.
- * - *dblclick* : notification that a Endpoint was double clicked.
- * - *mouseenter* : notification that the mouse is over a Endpoint.
- * - *mouseexit* : notification that the mouse exited a Endpoint.
- *
- * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event.
- */
-
- /*
- * Function: setPaintStyle
- * Sets the Endpoint's paint style and then repaints the Endpoint.
- *
- * Parameters:
- * style - Style to use.
- */
-
- /*
- * Function: setHoverPaintStyle
- * Sets the paint style to use when the mouse is hovering over the Endpoint. 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 Endpoint will not be repainted. useful when setting things up initially.
- */
-
- /*
- * Function: setHover
- * Sets/unsets the hover state of this Endpoint.
- *
- * 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.
- */
-
-// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS *************************************************
-
- var visible = true, __enabled = !(params.enabled === false);
- /*
- Function: isVisible
- Returns whether or not the Endpoint is currently visible.
- */
- this.isVisible = function() { return visible; };
- /*
- Function: setVisible
- Sets whether or not the Endpoint is currently visible.
-
- Parameters:
- visible - whether or not the Endpoint should be visible.
- doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false.
- doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false.
- */
- this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) {
- visible = v;
- if (self.canvas) self.canvas.style.display = v ? "block" : "none";
- self[v ? "showOverlays" : "hideOverlays"]();
- if (!doNotChangeConnections) {
- for (var i = 0; i < self.connections.length; i++) {
- self.connections[i].setVisible(v);
- if (!doNotNotifyOtherEndpoint) {
- var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0;
- // only change the other endpoint if this is its only connection.
- if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true);
- }
- }
- }
- };
-
- /*
- Function: isEnabled
- Returns whether or not the Endpoint is enabled for drag/drop connections.
- */
- this.isEnabled = function() { return __enabled; };
-
- /*
- Function: setEnabled
- Sets whether or not the Endpoint is enabled for drag/drop connections.
- */
- this.setEnabled = function(e) { __enabled = e; };
-
- var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null;
- if (_uuid) endpointsByUUID[_uuid] = self;
- var _elementId = _getAttribute(_element, "id");
- this.elementId = _elementId;
- this.element = _element;
-
- var _connectionCost = params.connectionCost;
- this.getConnectionCost = function() { return _connectionCost; };
- this.setConnectionCost = function(c) {
- _connectionCost = c;
- };
-
- var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true;
- this.areConnectionsBidirectional = function() { return _connectionsBidirectional; };
- this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; };
-
- self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance);
-
- // ANCHOR MANAGER
- if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted.
- _currentInstance.anchorManager.add(self, _elementId);
-
- var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot",
- endpointArgs = {
- _jsPlumb:self._jsPlumb,
- parent:params.parent,
- container:params.container,
- tooltip:params.tooltip,
- connectorTooltip:params.connectorTooltip,
- endpoint:self
- };
- if (_isString(_endpoint))
- _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs);
- else if (_isArray(_endpoint)) {
- endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs);
- _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs);
- }
- else {
- _endpoint = _endpoint.clone();
- }
-
- // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned,
- // and the clone is left in its place while the original one goes off on a magical journey.
- // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by
- // the whole world.
- var argsForClone = jsPlumb.extend({}, endpointArgs);
- _endpoint.clone = function() {
- var o = new Object();
- _endpoint.constructor.apply(o, [argsForClone]);
- return o;
- };
-
- self.endpoint = _endpoint;
- self.type = self.endpoint.type;
- // override setHover to pass it down to the underlying endpoint
- var _sh = self.setHover;
- self.setHover = function() {
- self.endpoint.setHover.apply(self.endpoint, arguments);
- _sh.apply(self, arguments);
- };
- // endpoint delegates to first connection for hover, if there is one.
- var internalHover = function(state) {
- if (self.connections.length > 0)
- self.connections[0].setHover(state, false);
- else
- self.setHover(state);
- };
-
- // bind listeners from endpoint to self, with the internal hover function defined above.
- _bindListeners(self.endpoint, self, internalHover);
-
- this.setPaintStyle(params.paintStyle ||
- params.style ||
- _currentInstance.Defaults.EndpointStyle ||
- jsPlumb.Defaults.EndpointStyle, true);
- this.setHoverPaintStyle(params.hoverPaintStyle ||
- _currentInstance.Defaults.EndpointHoverStyle ||
- jsPlumb.Defaults.EndpointHoverStyle, true);
- this.paintStyleInUse = this.paintStyle;
- this.connectorStyle = params.connectorStyle;
- this.connectorHoverStyle = params.connectorHoverStyle;
- this.connectorOverlays = params.connectorOverlays;
- this.connector = params.connector;
- this.connectorTooltip = params.connectorTooltip;
- this.isSource = params.isSource || false;
- this.isTarget = params.isTarget || false;
-
- var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of.
-
- this.getAttachedElements = function() {
- return self.connections;
- };
-
- /*
- * Property: canvas
- * The Endpoint's Canvas.
- */
- this.canvas = this.endpoint.canvas;
- /*
- * Property: connections
- * List of Connections this Endpoint is attached to.
- */
- this.connections = params.connections || [];
- /*
- * Property: scope
- * Scope descriptor for this Endpoint.
- */
- this.scope = params.scope || DEFAULT_SCOPE;
- this.timestamp = null;
- self.isReattach = params.reattach || false;
- self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable;
- if (params.connectionsDetachable === false || params.detachable === false)
- self.connectionsDetachable = false;
- var dragAllowedWhenFull = params.dragAllowedWhenFull || true;
-
- this.computeAnchor = function(params) {
- return self.anchor.compute(params);
- };
- /*
- * Function: addConnection
- * Adds a Connection to this Endpoint.
- *
- * Parameters:
- * connection - the Connection to add.
- */
- this.addConnection = function(connection) {
- self.connections.push(connection);
- };
- /*
- * Function: detach
- * Detaches the given Connection from this Endpoint.
- *
- * Parameters:
- * connection - the Connection to detach.
- * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target.
- */
- this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) {
- var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}),
- actuallyDetached = false;
- fireEvent = (fireEvent !== false);
- if (idx >= 0) {
- // 1. does the connection have a before detach (note this also checks jsPlumb's bound
- // detach handlers; but then Endpoint's check will, too, hmm.)
- if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) {
- // get the target endpoint
- var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0];
- // it would be nice to check with both endpoints that it is ok to detach. but
- // for this we'll have to get a bit fancier: right now if you use the same beforeDetach
- // interceptor for two endpoints (which is kind of common, because it's part of the
- // endpoint definition), then it gets fired twice. so in fact we need to loop through
- // each beforeDetach and see if it returns false, at which point we exit. but if it
- // returns true, we have to check the next one. however we need to track which ones
- // have already been run, and not run them again.
- if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) {
-
- self.connections.splice(idx, 1);
-
- // this avoids a circular loop
- if (!ignoreTarget) {
-
- t.detach(connection, true, forceDetach);
- // check connection to see if we want to delete the endpoints associated with it.
- // we only detach those that have just this connection; this scenario is most
- // likely if we got to this bit of code because it is set by the methods that
- // create their own endpoints, like .connect or .makeTarget. the user is
- // not likely to have interacted with those endpoints.
- if (connection.endpointsToDeleteOnDetach){
- for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) {
- var cde = connection.endpointsToDeleteOnDetach[i];
- if (cde && cde.connections.length == 0)
- _currentInstance.deleteEndpoint(cde);
- }
- }
- }
- _removeElements(connection.connector.getDisplayElements(), connection.parent);
- _removeWithFunction(connectionsByScope[connection.scope], function(c) {
- return c.id == connection.id;
- });
- actuallyDetached = true;
- var doFireEvent = (!ignoreTarget && fireEvent)
- fireDetachEvent(connection, doFireEvent, originalEvent);
- }
- }
- }
- return actuallyDetached;
- };
-
- /*
- * Function: detachAll
- * Detaches all Connections this Endpoint has.
- *
- * Parameters:
- * fireEvent - whether or not to fire the detach event. defaults to false.
- */
- this.detachAll = function(fireEvent, originalEvent) {
- while (self.connections.length > 0) {
- self.detach(self.connections[0], false, true, fireEvent, originalEvent);
- }
- };
- /*
- * Function: detachFrom
- * Removes any connections from this Endpoint that are connected to the given target endpoint.
- *
- * Parameters:
- * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint.
- * fireEvent - whether or not to fire the detach event. defaults to false.
- */
- this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) {
- var c = [];
- for ( var i = 0; i < self.connections.length; i++) {
- if (self.connections[i].endpoints[1] == targetEndpoint
- || self.connections[i].endpoints[0] == targetEndpoint) {
- c.push(self.connections[i]);
- }
- }
- for ( var i = 0; i < c.length; i++) {
- if (self.detach(c[i], false, true, fireEvent, originalEvent))
- c[i].setHover(false, false);
- }
- };
- /*
- * Function: detachFromConnection
- * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging.
- *
- * Parameters:
- * connection - Connection to detach from.
- */
- this.detachFromConnection = function(connection) {
- var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id});
- if (idx >= 0) {
- self.connections.splice(idx, 1);
- }
- };
-
- /*
- * Function: getElement
- * Returns the DOM element this Endpoint is attached to.
- */
- this.getElement = function() {
- return _element;
- };
-
- /*
- * Function: setElement
- * Sets the DOM element this Endpoint is attached to.
- */
- this.setElement = function(el) {
-
- // TODO possibly have this object take charge of moving the UI components into the appropriate
- // parent. this is used only by makeSource right now, and that function takes care of
- // moving the UI bits and pieces. however it would s
- var parentId = _getId(el);
- // remove the endpoint from the list for the current endpoint's element
- _removeWithFunction(endpointsByElement[self.elementId], function(e) {
- return e.id == self.id;
- });
- _element = _getElementObject(el);
- _elementId = _getId(_element);
- self.elementId = _elementId;
- // need to get the new parent now
- var newParentElement = _getParentFromParams({source:parentId}),
- curParent = jpcl.getParent(self.canvas);
- jpcl.removeElement(self.canvas, curParent);
- jpcl.appendElement(self.canvas, newParentElement);
-
- // now move connection(s)...i would expect there to be only one but we will iterate.
- for (var i = 0; i < self.connections.length; i++) {
- self.connections[i].moveParent(newParentElement);
- self.connections[i].sourceId = _elementId;
- self.connections[i].source = _element;
- }
- _addToList(endpointsByElement, parentId, self);
- //_currentInstance.repaint(parentId);
-
- };
-
- /*
- * Function: getUuid
- * Returns the UUID for this Endpoint, if there is one. Otherwise returns null.
- */
- this.getUuid = function() {
- return _uuid;
- };
- /**
- * private but must be exposed.
- */
- this.makeInPlaceCopy = function() {
- var loc = self.anchor.getCurrentLocation(self),
- o = self.anchor.getOrientation(self),
- inPlaceAnchor = {
- compute:function() { return [ loc[0], loc[1] ]},
- getCurrentLocation : function() { return [ loc[0], loc[1] ]},
- getOrientation:function() { return o; }
- };
-
- return _newEndpoint( {
- anchor : inPlaceAnchor,
- source : _element,
- paintStyle : this.paintStyle,
- endpoint : _endpoint,
- _transient:true,
- scope:self.scope
- });
- };
- /*
- * Function: isConnectedTo
- * Returns whether or not this endpoint is connected to the given Endpoint.
- *
- * Parameters:
- * endpoint - Endpoint to test.
- */
- this.isConnectedTo = function(endpoint) {
- var found = false;
- if (endpoint) {
- for ( var i = 0; i < self.connections.length; i++) {
- if (self.connections[i].endpoints[1] == endpoint) {
- found = true;
- break;
- }
- }
- }
- return found;
- };
-
- /**
- * private but needs to be exposed.
- */
- this.isFloating = function() {
- return floatingEndpoint != null;
- };
-
- /**
- * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can.
- */
- this.connectorSelector = function() {
- var candidate = self.connections[0];
- if (self.isTarget && candidate) return candidate;
- else {
- return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate;
- }
- };
-
- /*
- * Function: isFull
- * Returns whether or not the Endpoint can accept any more Connections.
- */
- this.isFull = function() {
- return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections);
- };
- /*
- * Function: setDragAllowedWhenFull
- * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in
- * which you're going to provide some other way of breaking connections, if you need to break them at all. This property
- * is by default true; use it in conjunction with the 'reattach' option on a connect call.
- *
- * Parameters:
- * allowed - whether drag is allowed or not when the Endpoint is full.
- */
- this.setDragAllowedWhenFull = function(allowed) {
- dragAllowedWhenFull = allowed;
- };
- /*
- * Function: setStyle
- * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call.
- * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to
- * setStyle and deprecate it if so.
- *
- * Parameters:
- * style - Style object to set, for example {fillStyle:"blue"}.
- *
- * @deprecated use setPaintStyle instead.
- */
- this.setStyle = self.setPaintStyle;
-
- /**
- * a deep equals check. everything must match, including the anchor,
- * styles, everything. TODO: finish Endpoint.equals
- */
- this.equals = function(endpoint) {
- return this.anchor.equals(endpoint.anchor);
- };
-
- // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null,
- // or no connection to it is found, we return the first connection in our list.
- var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) {
- var idx = 0;
- if (elementWithPrecedence != null) {
- for (var i = 0; i < self.connections.length; i++) {
- if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) {
- idx = i;
- break;
- }
- }
- }
-
- return self.connections[idx];
- };
-
- /*
- * Function: paint
- * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint
- * any of the Endpoint's connections.
- *
- * Parameters:
- * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again.
- * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations.
- * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied.
- */
- this.paint = function(params) {
- params = params || {};
- var timestamp = params.timestamp,
- recalc = !(params.recalc === false);
- if (!timestamp || self.timestamp !== timestamp) {
- _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc });
- var xy = params.offset || offsets[_elementId];
- if(xy) {
- var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle;
- if (ap == null) {
- var wh = params.dimensions || sizes[_elementId];
- if (xy == null || wh == null) {
- _updateOffset( { elId : _elementId, timestamp : timestamp });
- xy = offsets[_elementId];
- wh = sizes[_elementId];
- }
- var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp };
- if (recalc && self.anchor.isDynamic && self.connections.length > 0) {
- var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence),
- oIdx = c.endpoints[0] == self ? 1 : 0,
- oId = oIdx == 0 ? c.sourceId : c.targetId,
- oOffset = offsets[oId], oWH = sizes[oId];
- anchorParams.txy = [ oOffset.left, oOffset.top ];
- anchorParams.twh = oWH;
- anchorParams.tElement = c.endpoints[oIdx];
- }
- ap = self.anchor.compute(anchorParams);
- }
-
- var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse);
- _endpoint.paint(d, self.paintStyleInUse, self.anchor);
- self.timestamp = timestamp;
-
-
- /* paint overlays*/
- for ( var i = 0; i < self.overlays.length; i++) {
- var o = self.overlays[i];
- if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d);
- }
- }
- }
- };
-
- this.repaint = this.paint;
-
- /**
- * @deprecated
- */
- this.removeConnection = this.detach; // backwards compatibility
-
- // is this a connection source? we make it draggable and have the
- // drag listener maintain a connection with a floating endpoint.
- if (jsPlumb.CurrentLibrary.isDragSupported(_element)) {
- var placeholderInfo = { id:null, element:null },
- jpc = null,
- existingJpc = false,
- existingJpcParams = null,
- _dragHandler = _makeConnectionDragHandler(placeholderInfo);
-
- var start = function() {
- // drag might have started on an endpoint that is not actually a source, but which has
- // one or more connections.
- jpc = self.connectorSelector();
- var _continue = true;
- // if not enabled, return
- if (!self.isEnabled()) _continue = false;
- // if no connection and we're not a source, return.
- if (jpc == null && !params.isSource) _continue = false;
- // otherwise if we're full and not allowed to drag, also return false.
- if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false;
- // if the connection was setup as not detachable or one of its endpoints
- // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable
- // is set to false...
- if (jpc != null && !jpc.isDetachable()) _continue = false;
-
- if (_continue === false) {
- // this is for mootools and yui. returning false from this causes jquery to stop drag.
- // the events are wrapped in both mootools and yui anyway, but i don't think returning
- // false from the start callback would stop a drag.
- if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag();
- _dragHandler.stopDrag();
- return false;
- }
-
- // if we're not full but there was a connection, make it null. we'll create a new one.
- if (jpc && !self.isFull() && params.isSource) jpc = null;
-
- _updateOffset( { elId : _elementId });
- inPlaceCopy = self.makeInPlaceCopy();
- inPlaceCopy.paint();
-
- _makeDraggablePlaceholder(placeholderInfo, self.parent);
-
- // set the offset of this div to be where 'inPlaceCopy' is, to start with.
- // TODO merge this code with the code in both Anchor and FloatingAnchor, because it
- // does the same stuff.
- var ipcoel = _getElementObject(inPlaceCopy.canvas),
- ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel),
- po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas);
- jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]});
-
- // when using makeSource and a parent, we first draw the source anchor on the source element, then
- // move it to the parent. note that this happens after drawing the placeholder for the
- // first time.
- if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance);
-
-
- // store the id of the dragging div and the source element. the drop function will pick these up.
- _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id);
- _setAttribute(_getElementObject(self.canvas), "elId", _elementId);
- // create a floating anchor
- floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element);
-
- if (jpc == null) {
- self.anchor.locked = true;
- self.setHover(false, false);
- // TODO the hover call above does not reset any target endpoint's hover
- // states.
- // create a connection. one end is this endpoint, the other is a floating endpoint.
- jpc = _newConnection({
- sourceEndpoint : self,
- targetEndpoint : floatingEndpoint,
- source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly.
- target : placeholderInfo.element,
- anchors : [ self.anchor, floatingEndpoint.anchor ],
- paintStyle : params.connectorStyle, // this can be null. Connection will use the default.
- hoverPaintStyle:params.connectorHoverStyle,
- connector : params.connector, // this can also be null. Connection will use the default.
- overlays : params.connectorOverlays
- });
-
- } else {
- existingJpc = true;
- jpc.connector.setHover(false, false);
- // if existing connection, allow to be dropped back on the source endpoint (issue 51).
- _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true);
- // new anchor idx
- var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1;
- jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index.
- self.detachFromConnection(jpc); // detach from the connection while dragging is occurring.
-
- // store the original scope (issue 57)
- var c = _getElementObject(self.canvas),
- dragScope = jsPlumb.CurrentLibrary.getDragScope(c);
- _setAttribute(c, "originalScope", dragScope);
- // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones
- // that have our drop scope (issue 57).
- var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);
- jsPlumb.CurrentLibrary.setDragScope(c, dropScope);
-
- // now we replace ourselves with the temporary div we created above:
- if (anchorIdx == 0) {
- existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ];
- jpc.source = placeholderInfo.element;
- jpc.sourceId = placeholderInfo.id;
- } else {
- existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ];
- jpc.target = placeholderInfo.element;
- jpc.targetId = placeholderInfo.id;
- }
-
- // lock the other endpoint; if it is dynamic it will not move while the drag is occurring.
- jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true;
- // store the original endpoint and assign the new floating endpoint for the drag.
- jpc.suspendedEndpoint = jpc.endpoints[anchorIdx];
- jpc.suspendedEndpoint.setHover(false);
- jpc.endpoints[anchorIdx] = floatingEndpoint;
-
- // fire an event that informs that a connection is being dragged
- fireConnectionDraggingEvent(jpc);
-
- }
- // register it and register connection on it.
- floatingConnections[placeholderInfo.id] = jpc;
- floatingEndpoint.addConnection(jpc);
- // only register for the target endpoint; we will not be dragging the source at any time
- // before this connection is either discarded or made into a permanent connection.
- _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint);
- // tell jsplumb about it
- _currentInstance.currentlyDragging = true;
- };
-
- var jpcl = jsPlumb.CurrentLibrary,
- dragOptions = params.dragOptions || {},
- defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions),
- startEvent = jpcl.dragEvents["start"],
- stopEvent = jpcl.dragEvents["stop"],
- dragEvent = jpcl.dragEvents["drag"];
-
- dragOptions = jsPlumb.extend(defaultOpts, dragOptions);
- dragOptions.scope = dragOptions.scope || self.scope;
- dragOptions[startEvent] = _wrap(dragOptions[startEvent], start);
- // extracted drag handler function so can be used by makeSource
- dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag);
- dragOptions[stopEvent] = _wrap(dragOptions[stopEvent],
- function() {
- var originalEvent = jpcl.getDropEvent(arguments);
- _currentInstance.currentlyDragging = false;
- _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) {
- return e.id == floatingEndpoint.id;
- });
- _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted)
- _removeElement(inPlaceCopy.canvas, _element);
- _currentInstance.anchorManager.clearFor(placeholderInfo.id);
- var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
- jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false;
- if (jpc.endpoints[idx] == floatingEndpoint) {
- // if the connection was an existing one:
- if (existingJpc && jpc.suspendedEndpoint) {
- // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the
- // floating endpoint has been replaced.
- if (idx == 0) {
- jpc.source = existingJpcParams[0];
- jpc.sourceId = existingJpcParams[1];
- } else {
- jpc.target = existingJpcParams[0];
- jpc.targetId = existingJpcParams[1];
- }
-
- // restore the original scope (issue 57)
- jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]);
- jpc.endpoints[idx] = jpc.suspendedEndpoint;
- if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) {
- jpc.setHover(false);
- jpc.floatingAnchorIndex = null;
- jpc.suspendedEndpoint.addConnection(jpc);
- _currentInstance.repaint(existingJpcParams[1]);
- }
- jpc._forceDetach = null;
- } else {
- // TODO this looks suspiciously kind of like an Endpoint.detach call too.
- // i wonder if this one should post an event though. maybe this is good like this.
- _removeElements(jpc.connector.getDisplayElements(), self.parent);
- self.detachFromConnection(jpc);
- }
- }
- self.anchor.locked = false;
- self.paint({recalc:false});
- jpc.setHover(false, false);
-
- fireConnectionDragStopEvent(jpc);
-
- jpc = null;
- inPlaceCopy = null;
- delete endpointsByElement[floatingEndpoint.elementId];
- floatingEndpoint.anchor = null;
- floatingEndpoint = null;
- _currentInstance.currentlyDragging = false;
-
-
- });
-
- var i = _getElementObject(self.canvas);
- jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true);
- }
-
- // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections
- // back onto the endpoint you detached it from.
- var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) {
- if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) {
- var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions;
- dropOptions = jsPlumb.extend( {}, dropOptions);
- dropOptions.scope = dropOptions.scope || self.scope;
- var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'],
- overEvent = jsPlumb.CurrentLibrary.dragEvents['over'],
- outEvent = jsPlumb.CurrentLibrary.dragEvents['out'],
- drop = function() {
-
- var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments),
- draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)),
- id = _getAttribute(draggable, "dragId"),
- elId = _getAttribute(draggable, "elId"),
- scope = _getAttribute(draggable, "originalScope"),
- jpc = floatingConnections[id];
-
- if (jpc != null) {
- var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0;
-
- // restore the original scope if necessary (issue 57)
- if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope);
-
- var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true;
-
- if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) {
-
- var _doContinue = true;
-
- // the second check here is for the case that the user is dropping it back
- // where it came from.
- if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) {
- if (idx == 0) {
- jpc.source = jpc.suspendedEndpoint.element;
- jpc.sourceId = jpc.suspendedEndpoint.elementId;
- } else {
- jpc.target = jpc.suspendedEndpoint.element;
- jpc.targetId = jpc.suspendedEndpoint.elementId;
- }
-
- if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc))
- _doContinue = false;
- }
-
- // these have to be set before testing for beforeDrop.
- if (idx == 0) {
- jpc.source = self.element;
- jpc.sourceId = self.elementId;
- } else {
- jpc.target = self.element;
- jpc.targetId = self.elementId;
- }
-
-
- // now check beforeDrop. this will be available only on Endpoints that are setup to
- // have a beforeDrop condition (although, secretly, under the hood all Endpoints and
- // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because
- // it only makes sense to have it on a target endpoint.
- _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope);
-
- if (_doContinue) {
- // remove this jpc from the current endpoint
- jpc.endpoints[idx].detachFromConnection(jpc);
- if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc);
- jpc.endpoints[idx] = self;
- self.addConnection(jpc);
-
- // copy our parameters in to the connection:
- var params = self.getParameters();
- for (var aParam in params)
- jpc.setParameter(aParam, params[aParam]);
-
- if (!jpc.suspendedEndpoint) {
- //_addToList(connectionsByScope, jpc.scope, jpc);
- _initDraggableIfNecessary(self.element, params.draggable, {});
- }
- else {
- var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId;
- // fire a detach event
- fireDetachEvent({
- source : idx == 0 ? suspendedElement : jpc.source,
- target : idx == 1 ? suspendedElement : jpc.target,
- sourceId : idx == 0 ? suspendedElementId : jpc.sourceId,
- targetId : idx == 1 ? suspendedElementId : jpc.targetId,
- sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0],
- targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1],
- connection : jpc
- }, true, originalEvent);
- }
-
- // finalise will inform the anchor manager and also add to
- // connectionsByScope if necessary.
- _finaliseConnection(jpc, null, originalEvent);
- }
- else {
- // otherwise just put it back on the endpoint it was on before the drag.
- if (jpc.suspendedEndpoint) {
- // self.detachFrom(jpc);
- jpc.endpoints[idx] = jpc.suspendedEndpoint;
- jpc.setHover(false);
- jpc._forceDetach = true;
- if (idx == 0) {
- jpc.source = jpc.suspendedEndpoint.element;
- jpc.sourceId = jpc.suspendedEndpoint.elementId;
- } else {
- jpc.target = jpc.suspendedEndpoint.element;
- jpc.targetId = jpc.suspendedEndpoint.elementId;;
- }
- jpc.suspendedEndpoint.addConnection(jpc);
-
- jpc.endpoints[0].repaint();
- jpc.repaint();
- _currentInstance.repaint(jpc.source.elementId);
- jpc._forceDetach = false;
- }
- }
-
- jpc.floatingAnchorIndex = null;
- }
- _currentInstance.currentlyDragging = false;
- delete floatingConnections[id];
- }
- };
-
- dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop);
- dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() {
- if (self.isTarget) {
- var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments),
- id = _getAttribute( _getElementObject(draggable), "dragId"),
- jpc = floatingConnections[id];
- if (jpc != null) {
- var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
- jpc.endpoints[idx].anchor.over(self.anchor);
- }
- }
- });
- dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() {
- if (self.isTarget) {
- var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments),
- id = _getAttribute( _getElementObject(draggable), "dragId"),
- jpc = floatingConnections[id];
- if (jpc != null) {
- var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
- jpc.endpoints[idx].anchor.out();
- }
- }
- });
- jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient);
- }
- };
-
- // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported.
- _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self);
-
- return self;
- };
- };
-
- var jsPlumb = window.jsPlumb = new jsPlumbInstance();
- jsPlumb.getInstance = function(_defaults) {
- var j = new jsPlumbInstance(_defaults);
- j.init();
- return j;
- };
- jsPlumb.util = {
- convertStyle : function(s, ignoreAlpha) {
- // TODO: jsPlumb should support a separate 'opacity' style member.
- if ("transparent" === s) return s;
- var o = s,
- pad = function(n) { return n.length == 1 ? "0" + n : n; },
- hex = function(k) { return pad(Number(k).toString(16)); },
- pattern = /(rgb[a]?\()(.*)(\))/;
- if (s.match(pattern)) {
- var parts = s.match(pattern)[2].split(",");
- o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]);
- if (!ignoreAlpha && parts.length == 4)
- o = o + hex(parts[3]);
- }
- return o;
- },
- gradient : function(p1, p2) {
- p1 = _isArray(p1) ? p1 : [p1.x, p1.y];
- p2 = _isArray(p2) ? p2 : [p2.x, p2.y];
- return (p2[1] - p1[1]) / (p2[0] - p1[0]);
- },
- normal : function(p1, p2) {
- return -1 / jsPlumb.util.gradient(p1,p2);
- },
- segment : function(p1, p2) {
- p1 = _isArray(p1) ? p1 : [p1.x, p1.y];
- p2 = _isArray(p2) ? p2 : [p2.x, p2.y];
- if (p2[0] > p1[0]) {
- return (p2[1] > p1[1]) ? 2 : 1;
- }
- else {
- return (p2[1] > p1[1]) ? 3 : 4;
- }
- },
- intersects : function(r1, r2) {
- var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h,
- a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h;
-
- return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) ||
- ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) ||
- ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) ||
- ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) ||
-
- ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) ||
- ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) ||
- ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) ||
- ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) );
- },
- segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ],
- inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ],
- pointOnLine : function(fromPoint, toPoint, distance) {
- var m = jsPlumb.util.gradient(fromPoint, toPoint),
- s = jsPlumb.util.segment(fromPoint, toPoint),
- segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s],
- theta = Math.atan(m),
- y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1],
- x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0];
- return { x:fromPoint.x + x, y:fromPoint.y + y };
- },
- /**
- * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long.
- * @param fromPoint
- * @param toPoint
- * @param length
- */
- perpendicularLineTo : function(fromPoint, toPoint, length) {
- var m = jsPlumb.util.gradient(fromPoint, toPoint),
- theta2 = Math.atan(-1 / m),
- y = length / 2 * Math.sin(theta2),
- x = length / 2 * Math.cos(theta2);
- return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}];
- }
- };
-
- var _curryAnchor = function(x, y, ox, oy, type, fnInit) {
- return function(params) {
- params = params || {};
- //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance);
- var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance);
- a.type = type;
- if (fnInit) fnInit(a, params);
- return a;
- };
- };
- jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter");
- jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter");
- jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle");
- jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle");
- jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center");
- jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight");
- jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight");
- jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft");
- jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft");
-
- // TODO test that this does not break with the current instance idea
- jsPlumb.Defaults.DynamicAnchors = function(params) {
- return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance);
- };
- jsPlumb.Anchors["AutoDefault"] = function(params) {
- var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params));
- a.type = "AutoDefault";
- return a;
- };
-
- jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) {
- // find what to use as the "position finder". the user may have supplied a String which represents
- // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the
- // position finder as a function. we find out what to use and then set it on the anchor.
- var pf = params.position || "Fixed";
- anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf;
- // always set the constructor params; the position finder might need them later (the Grid one does,
- // for example)
- anchor.constructorParams = params;
- });
-
- // Continuous anchor is just curried through to the 'get' method of the continuous anchor
- // factory.
- jsPlumb.Anchors["Continuous"] = function(params) {
- return params.jsPlumbInstance.continuousAnchorFactory.get(params);
- };
-
- // these are the default anchor positions finders, which are used by the makeTarget function. supply
- // a position finder argument to that function allows you to specify where the resulting anchor will
- // be located
- jsPlumb.AnchorPositionFinders = {
- "Fixed": function(dp, ep, es, params) {
- return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ];
- },
- "Grid":function(dp, ep, es, params) {
- var dx = dp.left - ep.left, dy = dp.top - ep.top,
- gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]),
- mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
- return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ];
- }
- };
-})();
-/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.8
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the default Connectors, Endpoint and Overlay definitions.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
-
-(function() {
-
- /**
- *
- * Helper class to consume unused mouse events by components that are DOM elements and
- * are used by all of the different rendering modes.
- *
- */
- jsPlumb.DOMElementComponent = function(params) {
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- // when render mode is canvas, these functions may be called by the canvas mouse handler.
- // this component is safe to pipe this stuff to /dev/null.
- this.mousemove =
- this.dblclick =
- this.click =
- this.mousedown =
- this.mouseup = function(e) { };
- };
-
- /**
- * Class: Connectors.Straight
- * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters.
- */
- jsPlumb.Connectors.Straight = function() {
- this.type = "Straight";
- var self = this,
- currentPoints = null,
- _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length;
-
- /**
- * Computes the new size and position of the canvas.
- */
- this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) {
- var w = Math.abs(sourcePos[0] - targetPos[0]),
- h = Math.abs(sourcePos[1] - targetPos[1]),
- // these are padding to ensure the whole connector line appears
- xo = 0.45 * w, yo = 0.45 * h;
- // these are padding to ensure the whole connector line appears
- w *= 1.9; h *=1.9;
-
- var x = Math.min(sourcePos[0], targetPos[0]) - xo;
- var y = Math.min(sourcePos[1], targetPos[1]) - yo;
-
- // minimum size is 2 * line Width if minWidth was not given.
- var calculatedMinWidth = Math.max(2 * lineWidth, minWidth);
-
- if (w < calculatedMinWidth) {
- w = calculatedMinWidth;
- x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2);
- xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;
- }
- if (h < calculatedMinWidth) {
- h = calculatedMinWidth;
- y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2);
- yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;
- }
-
- _sx = sourcePos[0] < targetPos[0] ? xo : w-xo;
- _sy = sourcePos[1] < targetPos[1] ? yo:h-yo;
- _tx = sourcePos[0] < targetPos[0] ? w-xo : xo;
- _ty = sourcePos[1] < targetPos[1] ? h-yo : yo;
- currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ];
- _dx = _tx - _sx, _dy = _ty - _sy;
- //_m = _dy / _dx, _m2 = -1 / _m;
- _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m;
- _b = -1 * ((_m * _sx) - _sy);
- _theta = Math.atan(_m); _theta2 = Math.atan(_m2);
- //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty});
- _length = Math.sqrt((_dx * _dx) + (_dy * _dy));
-
- return currentPoints;
- };
-
-
- /**
- * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
- * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
- */
- this.pointOnPath = function(location) {
- if (location == 0)
- return { x:_sx, y:_sy };
- else if (location == 1)
- return { x:_tx, y:_ty };
- else
- return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length);
- };
-
- /**
- * returns the gradient of the connector at the given point - which for us is constant.
- */
- this.gradientAtPoint = function(location) {
- return _m;
- };
-
- /**
- * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where
- * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
- * this hands off to jsPlumb.util to do the maths, supplying two points and the distance.
- */
- this.pointAlongPathFrom = function(location, distance) {
- var p = self.pointOnPath(location),
- farAwayPoint = location == 1 ? {
- x:_sx + ((_tx - _sx) * 10),
- y:_sy + ((_sy - _ty) * 10)
- } : {x:_tx, y:_ty };
-
- return jsPlumb.util.pointOnLine(p, farAwayPoint, distance);
- };
- };
-
-
- /**
- * Class:Connectors.Bezier
- * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's
- * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below.
- */
- /**
- * Function:Constructor
- *
- * Parameters:
- * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not
- * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line.
- * Optional; defaults to 150.
- * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0.
- *
- */
- jsPlumb.Connectors.Bezier = function(params) {
- var self = this;
- params = params || {};
- this.majorAnchor = params.curviness || 150;
- this.minorAnchor = 10;
- var currentPoints = null;
- this.type = "Bezier";
-
- this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) {
- // determine if the two anchors are perpendicular to each other in their orientation. we swap the control
- // points around if so (code could be tightened up)
- var soo = sourceAnchor.getOrientation(sourceEndpoint),
- too = targetAnchor.getOrientation(targetEndpoint),
- perpendicular = soo[0] != too[0] || soo[1] == too[1],
- p = [],
- ma = self.majorAnchor, mi = self.minorAnchor;
-
- if (!perpendicular) {
- if (soo[0] == 0) // X
- p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi);
- else p.push(point[0] - (ma * soo[0]));
-
- if (soo[1] == 0) // Y
- p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi);
- else p.push(point[1] + (ma * too[1]));
- }
- else {
- if (too[0] == 0) // X
- p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi);
- else p.push(point[0] + (ma * too[0]));
-
- if (too[1] == 0) // Y
- p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi);
- else p.push(point[1] + (ma * soo[1]));
- }
-
- return p;
- };
-
- var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY;
-
- this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) {
- lineWidth = lineWidth || 0;
- _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth;
- _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth;
- _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2);
- _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2);
- _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2);
- _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2);
- _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2);
- _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2);
-
- _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor);
- _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor);
- var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2),
- maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2);
-
- if (maxx > _w) _w = maxx;
- if (minx < 0) {
- _canvasX += minx; var ox = Math.abs(minx);
- _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox;
- }
-
- var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2),
- maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2);
-
- if (maxy > _h) _h = maxy;
- if (miny < 0) {
- _canvasY += miny; var oy = Math.abs(miny);
- _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy;
- }
-
- if (minWidth && _w < minWidth) {
- var posAdjust = (minWidth - _w) / 2;
- _w = minWidth;
- _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust;
- }
-
- if (minWidth && _h < minWidth) {
- var posAdjust = (minWidth - _h) / 2;
- _h = minWidth;
- _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust;
- }
-
- currentPoints = [_canvasX, _canvasY, _w, _h,
- _sx, _sy, _tx, _ty,
- _CP[0], _CP[1], _CP2[0], _CP2[1] ];
-
- return currentPoints;
- };
-
- var _makeCurve = function() {
- return [
- { x:_sx, y:_sy },
- { x:_CP[0], y:_CP[1] },
- { x:_CP2[0], y:_CP2[1] },
- { x:_tx, y:_ty }
- ];
- };
-
- /**
- * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
- * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
- */
- this.pointOnPath = function(location) {
- return jsBezier.pointOnCurve(_makeCurve(), location);
- };
-
- /**
- * returns the gradient of the connector at the given point.
- */
- this.gradientAtPoint = function(location) {
- return jsBezier.gradientAtPoint(_makeCurve(), location);
- };
-
- /**
- * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult.
- * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous
- * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see.
- * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller
- * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to
- * calculate the step as a function of distance/distance between endpoints.
- */
- this.pointAlongPathFrom = function(location, distance) {
- return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance);
- };
- };
-
-
- /**
- * Class: Connectors.Flowchart
- * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels.
- */
- jsPlumb.Connectors.Flowchart = function(params) {
- this.type = "Flowchart";
- params = params || {};
- var self = this,
- minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30,
- segments = [],
- totalLength = 0,
- segmentProportions = [],
- segmentProportionalLengths = [],
- points = [],
- swapX, swapY,
- maxX = 0, maxY = 0,
- /**
- * recalculates the points at which the segments begin and end, proportional to the total length travelled
- * by all the segments that constitute the connector. we use this to help with pointOnPath calculations.
- */
- updateSegmentProportions = function(startX, startY, endX, endY) {
- var curLoc = 0;
- for (var i = 0; i < segments.length; i++) {
- segmentProportionalLengths[i] = segments[i][5] / totalLength;
- segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ];
- }
- },
- appendSegmentsToPoints = function() {
- points.push(segments.length);
- for (var i = 0; i < segments.length; i++) {
- points.push(segments[i][0]);
- points.push(segments[i][1]);
- }
- },
- /**
- * helper method to add a segment.
- */
- addSegment = function(x, y, sx, sy, tx, ty) {
- var lx = segments.length == 0 ? sx : segments[segments.length - 1][0],
- ly = segments.length == 0 ? sy : segments[segments.length - 1][1],
- m = x == lx ? Infinity : 0,
- l = Math.abs(x == lx ? y - ly : x - lx);
- segments.push([x, y, lx, ly, m, l]);
- totalLength += l;
-
- maxX = Math.max(maxX, x);
- maxY = Math.max(maxY, y);
- },
- /**
- * returns [segment, proportion of travel in segment, segment index] for the segment
- * that contains the point which is 'location' distance along the entire path, where
- * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths
- * are made up of a list of segments, each of which contributes some fraction to
- * the total length.
- */
- findSegmentForLocation = function(location) {
- var idx = segmentProportions.length - 1, inSegmentProportion = 1;
- for (var i = 0; i < segmentProportions.length; i++) {
- if (segmentProportions[i][1] >= location) {
- idx = i;
- inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i];
- break;
- }
- }
- return { segment:segments[idx], proportion:inSegmentProportion, index:idx };
- };
-
- this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint,
- sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) {
-
- segments = [];
- segmentProportions = [];
- totalLength = 0;
- segmentProportionalLengths = [];
- maxX = maxY = 0;
-
- swapX = targetPos[0] < sourcePos[0];
- swapY = targetPos[1] < sourcePos[1];
-
- var lw = lineWidth || 1,
- offx = (lw / 2) + (minStubLength * 2),
- offy = (lw / 2) + (minStubLength * 2),
- so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint),
- to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint),
- x = swapX ? targetPos[0] : sourcePos[0],
- y = swapY ? targetPos[1] : sourcePos[1],
- w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx,
- h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy;
-
- // if either anchor does not have an orientation set, we derive one from their relative
- // positions. we fix the axis to be the one in which the two elements are further apart, and
- // point each anchor at the other element. this is also used when dragging a new connection.
- if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) {
- var index = w > h ? 0 : 1, oIndex = [1,0][index];
- so = []; to = [];
- so[index] = sourcePos[index] > targetPos[index] ? -1 : 1;
- to[index] = sourcePos[index] > targetPos[index] ? 1 : -1;
- so[oIndex] = 0;
- to[oIndex] = 0;
- }
-
- if (w < minWidth) {
- offx += (minWidth - w) / 2;
- w = minWidth;
- }
- if (h < minWidth) {
- offy += (minWidth - h) / 2;
- h = minWidth;
- }
-
- var sx = swapX ? w-offx : offx,
- sy = swapY ? h-offy : offy,
- tx = swapX ? offx : w-offx ,
- ty = swapY ? offy : h-offy,
- startStubX = sx + (so[0] * minStubLength),
- startStubY = sy + (so[1] * minStubLength),
- endStubX = tx + (to[0] * minStubLength),
- endStubY = ty + (to[1] * minStubLength),
- isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength,
- isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength,
- midx = startStubX + ((endStubX - startStubX) / 2),
- midy = startStubY + ((endStubY - startStubY) / 2),
- oProduct = ((so[0] * to[0]) + (so[1] * to[1])),
- opposite = oProduct == -1,
- perpendicular = oProduct == 0,
- orthogonal = oProduct == 1;
-
- x -= offx; y -= offy;
- points = [x, y, w, h, sx, sy, tx, ty];
- var extraPoints = [];
-
- addSegment(startStubX, startStubY, sx, sy, tx, ty);
-
- var sourceAxis = so[0] == 0 ? "y" : "x",
- anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular",
- segment = jsPlumb.util.segment([sx, sy], [tx, ty]),
- flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1,
- flipSegments = {
- "x":[null, 4, 3, 2, 1],
- "y":[null, 2, 1, 4, 3]
- }
-
- if (flipSourceSegments)
- segment = flipSegments[sourceAxis][segment];
-
- var findClearedLine = function(start, mult, anchorPos, dimension) {
- return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength);
- //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX,
- },
-
- lineCalculators = {
- oppositex : function() {
- if (sourceEndpoint.elementId == targetEndpoint.elementId) {
- var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength;
- return [ [ startStubX, _y ], [ endStubX, _y ]];
- }
- else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) {
- return [[ midx, sy ], [ midx, ty ]];
- }
- else {
- return [[ startStubX, midy ], [endStubX, midy ]];
- }
- },
- orthogonalx : function() {
- if (segment == 1 || segment == 2) {
- return [ [ endStubX, startStubY ]];
- }
- else {
- return [ [ startStubX, endStubY ]];
- }
- },
- perpendicularx : function() {
- var my = (ty + sy) / 2;
- if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) {
- if (Math.abs(tx - sx) > minStubLength)
- return [ [endStubX, startStubY ]];
- else
- return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]];
- }
- else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) {
- return [ [ startStubX, my ], [ endStubX, my ]];
- }
- else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) {
- return [ [ startStubX, endStubY ]];
- }
- else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) {
- if (Math.abs(tx - sx) > minStubLength)
- return [ [ midx, startStubY ], [ midx, endStubY ]];
- else
- return [ [ startStubX, endStubY ]];
- }
- },
- oppositey : function() {
- if (sourceEndpoint.elementId == targetEndpoint.elementId) {
- var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength;
- return [ [ _x, startStubY ], [ _x, endStubY ]];
- }
- else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) {
- return [[ sx, midy ], [ tx, midy ]];
- }
- else {
- return [[ midx, startStubY ], [midx, endStubY ]];
- }
- },
- orthogonaly : function() {
- if (segment == 2 || segment == 3) {
- return [ [ startStubX, endStubY ]];
- }
- else {
- return [ [ endStubX, startStubY ]];
- }
- },
- perpendiculary : function() {
- var mx = (tx + sx) / 2;
- if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) {
- if (Math.abs(tx - sx) > minStubLength)
- return [ [startStubX, endStubY ]];
- else
- return [ [startStubX, midy ], [ endStubX, midy ]];
- }
- else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) {
- var mx = (tx + sx) / 2;
- return [ [ mx, startStubY ], [ mx, endStubY ]];
- }
- else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) {
- return [ [ endStubX, startStubY ]];
- }
- else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) {
- if (Math.abs(ty - sy) > minStubLength)
- return [ [ startStubX, midy ], [ endStubX, midy ]];
- else
- return [ [ endStubX, startStubY ]];
- }
- }
- };
-
- var p = lineCalculators[anchorOrientation + sourceAxis]();
- if (p) {
- for (var i = 0; i < p.length; i++) {
- addSegment(p[i][0], p[i][1], sx, sy, tx, ty);
- }
- }
-
-
- addSegment(endStubX, endStubY, sx, sy, tx, ty);
- addSegment(tx, ty, sx, sy, tx, ty);
-
- appendSegmentsToPoints();
- updateSegmentProportions(sx, sy, tx, ty);
-
- // adjust the max values of the canvas if we have a value that is larger than what we previously set.
- //
- if (maxY > points[3]) points[3] = maxY + (lineWidth * 2);
- if (maxX > points[2]) points[2] = maxX + (lineWidth * 2);
-
- return points;
- };
-
- /**
- * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
- * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position
- * from our knowledge of the segment's start and end points.
- */
- this.pointOnPath = function(location) {
- return self.pointAlongPathFrom(location, 0);
- };
-
- /**
- * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the
- * segment the point falls in. segment gradients are calculated in the compute method.
- */
- this.gradientAtPoint = function(location) {
- return segments[findSegmentForLocation(location)["index"]][4];
- };
-
- /**
- * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where
- * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view
- * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several
- * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is
- * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better
- * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along
- * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a
- * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional
- * advantage is, of course, that there's less computation involved doing it that way.
- */
- this.pointAlongPathFrom = function(location, distance) {
- var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4];
- var e = {
- x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance,
- y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance,
- segmentInfo : s
- };
-
- return e;
- };
- };
-
- // ********************************* END OF CONNECTOR TYPES *******************************************************************
-
- // ********************************* ENDPOINT TYPES *******************************************************************
-
- /**
- * Class: Endpoints.Dot
- * A round endpoint, with default radius 10 pixels.
- */
-
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * radius - radius of the endpoint. defaults to 10 pixels.
- */
- jsPlumb.Endpoints.Dot = function(params) {
- this.type = "Dot";
- var self = this;
- params = params || {};
- this.radius = params.radius || 10;
- this.defaultOffset = 0.5 * this.radius;
- this.defaultInnerRadius = this.radius / 3;
-
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- var r = endpointStyle.radius || self.radius,
- x = anchorPoint[0] - r,
- y = anchorPoint[1] - r;
- return [ x, y, r * 2, r * 2, r ];
- };
- };
-
- /**
- * Class: Endpoints.Rectangle
- * A Rectangular Endpoint, with default size 20x20.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * width - width of the endpoint. defaults to 20 pixels.
- * height - height of the endpoint. defaults to 20 pixels.
- */
- jsPlumb.Endpoints.Rectangle = function(params) {
- this.type = "Rectangle";
- var self = this;
- params = params || {};
- this.width = params.width || 20;
- this.height = params.height || 20;
-
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- var width = endpointStyle.width || self.width,
- height = endpointStyle.height || self.height,
- x = anchorPoint[0] - (width/2),
- y = anchorPoint[1] - (height/2);
- return [ x, y, width, height];
- };
- };
-
-
- var DOMElementEndpoint = function(params) {
- jsPlumb.DOMElementComponent.apply(this, arguments);
- var self = this;
-
- var displayElements = [ ];
- this.getDisplayElements = function() {
- return displayElements;
- };
-
- this.appendDisplayElement = function(el) {
- displayElements.push(el);
- };
- };
- /**
- * Class: Endpoints.Image
- * Draws an image as the Endpoint.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * src - location of the image to use.
- */
- jsPlumb.Endpoints.Image = function(params) {
-
- this.type = "Image";
- DOMElementEndpoint.apply(this, arguments);
-
- var self = this,
- initialized = false,
- widthToUse = params.width,
- heightToUse = params.height,
- _onload = null,
- _endpoint = params.endpoint;
-
- this.img = new Image();
- self.ready = false;
-
- this.img.onload = function() {
- self.ready = true;
- widthToUse = widthToUse || self.img.width;
- heightToUse = heightToUse || self.img.height;
- if (_onload) {
- _onload(self);
- }
- };
-
- /*
- Function: setImage
- Sets the Image to use in this Endpoint.
-
- Parameters:
- img - may be a URL or an Image object
- onload - optional; a callback to execute once the image has loaded.
- */
- _endpoint.setImage = function(img, onload) {
- var s = img.constructor == String ? img : img.src;
- _onload = onload;
- self.img.src = img;
-
- if (self.canvas != null)
- self.canvas.setAttribute("src", img);
- };
-
- _endpoint.setImage(params.src || params.url, params.onload);
-
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- self.anchorPoint = anchorPoint;
- if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2,
- widthToUse, heightToUse];
- else return [0,0,0,0];
- };
-
- self.canvas = document.createElement("img"), initialized = false;
- self.canvas.style["margin"] = 0;
- self.canvas.style["padding"] = 0;
- self.canvas.style["outline"] = 0;
- self.canvas.style["position"] = "absolute";
- var clazz = params.cssClass ? " " + params.cssClass : "";
- self.canvas.className = jsPlumb.endpointClass + clazz;
- if (widthToUse) self.canvas.setAttribute("width", widthToUse);
- if (heightToUse) self.canvas.setAttribute("height", heightToUse);
- jsPlumb.appendElement(self.canvas, params.parent);
- self.attachListeners(self.canvas, self);
-
- var actuallyPaint = function(d, style, anchor) {
- if (!initialized) {
- self.canvas.setAttribute("src", self.img.src);
- self.appendDisplayElement(self.canvas);
- initialized = true;
- }
- var x = self.anchorPoint[0] - (widthToUse / 2),
- y = self.anchorPoint[1] - (heightToUse / 2);
- jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse);
- };
-
- this.paint = function(d, style, anchor) {
- if (self.ready) {
- actuallyPaint(d, style, anchor);
- }
- else {
- window.setTimeout(function() {
- self.paint(d, style, anchor);
- }, 200);
- }
- };
- };
-
- /**
- * Class: Endpoints.Blank
- * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints.
- */
- jsPlumb.Endpoints.Blank = function(params) {
- var self = this;
- this.type = "Blank";
- DOMElementEndpoint.apply(this, arguments);
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- return [anchorPoint[0], anchorPoint[1],10,0];
- };
-
- self.canvas = document.createElement("div");
- self.canvas.style.display = "block";
- self.canvas.style.width = "1px";
- self.canvas.style.height = "1px";
- self.canvas.style.background = "transparent";
- self.canvas.style.position = "absolute";
- self.canvas.className = self._jsPlumb.endpointClass;
- jsPlumb.appendElement(self.canvas, params.parent);
-
- this.paint = function(d, style, anchor) {
- jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
- };
- };
-
- /**
- * Class: Endpoints.Triangle
- * A triangular Endpoint.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * width - width of the triangle's base. defaults to 55 pixels.
- * height - height of the triangle from base to apex. defaults to 55 pixels.
- */
- jsPlumb.Endpoints.Triangle = function(params) {
- this.type = "Triangle";
- params = params || { };
- params.width = params.width || 55;
- params.height = params.height || 55;
- this.width = params.width;
- this.height = params.height;
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- var width = endpointStyle.width || self.width,
- height = endpointStyle.height || self.height,
- x = anchorPoint[0] - (width/2),
- y = anchorPoint[1] - (height/2);
- return [ x, y, width, height ];
- };
- };
-// ********************************* END OF ENDPOINT TYPES *******************************************************************
-
-
-// ********************************* OVERLAY DEFINITIONS ***********************************************************************
-
- var AbstractOverlay = function(params) {
- var visible = true, self = this;
- this.isAppendedAtTopLevel = true;
- this.component = params.component;
- this.loc = params.location == null ? 0.5 : params.location;
- this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation;
- this.setVisible = function(val) {
- visible = val;
- self.component.repaint();
- };
- this.isVisible = function() { return visible; };
- this.hide = function() { self.setVisible(false); };
- this.show = function() { self.setVisible(true); };
-
- this.incrementLocation = function(amount) {
- self.loc += amount;
- self.component.repaint();
- };
- this.setLocation = function(l) {
- self.loc = l;
- self.component.repaint();
- };
- this.getLocation = function() {
- return self.loc;
- };
- };
-
-
- /**
- * Class: Overlays.Arrow
- *
- * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length
- * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction
- * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line
- * across the tail.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * length - distance in pixels from head to tail baseline. default 20.
- * width - width in pixels of the tail baseline. default 20.
- * fillStyle - style to use when filling the arrow. defaults to "black".
- * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
- * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
- * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623.
- * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
- * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
- */
- jsPlumb.Overlays.Arrow = function(params) {
- this.type = "Arrow";
- AbstractOverlay.apply(this, arguments);
- this.isAppendedAtTopLevel = false;
- params = params || {};
- var self = this;
-
- this.length = params.length || 20;
- this.width = params.width || 20;
- this.id = params.id;
- var direction = (params.direction || 1) < 0 ? -1 : 1,
- paintStyle = params.paintStyle || { lineWidth:1 },
- // how far along the arrow the lines folding back in come to. default is 62.3%.
- foldback = params.foldback || 0.623;
-
-
- this.computeMaxSize = function() { return self.width * 1.5; };
-
- this.cleanup = function() { }; // nothing to clean up for Arrows
-
- this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) {
-
- var hxy, mid, txy, tail, cxy;
- if (connector.pointAlongPathFrom) {
-
- if (self.loc == 1) {
- hxy = connector.pointOnPath(self.loc);
- mid = connector.pointAlongPathFrom(self.loc, -1);
- txy = jsPlumb.util.pointOnLine(hxy, mid, self.length);
- }
- else if (self.loc == 0) {
- txy = connector.pointOnPath(self.loc);
- mid = connector.pointAlongPathFrom(self.loc, 1);
- hxy = jsPlumb.util.pointOnLine(txy, mid, self.length);
- }
- else {
- hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2),
- mid = connector.pointOnPath(self.loc),
- txy = jsPlumb.util.pointOnLine(hxy, mid, self.length);
- }
-
- tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width);
- cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length);
-
- var minx = Math.min(hxy.x, tail[0].x, tail[1].x),
- maxx = Math.max(hxy.x, tail[0].x, tail[1].x),
- miny = Math.min(hxy.y, tail[0].y, tail[1].y),
- maxy = Math.max(hxy.y, tail[0].y, tail[1].y);
-
- var d = { hxy:hxy, tail:tail, cxy:cxy },
- strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle,
- fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle,
- lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth;
-
- self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions);
-
- return [ minx, maxx, miny, maxy];
- }
- else return [0,0,0,0];
- };
- };
-
- /**
- * Class: Overlays.PlainArrow
- *
- * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some
- * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does
- * a 'call' to Arrow with foldback set appropriately.
- */
- /**
- * Function: Constructor
- * See for allowed parameters for this overlay.
- */
- jsPlumb.Overlays.PlainArrow = function(params) {
- params = params || {};
- var p = jsPlumb.extend(params, {foldback:1});
- jsPlumb.Overlays.Arrow.call(this, p);
- this.type = "PlainArrow";
- };
-
- /**
- * Class: Overlays.Diamond
- *
- * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
- * happens that in this case, that point is greater than the length of the the arrow.
- *
- * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
- * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of
- * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
- * would be -l/4 in this case - move along one quarter of the total length.
- */
- /**
- * Function: Constructor
- * See for allowed parameters for this overlay.
- */
- jsPlumb.Overlays.Diamond = function(params) {
- params = params || {};
- var l = params.length || 40,
- p = jsPlumb.extend(params, {length:l/2, foldback:2});
- jsPlumb.Overlays.Arrow.call(this, p);
- this.type = "Diamond";
- };
-
-
-
- /**
- * Class: Overlays.Label
- * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb
- * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter
- * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it
- * puts on the Label's 'style' attribute, so the end result is the same.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes
- * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle.
- * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your
- * label function returns null. empty strings _will_ be painted.
- * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
- *
- */
- jsPlumb.Overlays.Label = function(params) {
- this.type = "Label";
- jsPlumb.DOMElementComponent.apply(this, arguments);
- AbstractOverlay.apply(this, arguments);
- this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle;
- this.id = params.id;
- this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it!
- var label = params.label || "",
- self = this,
- initialised = false,
- div = document.createElement("div"),
- labelText = null;
- div.style["position"] = "absolute";
-
- var clazz = params["_jsPlumb"].overlayClass + " " +
- (self.labelStyle.cssClass ? self.labelStyle.cssClass :
- params.cssClass ? params.cssClass : "");
-
- div.className = clazz;
-
- jsPlumb.appendElement(div, params.component.parent);
- jsPlumb.getId(div);
- self.attachListeners(div, self);
- self.canvas = div;
-
- //override setVisible
- var osv = self.setVisible;
- self.setVisible = function(state) {
- osv(state); // call superclass
- div.style.display = state ? "block" : "none";
- };
-
- this.getElement = function() {
- return div;
- };
-
- this.cleanup = function() {
- if (div != null) jsPlumb.CurrentLibrary.removeElement(div);
- };
-
- /*
- * Function: setLabel
- * sets the label's, um, label. you would think i'd call this function
- * 'setText', but you can pass either a Function or a String to this, so
- * it makes more sense as 'setLabel'.
- */
- this.setLabel = function(l) {
- label = l;
- labelText = null;
- self.component.repaint();
- };
-
- this.getLabel = function() {
- return label;
- };
-
- this.paint = function(component, d, componentDimensions) {
- if (!initialised) {
- component.appendDisplayElement(div);
- self.attachListeners(div, component);
- initialised = true;
- }
- div.style.left = (componentDimensions[0] + d.minx) + "px";
- div.style.top = (componentDimensions[1] + d.miny) + "px";
- };
-
- this.getTextDimensions = function() {
- if (typeof label == "function") {
- var lt = label(self);
- div.innerHTML = lt.replace(/\r\n/g, "
");
- }
- else {
- if (labelText == null) {
- labelText = label;
- div.innerHTML = labelText.replace(/\r\n/g, "
");
- }
- }
- var de = jsPlumb.CurrentLibrary.getElementObject(div),
- s = jsPlumb.CurrentLibrary.getSize(de);
- return {width:s[0], height:s[1]};
- };
-
- this.computeMaxSize = function(connector) {
- var td = self.getTextDimensions(connector);
- return td.width ? Math.max(td.width, td.height) * 1.5 : 0;
- };
-
- this.draw = function(component, currentConnectionPaintStyle, componentDimensions) {
- var td = self.getTextDimensions(component);
- if (td.width != null) {
- var cxy = {x:0,y:0};
- if (component.pointOnPath)
- cxy = component.pointOnPath(self.loc); // a connection
- else {
- var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc;
- cxy = { x:locToUse[0] * componentDimensions[2],
- y:locToUse[1] * componentDimensions[3] };
- }
-
- minx = cxy.x - (td.width / 2),
- miny = cxy.y - (td.height / 2);
-
- self.paint(component, {
- minx:minx,
- miny:miny,
- td:td,
- cxy:cxy
- }, componentDimensions);
-
- return [minx, minx+td.width, miny, miny+td.height];
- }
- else return [0,0,0,0];
- };
-
- this.reattachListeners = function(connector) {
- if (div) {
- self.reattachListenersForElement(div, self, connector);
- }
- };
- };
-
- // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it.
- jsPlumb.Overlays.GuideLines = function() {
- var self = this;
- self.length = 50;
- self.lineWidth = 5;
- this.type = "GuideLines";
- AbstractOverlay.apply(this, arguments);
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) {
-
- var head = connector.pointAlongPathFrom(self.loc, self.length / 2),
- mid = connector.pointOnPath(self.loc),
- tail = jsPlumb.util.pointOnLine(head, mid, self.length),
- tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40),
- headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20);
-
- self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions);
-
- return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)];
- };
-
- this.computeMaxSize = function() { return 50; };
-
- this.cleanup = function() { }; // nothing to clean up for GuideLines
- };
-
- // ********************************* END OF OVERLAY DEFINITIONS ***********************************************************************
-
- // ********************************* OVERLAY CANVAS RENDERERS***********************************************************************
-
- // ********************************* END OF OVERLAY CANVAS RENDERERS ***********************************************************************
-})();/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.8
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the state machine connectors.
- *
- * Thanks to Brainstorm Mobile Solutions for supporting the development of these.
- *
- * 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() {
-
- var Line = function(x1, y1, x2, y2) {
-
- this.m = (y2 - y1) / (x2 - x1);
- this.b = -1 * ((this.m * x1) - y1);
-
- this.rectIntersect = function(x,y,w,h) {
- var results = [];
-
- // try top face
- // the equation of the top face is y = (0 * x) + b; y = b.
- var xInt = (y - this.b) / this.m;
- // test that the X value is in the line's range.
- if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
-
- // try right face
- var yInt = (this.m * (x + w)) + this.b;
- if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
-
- // bottom face
- var xInt = ((y + h) - this.b) / this.m;
- // test that the X value is in the line's range.
- if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
-
- // try left face
- var yInt = (this.m * x) + this.b;
- if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
-
- if (results.length == 2) {
- var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2;
- results.push([ midx,midy ]);
- // now calculate the segment inside the rectangle where the midpoint lies.
- var xseg = midx <= x + (w / 2) ? -1 : 1,
- yseg = midy <= y + (h / 2) ? -1 : 1;
- results.push([xseg, yseg]);
- return results;
- }
-
- return null;
-
- };
- },
- _segment = function(x1, y1, x2, y2) {
- if (x1 <= x2 && y2 <= y1) return 1;
- else if (x1 <= x2 && y1 <= y2) return 2;
- else if (x2 <= x1 && y2 >= y1) return 3;
- return 4;
- },
-
- // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the
- // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they
- // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the
- // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and
- // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element
- // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left.
- //
- // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are:
- //
- // 0 - absolute x
- // 1 - absolute y
- // 2 - proportional x in element (0 is left edge, 1 is right edge)
- // 3 - proportional y in element (0 is top edge, 1 is bottom edge)
- //
- _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) {
-
- // TODO (maybe)
- // - if anchor pos is 0.5, make the control point take into account the relative position of the elements.
- if (distance <= proximityLimit) return [midx, midy];
-
- if (segment == 1) {
- if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
- else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
- else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
- }
- else if (segment == 2) {
- if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
- else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
- else return [ midx + (1 * dx) , midy + (-1 * dy) ];
- }
- else if (segment == 3) {
- if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
- else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
- else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
- }
- else if (segment == 4) {
- if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
- else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
- else return [ midx + (1 * dx) , midy + (-1 * dy) ];
- }
- };
-
- /*
- Function: StateMachine constructor
-
- Allowed parameters:
- curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the
- Bezier curve's control point is from the midpoint of the straight line connecting the two
- endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches
- its control points; they act as gravitational masses. defaults to 10.
- margin - distance from element to start and end connectors, in pixels. defaults to 5.
- proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy
- curves. by default this is 80 pixels.
- loopbackRadius - the radius of a loopback connector. optional; defaults to 25.
- */
- jsPlumb.Connectors.StateMachine = function(params) {
- var self = this,
- currentPoints = null,
- _sx, _sy, _tx, _ty, _controlPoint = [],
- curviness = params.curviness || 10,
- margin = params.margin || 5,
- proximityLimit = params.proximityLimit || 80,
- clockwise = params.orientation && params.orientation == "clockwise",
- loopbackRadius = params.loopbackRadius || 25,
- isLoopback = false;
-
- this.type = "StateMachine";
- params = params || {};
-
- this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) {
-
- var w = Math.abs(sourcePos[0] - targetPos[0]),
- h = Math.abs(sourcePos[1] - targetPos[1]),
- // these are padding to ensure the whole connector line appears
- xo = 0.45 * w, yo = 0.45 * h;
- // these are padding to ensure the whole connector line appears
- w *= 1.9; h *= 1.9;
- //ensure at least one pixel width
- lineWidth = lineWidth || 1;
- var x = Math.min(sourcePos[0], targetPos[0]) - xo,
- y = Math.min(sourcePos[1], targetPos[1]) - yo;
-
- if (sourceEndpoint.elementId != targetEndpoint.elementId) {
-
- isLoopback = false;
-
- _sx = sourcePos[0] < targetPos[0] ? xo : w-xo;
- _sy = sourcePos[1] < targetPos[1] ? yo:h-yo;
- _tx = sourcePos[0] < targetPos[0] ? w-xo : xo;
- _ty = sourcePos[1] < targetPos[1] ? h-yo : yo;
-
- // now adjust for the margin
- if (sourcePos[2] == 0) _sx -= margin;
- if (sourcePos[2] == 1) _sx += margin;
- if (sourcePos[3] == 0) _sy -= margin;
- if (sourcePos[3] == 1) _sy += margin;
- if (targetPos[2] == 0) _tx -= margin;
- if (targetPos[2] == 1) _tx += margin;
- if (targetPos[3] == 0) _ty -= margin;
- if (targetPos[3] == 1) _ty += margin;
-
- //
- // these connectors are quadratic bezier curves, having a single control point. if both anchors
- // are located at 0.5 on their respective faces, the control point is set to the midpoint and you
- // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since
- // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned
- // at 'curviness' pixels away along the normal to the straight line connecting the two anchors.
- //
- // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes
- // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node,
- // for example, we might increase the distance the control point is away from the midpoint in a bid to
- // steer it around that node. this will work within limits, but i think those limits would also be the likely
- // limits for, once again, aesthetic good sense in the layout of a chart using these connectors.
- //
- // the second possible change is actually two possible changes: firstly, it is possible we should gradually
- // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some
- // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors
- // with respect to how far their anchor is from the center of its respective face. this could either look cool,
- // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time.
- //
-
- var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2,
- m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2),
- dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)),
- dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)),
- segment = _segment(_sx, _sy, _tx, _ty),
- distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2));
-
- // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it
- // will work by extending the control point to force the curve to be, um, curvier.
- _controlPoint = _findControlPoint(_midx,
- _midy,
- segment,
- sourcePos,
- targetPos,
- curviness, curviness,
- distance,
- proximityLimit);
-
-
- var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth),
- requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth);
-
- if (w < requiredWidth) {
- var dw = requiredWidth - w;
- x -= (dw / 2);
- _sx += (dw / 2);
- _tx += (dw / 2);
- w = requiredWidth;
- _controlPoint[0] += (dw / 2);
- }
-
- if (h < requiredHeight) {
- var dh = requiredHeight - h;
- y -= (dh / 2);
- _sy += (dh / 2);
- _ty += (dh / 2);
- h = requiredHeight;
- _controlPoint[1] += (dh / 2);
- }
- currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ];
- }
- else {
- isLoopback = true;
- // a loopback connector. draw an arc from one anchor to the other.
- // i guess we'll do this the same way as the others. just the control point will be a fair distance away.
- var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin,
- cx = x1, cy = y1 - loopbackRadius;
-
- // canvas sizing stuff, to ensure the whole painted area is visible.
- w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius));
- x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius;
- currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y];
- }
-
- return currentPoints;
- };
-
- var _makeCurve = function() {
- return [
- { x:_tx, y:_ty },
- { x:_controlPoint[0], y:_controlPoint[1] },
- { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1},
- { x:_sx, y:_sy }
- ];
- };
-
- /**
- * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
- * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
- */
- this.pointOnPath = function(location) {
- if (isLoopback) {
-
- if (location > 0 && location < 1) location = 1- location;
-
-// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ]
- // so the path length is the circumference of the circle
- //var len = 2 * Math.PI * currentPoints[6],
- // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we
- // support other faces it will have to be calculated for each one. 1 is also PI/2.
- // 0.5 is -PI/2.
- var startAngle = (location * 2 * Math.PI) + (Math.PI / 2),
- startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)),
- startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle));
-
- return {x:startX, y:startY};
-
- }
- else return jsBezier.pointOnCurve(_makeCurve(), location);
- };
-
- /**
- * returns the gradient of the connector at the given point.
- */
- this.gradientAtPoint = function(location) {
- if (isLoopback)
- return Math.atan(location * 2 * Math.PI);
- else
- return jsBezier.gradientAtPoint(_makeCurve(), location);
- };
-
- /**
- * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult.
- * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous
- * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see.
- * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller
- * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to
- * calculate the step as a function of distance/distance between endpoints.
- */
- this.pointAlongPathFrom = function(location, distance) {
- if (isLoopback) {
-
- if (location > 0 && location < 1) location = 1- location;
-
- var circumference = 2 * Math.PI * currentPoints[6],
- arcSpan = distance / circumference * 2 * Math.PI,
- startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2),
-
- startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)),
- startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle));
-
- return {x:startX, y:startY};
- }
- return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance);
- };
-
- };
-
- /*
- * Canvas state machine renderer.
- */
- jsPlumb.Connectors.canvas.StateMachine = function(params) {
- params = params || {};
- var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector;
- jsPlumb.Connectors.StateMachine.apply(this, arguments);
- jsPlumb.CanvasConnector.apply(this, arguments);
-
-
- this._paint = function(dimensions) {
-
- if (dimensions.length == 10) {
- self.ctx.beginPath();
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]);
- self.ctx.stroke();
-
- /*/ draw the guideline
- if (drawGuideline) {
- self.ctx.save();
- self.ctx.beginPath();
- self.ctx.strokeStyle = "silver";
- self.ctx.lineWidth = 1;
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- self.ctx.lineTo(dimensions[6], dimensions[7]);
- self.ctx.stroke();
- self.ctx.restore();
- }
- //*/
- }
- else {
- // a loopback connector
- self.ctx.save();
- self.ctx.beginPath();
- var startAngle = 0, // Starting point on circle
- endAngle = 2 * Math.PI, // End point on circle
- clockwise = dimensions[7]; // clockwise or anticlockwise
- self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise);
- self.ctx.stroke();
- self.ctx.closePath();
- self.ctx.restore();
- }
- };
-
- this.createGradient = function(dim, ctx) {
- return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
- };
- };
-
- /*
- * SVG State Machine renderer
- */
- jsPlumb.Connectors.svg.StateMachine = function() {
- var self = this;
- jsPlumb.Connectors.StateMachine.apply(this, arguments);
- jsPlumb.SvgConnector.apply(this, arguments);
- this.getPath = function(d) {
-
- if (d.length == 10)
- return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7];
- else {
- // loopback
- return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9];
- }
- };
- };
-
- /*
- * VML state machine renderer
- */
- jsPlumb.Connectors.vml.StateMachine = function() {
- jsPlumb.Connectors.StateMachine.apply(this, arguments);
- jsPlumb.VmlConnector.apply(this, arguments);
- var _conv = jsPlumb.vml.convertValue;
- this.getPath = function(d) {
- if (d.length == 10) {
- return "m" + _conv(d[4]) + "," + _conv(d[5]) +
- " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e";
- }
- else {
- // loopback
- var left = _conv(d[8] - d[6]),
- top = _conv(d[9] - (2 * d[6])),
- right = left + _conv(2 * d[6]),
- bottom = top + _conv(2 * d[6]),
- posString = left + "," + top + "," + right + "," + bottom;
-
- var o = "ar " + posString + "," + _conv(d[8]) + ","
- + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e";
-
- return o;
- }
- };
- };
-
-})();
-
-/*
- // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way?
- // if (avoidSelector) {
- // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty);
- // var sel = jsPlumb.getSelector(avoidSelector);
- // for (var i = 0; i < sel.length; i++) {
- // var id = jsPlumb.getId(sel[i]);
- // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) {
- // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id);
-//
-// if (o && s) {
-// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]);
-// if (collision) {
- // set the control point to be a certain distance from the midpoint of the two points that
- // the line crosses on the rectangle.
- // TODO where will this 75 number come from?
- // _controlX = collision[2][0] + (75 * collision[3][0]);
- // / _controlY = collision[2][1] + (75 * collision[3][1]);
-// }
-// }
- // }
- // }
- //}
- *//*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.8
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the VML renderers.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
-
-;(function() {
-
- // http://ajaxian.com/archives/the-vml-changes-in-ie-8
- // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/
- // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
-
- var vmlAttributeMap = {
- "stroke-linejoin":"joinstyle",
- "joinstyle":"joinstyle",
- "endcap":"endcap",
- "miterlimit":"miterlimit"
- },
- jsPlumbStylesheet = null;
-
- if (document.createStyleSheet) {
-
- var ruleClasses = [
- ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect",
- "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group"
- ],
- rule = "behavior:url(#default#VML);position:absolute;";
-
- jsPlumbStylesheet = document.createStyleSheet();
-
- for (var i = 0; i < ruleClasses.length; i++)
- jsPlumbStylesheet.addRule(ruleClasses[i], rule);
-
- // in this page it is also mentioned that IE requires the extra arg to the namespace
- // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
- // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either.
- // var iev = document.documentMode;
- //if (!iev || iev < 8)
- document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml");
- //else
- // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML");
- }
-
- jsPlumb.vml = {};
-
- var scale = 1000,
-
- _groupMap = {},
- _getGroup = function(container, connectorClass) {
- var id = jsPlumb.getId(container),
- g = _groupMap[id];
- if(!g) {
- g = _node("group", [0,0,scale, scale], {"class":connectorClass});
- //g.style.position=absolute;
- //g["coordsize"] = "1000,1000";
- g.style.backgroundColor="red";
- _groupMap[id] = g;
- jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance.
- //document.body.appendChild(g);
- }
- return g;
- },
- _atts = function(o, atts) {
- for (var i in atts) {
- // IE8 fix: setattribute does not work after an element has been added to the dom!
- // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
- //o.setAttribute(i, atts[i]);
-
- /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following:
-
- if (document.documentMode==8) {
- ele.opacity=1;
- } else {
- ele.setAttribute(‘opacity’,1);
- }
- */
-
- o[i] = atts[i];
- }
- },
- _node = function(name, d, atts, parent, _jsPlumb) {
- atts = atts || {};
- var o = document.createElement("jsplumb:" + name);
- _jsPlumb.appendElement(o, parent);
- o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml";
- _pos(o, d);
- _atts(o, atts);
- return o;
- },
- _pos = function(o,d) {
- o.style.left = d[0] + "px";
- o.style.top = d[1] + "px";
- o.style.width= d[2] + "px";
- o.style.height= d[3] + "px";
- o.style.position = "absolute";
- },
- _conv = jsPlumb.vml.convertValue = function(v) {
- return Math.floor(v * scale);
- },
- // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so,
- // or 1 if not. TODO in the future, support variable opacity.
- _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) {
- if ("transparent" === styleToCheck)
- component.setOpacity(type, "0.0");
- else
- component.setOpacity(type, "1.0");
- },
- _applyStyles = function(node, style, component, _jsPlumb) {
- var styleToWrite = {};
- if (style.strokeStyle) {
- styleToWrite["stroked"] = "true";
- var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true);
- styleToWrite["strokecolor"] = strokeColor;
- _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component);
- styleToWrite["strokeweight"] = style.lineWidth + "px";
- }
- else styleToWrite["stroked"] = "false";
-
- if (style.fillStyle) {
- styleToWrite["filled"] = "true";
- var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true);
- styleToWrite["fillcolor"] = fillColor;
- _maybeSetOpacity(styleToWrite, fillColor, "fill", component);
- }
- else styleToWrite["filled"] = "false";
-
- if(style["dashstyle"]) {
- if (component.strokeNode == null) {
- component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb);
- }
- else
- component.strokeNode.dashstyle = style["dashstyle"];
- }
- else if (style["stroke-dasharray"] && style["lineWidth"]) {
- var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",",
- parts = style["stroke-dasharray"].split(sep),
- styleToUse = "";
- for(var i = 0; i < parts.length; i++) {
- styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep);
- }
- if (component.strokeNode == null) {
- component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb);
- //node.appendChild(component.strokeNode);
- }
- else
- component.strokeNode.dashstyle = styleToUse;
- }
-
- _atts(node, styleToWrite);
- },
- /*
- * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent.
- */
- VmlComponent = function() {
- var self = this;
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- this.opacityNodes = {
- "stroke":null,
- "fill":null
- };
- this.initOpacityNodes = function(vml) {
- self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb);
- self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb);
- };
- this.setOpacity = function(type, value) {
- var node = self.opacityNodes[type];
- if (node) node["opacity"] = "" + value;
- };
- var displayElements = [ ];
- this.getDisplayElements = function() {
- return displayElements;
- };
-
- this.appendDisplayElement = function(el, doNotAppendToCanvas) {
- if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el);
- displayElements.push(el);
- };
- },
- /*
- * Base class for Vml connectors. extends VmlComponent.
- */
- VmlConnector = jsPlumb.VmlConnector = function(params) {
- var self = this;
- self.strokeNode = null;
- self.canvas = null;
- VmlComponent.apply(this, arguments);
- var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : "");
- this.paint = function(d, style, anchor) {
- if (style != null) {
- var path = self.getPath(d), p = { "path":path };
-
- //*
- if (style.outlineColor) {
- var outlineWidth = style.outlineWidth || 1,
- outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
- outlineStyle = {
- strokeStyle : jsPlumb.util.convertStyle(style.outlineColor),
- lineWidth : outlineStrokeWidth
- };
- for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa];
-
- if (self.bgCanvas == null) {
- p["class"] = clazz;
- p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
- self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb);
- _pos(self.bgCanvas, d);
- self.appendDisplayElement(self.bgCanvas, true);
- self.attachListeners(self.bgCanvas, self);
- self.initOpacityNodes(self.bgCanvas, ["stroke"]);
- }
- else {
- p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
- _pos(self.bgCanvas, d);
- _atts(self.bgCanvas, p);
- }
-
- _applyStyles(self.bgCanvas, outlineStyle, self);
- }
- //*/
-
- if (self.canvas == null) {
- p["class"] = clazz;
- p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
- if (self.tooltip) p["label"] = self.tooltip;
- self.canvas = _node("shape", d, p, params.parent, self._jsPlumb);
- //var group = _getGroup(params.parent); // test of append everything to a group
- //group.appendChild(self.canvas); // sort of works but not exactly;
- //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups
-
- self.appendDisplayElement(self.canvas, true);
- self.attachListeners(self.canvas, self);
- self.initOpacityNodes(self.canvas, ["stroke"]);
- }
- else {
- p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
- _pos(self.canvas, d);
- _atts(self.canvas, p);
- }
-
- _applyStyles(self.canvas, style, self, self._jsPlumb);
- }
- };
-
- //self.appendDisplayElement(self.canvas);
-
- this.reattachListeners = function() {
- if (self.canvas) self.reattachListenersForElement(self.canvas, self);
- };
- },
- /*
- *
- * Base class for Vml Endpoints. extends VmlComponent.
- *
- */
- VmlEndpoint = function(params) {
- VmlComponent.apply(this, arguments);
- var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null;
- self.canvas = document.createElement("div");
- self.canvas.style["position"] = "absolute";
-
- var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : "");
-
- //var group = _getGroup(params.parent);
- //group.appendChild(self.canvas);
- params["_jsPlumb"].appendElement(self.canvas, params.parent);
-
- if (self.tooltip) self.canvas.setAttribute("label", self.tooltip);
-
- this.paint = function(d, style, anchor) {
- var p = { };
-
- jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
- if (vml == null) {
- p["class"] = clazz;
- vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb);
- self.attachListeners(vml, self);
-
- self.appendDisplayElement(vml, true);
- self.appendDisplayElement(self.canvas, true);
-
- self.initOpacityNodes(vml, ["fill"]);
- }
- else {
- _pos(vml, [0,0, d[2], d[3]]);
- _atts(vml, p);
- }
-
- _applyStyles(vml, style, self);
- };
-
- this.reattachListeners = function() {
- if (vml) self.reattachListenersForElement(vml, self);
- };
- };
-
- jsPlumb.Connectors.vml.Bezier = function() {
- jsPlumb.Connectors.Bezier.apply(this, arguments);
- VmlConnector.apply(this, arguments);
- this.getPath = function(d) {
- return "m" + _conv(d[4]) + "," + _conv(d[5]) +
- " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e";
- };
- };
-
- jsPlumb.Connectors.vml.Straight = function() {
- jsPlumb.Connectors.Straight.apply(this, arguments);
- VmlConnector.apply(this, arguments);
- this.getPath = function(d) {
- return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e";
- };
- };
-
- jsPlumb.Connectors.vml.Flowchart = function() {
- jsPlumb.Connectors.Flowchart.apply(this, arguments);
- VmlConnector.apply(this, arguments);
- this.getPath = function(dimensions) {
- var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l";
- // loop through extra points
- for (var i = 0; i < dimensions[8]; i++) {
- p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]);
- }
- // finally draw a line to the end
- p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e";
- return p;
- };
- };
-
- jsPlumb.Endpoints.vml.Dot = function() {
- jsPlumb.Endpoints.Dot.apply(this, arguments);
- VmlEndpoint.apply(this, arguments);
- this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); };
- };
-
- jsPlumb.Endpoints.vml.Rectangle = function() {
- jsPlumb.Endpoints.Rectangle.apply(this, arguments);
- VmlEndpoint.apply(this, arguments);
- this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); };
- };
-
- /*
- * VML Image Endpoint is the same as the default image endpoint.
- */
- jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image;
-
- /**
- * placeholder for Blank endpoint in vml renderer.
- */
- jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank;
-
- /**
- * VML Label renderer. uses the default label renderer (which adds an element to the DOM)
- */
- jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label;
-
- var AbstractVmlArrowOverlay = function(superclass, originalArgs) {
- superclass.apply(this, originalArgs);
- VmlComponent.apply(this, arguments);
- var self = this, path = null;
- self.canvas = null;
- var getPath = function(d, connectorDimensions) {
- return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) +
- " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) +
- " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) +
- " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) +
- " x e";
- };
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) {
- var p = {};
- if (strokeStyle) {
- p["stroked"] = "true";
- p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true);
- }
- if (lineWidth) p["strokeweight"] = lineWidth + "px";
- if (fillStyle) {
- p["filled"] = "true";
- p["fillcolor"] = fillStyle;
- }
- var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
- ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
- xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
- ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
- w = Math.abs(xmax - xmin),
- h = Math.abs(ymax - ymin),
- dim = [xmin, ymin, w, h];
-
- // for VML, we create overlays using shapes that have the same dimensions and
- // coordsize as their connector - overlays calculate themselves relative to the
- // connector (it's how it's been done since the original canvas implementation, because
- // for canvas that makes sense).
- p["path"] = getPath(d, connectorDimensions);
- p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale);
-
- dim[0] = connectorDimensions[0];
- dim[1] = connectorDimensions[1];
- dim[2] = connectorDimensions[2];
- dim[3] = connectorDimensions[3];
-
- if (self.canvas == null) {
- //p["class"] = jsPlumb.overlayClass; // TODO currentInstance?
- self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb);
- connector.appendDisplayElement(self.canvas, true);
- self.attachListeners(self.canvas, connector);
- }
- else {
- _pos(self.canvas, dim);
- _atts(self.canvas, p);
- }
- };
-
- this.reattachListeners = function() {
- if (self.canvas) self.reattachListenersForElement(self.canvas, self);
- };
- };
-
- jsPlumb.Overlays.vml.Arrow = function() {
- AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
- };
-
- jsPlumb.Overlays.vml.PlainArrow = function() {
- AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
- };
-
- jsPlumb.Overlays.vml.Diamond = function() {
- AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
- };
-})();/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.8
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the SVG renderers.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
-
-/**
- * SVG support for jsPlumb.
- *
- * things to investigate:
- *
- * gradients: https://developer.mozilla.org/en/svg_in_html_introduction
- * css:http://tutorials.jenkov.com/svg/svg-and-css.html
- * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath
- * pointer events: https://developer.mozilla.org/en/css/pointer-events
- *
- * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events
- *
- */
-;(function() {
-
- var svgAttributeMap = {
- "joinstyle":"stroke-linejoin",
- "stroke-linejoin":"stroke-linejoin",
- "stroke-dashoffset":"stroke-dashoffset",
- "stroke-linecap":"stroke-linecap"
- },
- STROKE_DASHARRAY = "stroke-dasharray",
- DASHSTYLE = "dashstyle",
- LINEAR_GRADIENT = "linearGradient",
- RADIAL_GRADIENT = "radialGradient",
- FILL = "fill",
- STOP = "stop",
- STROKE = "stroke",
- STROKE_WIDTH = "stroke-width",
- STYLE = "style",
- NONE = "none",
- JSPLUMB_GRADIENT = "jsplumb_gradient_",
- LINE_WIDTH = "lineWidth",
- ns = {
- svg:"http://www.w3.org/2000/svg",
- xhtml:"http://www.w3.org/1999/xhtml"
- },
- _attr = function(node, attributes) {
- for (var i in attributes)
- node.setAttribute(i, "" + attributes[i]);
- },
- _node = function(name, attributes) {
- var n = document.createElementNS(ns.svg, name);
- attributes = attributes || {};
- attributes["version"] = "1.1";
- attributes["xmlns"] = ns.xhtml;
- _attr(n, attributes);
- return n;
- },
- _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; },
- _clearGradient = function(parent) {
- for (var i = 0; i < parent.childNodes.length; i++) {
- if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT)
- parent.removeChild(parent.childNodes[i]);
- }
- },
- _updateGradient = function(parent, node, style, dimensions, uiComponent) {
- var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp();
- // first clear out any existing gradient
- _clearGradient(parent);
- // this checks for an 'offset' property in the gradient, and in the absence of it, assumes
- // we want a linear gradient. if it's there, we create a radial gradient.
- // it is possible that a more explicit means of defining the gradient type would be
- // better. relying on 'offset' means that we can never have a radial gradient that uses
- // some default offset, for instance.
- if (!style.gradient.offset) {
- var g = _node(LINEAR_GRADIENT, {id:id});
- parent.appendChild(g);
- }
- else {
- var g = _node(RADIAL_GRADIENT, {
- id:id
- });
- parent.appendChild(g);
- }
-
- // the svg radial gradient seems to treat stops in the reverse
- // order to how canvas does it. so we want to keep all the maths the same, but
- // iterate the actual style declarations in reverse order, if the x indexes are not in order.
- for (var i = 0; i < style.gradient.stops.length; i++) {
- // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of
- // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments.
- // so, not too concerned about leaving this in for now.
- var styleToUse = i;
- if (dimensions.length == 8)
- styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i;
- else
- styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i;
- var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true);
- var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor});
- g.appendChild(s);
- }
- var applyGradientTo = style.strokeStyle ? STROKE : FILL;
- node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")");
- },
- _applyStyles = function(parent, node, style, dimensions, uiComponent) {
-
- if (style.gradient) {
- _updateGradient(parent, node, style, dimensions, uiComponent);
- }
- else {
- // make sure we clear any existing gradient
- _clearGradient(parent);
- node.setAttribute(STYLE, "");
- }
-
- node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE);
- node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE);
- if (style.lineWidth) {
- node.setAttribute(STROKE_WIDTH, style.lineWidth);
- }
-
- // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like
- // the syntax in VML but is actually kind of nasty: values are given in the pixel
- // coordinate space, whereas in VML they are multiples of the width of the stroked
- // line, which makes a lot more sense. for that reason, jsPlumb is supporting both
- // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from
- // VML, which will be the preferred method. the code below this converts a dashstyle
- // attribute given in terms of stroke width into a pixel representation, by using the
- // stroke's lineWidth.
- if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) {
- var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",",
- parts = style[DASHSTYLE].split(sep),
- styleToUse = "";
- parts.forEach(function(p) {
- styleToUse += (Math.floor(p * style.lineWidth) + sep);
- });
- node.setAttribute(STROKE_DASHARRAY, styleToUse);
- }
- else if(style[STROKE_DASHARRAY]) {
- node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]);
- }
-
- // extra attributes such as join type, dash offset.
- for (var i in svgAttributeMap) {
- if (style[i]) {
- node.setAttribute(svgAttributeMap[i], style[i]);
- }
- }
- },
- _decodeFont = function(f) {
- var r = /([0-9].)(p[xt])\s(.*)/;
- var bits = f.match(r);
- return {size:bits[1] + bits[2], font:bits[3]};
- },
- _classManip = function(el, add, clazz) {
- var classesToAddOrRemove = clazz.split(" "),
- className = el.className,
- curClasses = className.baseVal.split(" ");
-
- for (var i = 0; i < classesToAddOrRemove.length; i++) {
- if (add) {
- if (curClasses.indexOf(classesToAddOrRemove[i]) == -1)
- curClasses.push(classesToAddOrRemove[i]);
- }
- else {
- var idx = curClasses.indexOf(classesToAddOrRemove[i]);
- if (idx != -1)
- curClasses.splice(idx, 1);
- }
- }
-
- el.className.baseVal = curClasses.join(" ");
- },
- _addClass = function(el, clazz) {
- _classManip(el, true, clazz);
- },
- _removeClass = function(el, clazz) {
- _classManip(el, false, clazz);
- };
-
- /**
- utility methods for other objects to use.
- */
- jsPlumb.util.svg = {
- addClass:_addClass,
- removeClass:_removeClass
- };
-
- /*
- * Base class for SVG components.
- */
- //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) {
- var SvgComponent = function(params) {
- var self = this,
- pointerEventsSpec = params.pointerEventsSpec || "all";
- jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs);
- self.canvas = null, self.path = null, self.svg = null;
-
- var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""),
- svgParams = {
- "style":"",
- "width":0,
- "height":0,
- "pointer-events":pointerEventsSpec,
- "position":"absolute"
- };
- if (self.tooltip) svgParams["title"] = self.tooltip;
- self.svg = _node("svg", svgParams);
- if (params.useDivWrapper) {
- self.canvas = document.createElement("div");
- self.canvas.style["position"] = "absolute";
- jsPlumb.sizeCanvas(self.canvas,0,0,1,1);
- self.canvas.className = clazz;
- if (self.tooltip) self.canvas.setAttribute("title", self.tooltip);
- }
- else {
- _attr(self.svg, { "class":clazz });
- self.canvas = self.svg;
- }
-
- params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]);
- if (params.useDivWrapper) self.canvas.appendChild(self.svg);
-
- // TODO this displayElement stuff is common between all components, across all
- // renderers. would be best moved to jsPlumbUIComponent.
- var displayElements = [ self.canvas ];
- this.getDisplayElements = function() {
- return displayElements;
- };
-
- this.appendDisplayElement = function(el) {
- displayElements.push(el);
- };
-
- this.paint = function(d, style, anchor) {
- if (style != null) {
- var x = d[0], y = d[1];
- if (params.useDivWrapper) {
- jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
- x = 0, y = 0;
- }
- _attr(self.svg, {
- "style":_pos([x, y, d[2], d[3]]),
- "width": d[2],
- "height": d[3]
- });
- self._paint.apply(this, arguments);
- }
- };
- };
-
- /*
- * Base class for SVG connectors.
- */
- var SvgConnector = jsPlumb.SvgConnector = function(params) {
- var self = this;
- SvgComponent.apply(this, [ {
- cssClass:params["_jsPlumb"].connectorClass,
- originalArgs:arguments,
- pointerEventsSpec:"none",
- tooltip:params.tooltip,
- _jsPlumb:params["_jsPlumb"]
- } ]);
- this._paint = function(d, style) {
- var p = self.getPath(d), a = { "d":p }, outlineStyle = null;
- a["pointer-events"] = "all";
-
- // outline style. actually means drawing an svg object underneath the main one.
- if (style.outlineColor) {
- var outlineWidth = style.outlineWidth || 1,
- outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
- outlineStyle = jsPlumb.CurrentLibrary.extend({}, style);
- outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor);
- outlineStyle.lineWidth = outlineStrokeWidth;
-
- if (self.bgPath == null) {
- self.bgPath = _node("path", a);
- self.svg.appendChild(self.bgPath);
- self.attachListeners(self.bgPath, self);
- }
- else {
- _attr(self.bgPath, a);
- }
-
- _applyStyles(self.svg, self.bgPath, outlineStyle, d, self);
- }
-
-
- // test - see below
- // a["clip-path"]= "url(#testClip)";
-
- if (self.path == null) {
- self.path = _node("path", a);
- self.svg.appendChild(self.path);
- self.attachListeners(self.path, self);
-
- /*
- this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection.
- you could do this by walking along the line, stepping along a little at a time, and setting the clip
- path to extend as far as that point.
-
- self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"});
- self.svg.appendChild(self.clip);
- self.clip.appendChild(_node("rect", {
- x:"0",y:"0",width:"0.5",height:"1"
- }));
- */
- }
- else {
- _attr(self.path, a);
- }
-
- _applyStyles(self.svg, self.path, style, d, self);
- };
-
- this.reattachListeners = function() {
- if (self.bgPath) self.reattachListenersForElement(self.bgPath, self);
- if (self.path) self.reattachListenersForElement(self.path, self);
- };
-
- };
-
- /*
- * SVG Bezier Connector
- */
- jsPlumb.Connectors.svg.Bezier = function(params) {
- jsPlumb.Connectors.Bezier.apply(this, arguments);
- SvgConnector.apply(this, arguments);
- this.getPath = function(d) {
- var _p = "M " + d[4] + " " + d[5];
- _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]);
- return _p;
- };
- };
-
- /*
- * SVG straight line Connector
- */
- jsPlumb.Connectors.svg.Straight = function(params) {
- jsPlumb.Connectors.Straight.apply(this, arguments);
- SvgConnector.apply(this, arguments);
- this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; };
- };
-
- jsPlumb.Connectors.svg.Flowchart = function() {
- var self = this;
- jsPlumb.Connectors.Flowchart.apply(this, arguments);
- SvgConnector.apply(this, arguments);
- this.getPath = function(dimensions) {
- var p = "M " + dimensions[4] + "," + dimensions[5];
- // loop through extra points
- for (var i = 0; i < dimensions[8]; i++) {
- p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)];
- }
- // finally draw a line to the end
- p = p + " " + dimensions[6] + "," + dimensions[7];
- return p;
- };
- };
-
- /*
- * Base class for SVG endpoints.
- */
- var SvgEndpoint = function(params) {
- var self = this;
- SvgComponent.apply(this, [ {
- cssClass:params["_jsPlumb"].endpointClass,
- originalArgs:arguments,
- pointerEventsSpec:"all",
- useDivWrapper:true,
- _jsPlumb:params["_jsPlumb"]
- } ]);
- this._paint = function(d, style) {
- var s = jsPlumb.extend({}, style);
- if (s.outlineColor) {
- s.strokeWidth = s.outlineWidth;
- s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true);
- }
-
- if (self.node == null) {
- self.node = self.makeNode(d, s);
- self.svg.appendChild(self.node);
- self.attachListeners(self.node, self);
- }
- _applyStyles(self.svg, self.node, s, d, self);
- _pos(self.node, d);
- };
-
- this.reattachListeners = function() {
- if (self.node) self.reattachListenersForElement(self.node, self);
- };
- };
-
- /*
- * SVG Dot Endpoint
- */
- jsPlumb.Endpoints.svg.Dot = function() {
- jsPlumb.Endpoints.Dot.apply(this, arguments);
- SvgEndpoint.apply(this, arguments);
- this.makeNode = function(d, style) {
- return _node("circle", {
- "cx" : d[2] / 2,
- "cy" : d[3] / 2,
- "r" : d[2] / 2
- });
- };
- };
-
- /*
- * SVG Rectangle Endpoint
- */
- jsPlumb.Endpoints.svg.Rectangle = function() {
- jsPlumb.Endpoints.Rectangle.apply(this, arguments);
- SvgEndpoint.apply(this, arguments);
- this.makeNode = function(d, style) {
- return _node("rect", {
- "width":d[2],
- "height":d[3]
- });
- };
- };
-
- /*
- * SVG Image Endpoint is the default image endpoint.
- */
- jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image;
- /*
- * Blank endpoint in svg renderer is the default Blank endpoint.
- */
- jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank;
- /*
- * Label endpoint in svg renderer is the default Label endpoint.
- */
- jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label;
-
- var AbstractSvgArrowOverlay = function(superclass, originalArgs) {
- superclass.apply(this, originalArgs);
- jsPlumb.jsPlumbUIComponent.apply(this, originalArgs);
- this.isAppendedAtTopLevel = false;
- var self = this, path =null;
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
- if (path == null) {
- path = _node("path");
- connector.svg.appendChild(path);
- self.attachListeners(path, connector);
- self.attachListeners(path, self);
- }
- var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : "";
-
- _attr(path, {
- "d" : makePath(d),
- "class" : clazz,
- stroke : strokeStyle ? strokeStyle : null,
- fill : fillStyle ? fillStyle : null
- });
- };
- var makePath = function(d) {
- return "M" + d.hxy.x + "," + d.hxy.y +
- " L" + d.tail[0].x + "," + d.tail[0].y +
- " L" + d.cxy.x + "," + d.cxy.y +
- " L" + d.tail[1].x + "," + d.tail[1].y +
- " L" + d.hxy.x + "," + d.hxy.y;
- };
- this.reattachListeners = function() {
- if (path) self.reattachListenersForElement(path, self);
- };
- };
-
- jsPlumb.Overlays.svg.Arrow = function() {
- AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
- };
-
- jsPlumb.Overlays.svg.PlainArrow = function() {
- AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
- };
-
- jsPlumb.Overlays.svg.Diamond = function() {
- AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
- };
-
- // a test
- jsPlumb.Overlays.svg.GuideLines = function() {
- var path = null, self = this, path2 = null, p1_1, p1_2;
- jsPlumb.Overlays.GuideLines.apply(this, arguments);
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
- if (path == null) {
- path = _node("path");
- connector.svg.appendChild(path);
- self.attachListeners(path, connector);
- self.attachListeners(path, self);
-
- p1_1 = _node("path");
- connector.svg.appendChild(p1_1);
- self.attachListeners(p1_1, connector);
- self.attachListeners(p1_1, self);
-
- p1_2 = _node("path");
- connector.svg.appendChild(p1_2);
- self.attachListeners(p1_2, connector);
- self.attachListeners(p1_2, self);
-
- }
-
- _attr(path, {
- "d" : makePath(d[0], d[1]),
- stroke : "red",
- fill : null
- });
-
- _attr(p1_1, {
- "d" : makePath(d[2][0], d[2][1]),
- stroke : "blue",
- fill : null
- });
-
- _attr(p1_2, {
- "d" : makePath(d[3][0], d[3][1]),
- stroke : "green",
- fill : null
- });
- };
-
- var makePath = function(d1, d2) {
- return "M " + d1.x + "," + d1.y +
- " L" + d2.x + "," + d2.y;
- };
-
- };
-})();/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.8
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the HTML5 canvas renderers.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
-
-;(function() {
-
-// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS *******************************************************************
-
- // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too.
- var _connectionBeingDragged = null,
- _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); },
- _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
- _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); },
- _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); },
- _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); };
-
- /*
- * Class:CanvasMouseAdapter
- * Provides support for mouse events on canvases.
- */
- var CanvasMouseAdapter = function() {
- var self = this;
- self.overlayPlacements = [];
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- jsPlumb.EventGenerator.apply(this, arguments);
- /**
- * returns whether or not the given event is ojver a painted area of the canvas.
- */
- this._over = function(e) {
- var o = _getOffset(_getElementObject(self.canvas)),
- pageXY = _pageXY(e),
- x = pageXY[0] - o.left, y = pageXY[1] - o.top;
- if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) {
- // first check overlays
- for ( var i = 0; i < self.overlayPlacements.length; i++) {
- var p = self.overlayPlacements[i];
- if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y))
- return true;
- }
-
- // then the canvas
- var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1);
- return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0;
- }
- return false;
- };
-
- var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false,
- _nullSafeHasClass = function(el, clazz) {
- return el != null && _hasClass(el, clazz);
- };
- this.mousemove = function(e) {
- var pageXY = _pageXY(e), clientXY = _clientXY(e),
- ee = document.elementFromPoint(clientXY[0], clientXY[1]),
- eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay");
- var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector"));
- if (!_mouseover && _continue && self._over(e)) {
- _mouseover = true;
- self.fire("mouseenter", self, e);
- return true;
- }
- // TODO here there is a remote chance that the overlay the mouse moved onto
- // is actually not an overlay for the current component. a more thorough check would
- // be to ensure the overlay belonged to the current component.
- else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) {
- _mouseover = false;
- self.fire("mouseexit", self, e);
- }
- self.fire("mousemove", self, e);
- };
-
- this.click = function(e) {
- if (_mouseover && self._over(e) && !_mouseWasDown)
- self.fire("click", self, e);
- _mouseWasDown = false;
- };
-
- this.dblclick = function(e) {
- if (_mouseover && self._over(e) && !_mouseWasDown)
- self.fire("dblclick", self, e);
- _mouseWasDown = false;
- };
-
- this.mousedown = function(e) {
- if(self._over(e) && !_mouseDown) {
- _mouseDown = true;
- _posWhenMouseDown = _getOffset(_getElementObject(self.canvas));
- self.fire("mousedown", self, e);
- }
- };
-
- this.mouseup = function(e) {
- _mouseDown = false;
- self.fire("mouseup", self, e);
- };
-
- this.contextmenu = function(e) {
- if (_mouseover && self._over(e) && !_mouseWasDown)
- self.fire("contextmenu", self, e);
- _mouseWasDown = false;
- };
- };
-
- var _newCanvas = function(params) {
- var canvas = document.createElement("canvas");
- params["_jsPlumb"].appendElement(canvas, params.parent);
- canvas.style.position = "absolute";
- if (params["class"]) canvas.className = params["class"];
- // set an id. if no id on the element and if uuid was supplied it
- // will be used, otherwise we'll create one.
- params["_jsPlumb"].getId(canvas, params.uuid);
- if (params.tooltip) canvas.setAttribute("title", params.tooltip);
-
- return canvas;
- };
-
- var CanvasComponent = function(params) {
- CanvasMouseAdapter.apply(this, arguments);
-
- var displayElements = [ ];
- this.getDisplayElements = function() { return displayElements; };
- this.appendDisplayElement = function(el) { displayElements.push(el); };
- }
-
- /**
- * Class:CanvasConnector
- * Superclass for Canvas Connector renderers.
- */
- var CanvasConnector = jsPlumb.CanvasConnector = function(params) {
-
- CanvasComponent.apply(this, arguments);
-
- var _paintOneStyle = function(dim, aStyle) {
- self.ctx.save();
- jsPlumb.extend(self.ctx, aStyle);
- if (aStyle.gradient) {
- var g = self.createGradient(dim, self.ctx);
- for ( var i = 0; i < aStyle.gradient.stops.length; i++)
- g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]);
- self.ctx.strokeStyle = g;
- }
- self._paint(dim);
- self.ctx.restore();
- };
-
- var self = this,
- clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || "");
- self.canvas = _newCanvas({
- "class":clazz,
- _jsPlumb:self._jsPlumb,
- parent:params.parent,
- tooltip:params.tooltip
- });
- self.ctx = self.canvas.getContext("2d");
-
- self.appendDisplayElement(self.canvas);
-
- self.paint = function(dim, style) {
- if (style != null) {
- jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]);
- if (style.outlineColor != null) {
- var outlineWidth = style.outlineWidth || 1,
- outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
- outlineStyle = {
- strokeStyle:style.outlineColor,
- lineWidth:outlineStrokeWidth
- };
- _paintOneStyle(dim, outlineStyle);
- }
- _paintOneStyle(dim, style);
- }
- };
- };
-
- /**
- * Class:CanvasEndpoint
- * Superclass for Canvas Endpoint renderers.
- */
- var CanvasEndpoint = function(params) {
- var self = this;
- CanvasComponent.apply(this, arguments);
- var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""),
- canvasParams = {
- "class":clazz,
- _jsPlumb:self._jsPlumb,
- parent:params.parent,
- tooltip:self.tooltip
- };
- self.canvas = _newCanvas(canvasParams);
- self.ctx = self.canvas.getContext("2d");
-
- self.appendDisplayElement(self.canvas);
-
- this.paint = function(d, style, anchor) {
- jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
- if (style.outlineColor != null) {
- var outlineWidth = style.outlineWidth || 1,
- outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
- var outlineStyle = {
- strokeStyle:style.outlineColor,
- lineWidth:outlineStrokeWidth
- };
- }
-
- self._paint.apply(this, arguments);
- };
- };
-
- jsPlumb.Endpoints.canvas.Dot = function(params) {
- jsPlumb.Endpoints.Dot.apply(this, arguments);
- CanvasEndpoint.apply(this, arguments);
- var self = this,
- parseValue = function(value) {
- try { return parseInt(value); }
- catch(e) {
- if (value.substring(value.length - 1) == '%')
- return parseInt(value.substring(0, value - 1));
- }
- },
- calculateAdjustments = function(gradient) {
- var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius;
- gradient.offset && (offsetAdjustment = parseValue(gradient.offset));
- gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius));
- return [offsetAdjustment, innerRadius];
- };
- this._paint = function(d, style, anchor) {
- if (style != null) {
- var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self);
- jsPlumb.extend(ctx, style);
- if (style.gradient) {
- var adjustments = calculateAdjustments(style.gradient),
- yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0],
- xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0],
- g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]);
- for (var i = 0; i < style.gradient.stops.length; i++)
- g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
- ctx.fillStyle = g;
- }
- ctx.beginPath();
- ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true);
- ctx.closePath();
- if (style.fillStyle || style.gradient) ctx.fill();
- if (style.strokeStyle) ctx.stroke();
- }
- };
- };
-
- jsPlumb.Endpoints.canvas.Rectangle = function(params) {
-
- var self = this;
- jsPlumb.Endpoints.Rectangle.apply(this, arguments);
- CanvasEndpoint.apply(this, arguments);
-
- this._paint = function(d, style, anchor) {
-
- var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self);
- jsPlumb.extend(ctx, style);
-
- /* canvas gradient */
- if (style.gradient) {
- // first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
- var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0;
- var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0;
- var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0;
- var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0;
- var g = ctx.createLinearGradient(x1,y1,x2,y2);
- for (var i = 0; i < style.gradient.stops.length; i++)
- g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
- ctx.fillStyle = g;
- }
-
- ctx.beginPath();
- ctx.rect(0, 0, d[2], d[3]);
- ctx.closePath();
- if (style.fillStyle || style.gradient) ctx.fill();
- if (style.strokeStyle) ctx.stroke();
- };
- };
-
- jsPlumb.Endpoints.canvas.Triangle = function(params) {
-
- var self = this;
- jsPlumb.Endpoints.Triangle.apply(this, arguments);
- CanvasEndpoint.apply(this, arguments);
-
- this._paint = function(d, style, anchor)
- {
- var width = d[2], height = d[3], x = d[0], y = d[1],
- ctx = self.canvas.getContext('2d'),
- offsetX = 0, offsetY = 0, angle = 0,
- orientation = anchor.getOrientation(self);
-
- if( orientation[0] == 1 ) {
- offsetX = width;
- offsetY = height;
- angle = 180;
- }
- if( orientation[1] == -1 ) {
- offsetX = width;
- angle = 90;
- }
- if( orientation[1] == 1 ) {
- offsetY = height;
- angle = -90;
- }
-
- ctx.fillStyle = style.fillStyle;
-
- ctx.translate(offsetX, offsetY);
- ctx.rotate(angle * Math.PI/180);
-
- ctx.beginPath();
- ctx.moveTo(0, 0);
- ctx.lineTo(width/2, height/2);
- ctx.lineTo(0, height);
- ctx.closePath();
- if (style.fillStyle || style.gradient) ctx.fill();
- if (style.strokeStyle) ctx.stroke();
- };
- };
-
- /*
- * Canvas Image Endpoint: uses the default version, which creates an tag.
- */
- jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image;
-
- /*
- * Blank endpoint in all renderers is just the default Blank endpoint.
- */
- jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank;
-
- /*
- * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element.
- */
- jsPlumb.Connectors.canvas.Bezier = function() {
- var self = this;
- jsPlumb.Connectors.Bezier.apply(this, arguments);
- CanvasConnector.apply(this, arguments);
- this._paint = function(dimensions) {
- self.ctx.beginPath();
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]);
- self.ctx.stroke();
- };
-
- // TODO i doubt this handles the case that source and target are swapped.
- this.createGradient = function(dim, ctx, swap) {
- return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
- };
- };
-
- /*
- * Canvas straight line Connector. Draws a straight line onto a Canvas element.
- */
- jsPlumb.Connectors.canvas.Straight = function() {
- var self = this;
- jsPlumb.Connectors.Straight.apply(this, arguments);
- CanvasConnector.apply(this, arguments);
- this._paint = function(dimensions) {
- self.ctx.beginPath();
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- self.ctx.lineTo(dimensions[6], dimensions[7]);
- self.ctx.stroke();
- };
-
- // TODO this does not handle the case that src and target are swapped.
- this.createGradient = function(dim, ctx) {
- return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
- };
- };
-
- jsPlumb.Connectors.canvas.Flowchart = function() {
- var self = this;
- jsPlumb.Connectors.Flowchart.apply(this, arguments);
- CanvasConnector.apply(this, arguments);
- this._paint = function(dimensions) {
- self.ctx.beginPath();
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- // loop through extra points
- for (var i = 0; i < dimensions[8]; i++) {
- self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]);
- }
- // finally draw a line to the end
- self.ctx.lineTo(dimensions[6], dimensions[7]);
- self.ctx.stroke();
- };
-
- this.createGradient = function(dim, ctx) {
- return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
- };
- };
-
-// ********************************* END OF CANVAS RENDERERS *******************************************************************
-
- jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label;
-
- /**
- * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this.
- */
- var CanvasOverlay = function() {
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- };
-
- var AbstractCanvasArrowOverlay = function(superclass, originalArgs) {
- superclass.apply(this, originalArgs);
- CanvasOverlay.apply(this, arguments);
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
- var ctx = connector.ctx;
-
- ctx.lineWidth = lineWidth;
- ctx.beginPath();
- ctx.moveTo(d.hxy.x, d.hxy.y);
- ctx.lineTo(d.tail[0].x, d.tail[0].y);
- ctx.lineTo(d.cxy.x, d.cxy.y);
- ctx.lineTo(d.tail[1].x, d.tail[1].y);
- ctx.lineTo(d.hxy.x, d.hxy.y);
- ctx.closePath();
-
- if (strokeStyle) {
- ctx.strokeStyle = strokeStyle;
- ctx.stroke();
- }
- if (fillStyle) {
- ctx.fillStyle = fillStyle;
- ctx.fill();
- }
- };
- };
-
- jsPlumb.Overlays.canvas.Arrow = function() {
- AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
- };
-
- jsPlumb.Overlays.canvas.PlainArrow = function() {
- AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
- };
-
- jsPlumb.Overlays.canvas.Diamond = function() {
- AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
- };
-})();/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.8
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the jQuery adapter.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
-/*
- * the library specific functions, such as find offset, get id, get attribute, extend etc.
- * the full list is:
- *
- * addClass adds a class to the given element
- * animate calls the underlying library's animate functionality
- * appendElement appends a child element to a parent element.
- * bind binds some event to an element
- * dragEvents a dictionary of event names
- * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally.
- * getAttribute gets some attribute from an element
- * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback
- * getDragScope gets the drag scope for a given element.
- * getDropScope gets the drop scope for a given element.
- * getElementObject turns an id or dom element into an element object of the underlying library's type.
- * getOffset gets an element's offset
- * getPageXY gets the page event's xy location.
- * getParent gets the parent of some element.
- * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be?
- * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be?
- * getSize gets an element's size.
- * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback.
- * hasClass returns whether or not the given element has the given class.
- * initDraggable initializes an element to be draggable
- * initDroppable initializes an element to be droppable
- * isDragSupported returns whether or not drag is supported for some element.
- * isDropSupported returns whether or not drop is supported for some element.
- * removeClass removes a class from a given element.
- * removeElement removes some element completely from the DOM.
- * setAttribute sets an attribute on some element.
- * setDraggable sets whether or not some element should be draggable.
- * setDragScope sets the drag scope for a given element.
- * setOffset sets the offset of some element.
- * trigger triggers some event on an element.
- * unbind unbinds some listener from some element.
- */
-(function($) {
-
- //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement;
-
- jsPlumb.CurrentLibrary = {
-
- /**
- * adds the given class to the element object.
- */
- addClass : function(el, clazz) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- try {
- if (el[0].className.constructor == SVGAnimatedString) {
- jsPlumb.util.svg.addClass(el[0], clazz);
- }
- }
- catch (e) {
- // SVGAnimatedString not supported; no problem.
- }
- el.addClass(clazz);
- },
-
- /**
- * animates the given element.
- */
- animate : function(el, properties, options) {
- el.animate(properties, options);
- },
-
- /**
- * appends the given child to the given parent.
- */
- appendElement : function(child, parent) {
- jsPlumb.CurrentLibrary.getElementObject(parent).append(child);
- },
-
- /**
- * executes ana ajax call.
- */
- ajax : function(params) {
- params = params || {};
- params.type = params.type || "get";
- $.ajax(params);
- },
-
- /**
- * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example,
- * uses 'on'.
- */
- bind : function(el, event, callback) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- el.bind(event, callback);
- },
-
- /**
- * mapping of drag events for jQuery
- */
- dragEvents : {
- 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step',
- 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete'
- },
-
- /**
- * wrapper around the library's 'extend' functionality (which it hopefully has.
- * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
- * instead. it's not like its hard.
- */
- extend : function(o1, o2) {
- return $.extend(o1, o2);
- },
-
- /**
- * gets the named attribute from the given element object.
- */
- getAttribute : function(el, attName) {
- return el.attr(attName);
- },
-
- getClientXY : function(eventObject) {
- return [eventObject.clientX, eventObject.clientY];
- },
-
- getDocumentElement : function() { return document; },
-
- /**
- * takes the args passed to an event function and returns you an object representing that which is being dragged.
- */
- getDragObject : function(eventArgs) {
- return eventArgs[1].draggable;
- },
-
- getDragScope : function(el) {
- return el.draggable("option", "scope");
- },
-
- getDropEvent : function(args) {
- return args[0];
- },
-
- getDropScope : function(el) {
- return el.droppable("option", "scope");
- },
-
- /**
- * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById),
- * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other
- * two cases). this is the opposite of getElementObject below.
- */
- getDOMElement : function(el) {
- if (typeof(el) == "string") return document.getElementById(el);
- else if (el.context) return el[0];
- else return el;
- },
-
- /**
- * gets an "element object" from the given input. this means an object that is used by the
- * underlying library on which jsPlumb is running. 'el' may already be one of these objects,
- * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup
- * function is used to find the element, using the given String as the element's id.
- *
- */
- getElementObject : function(el) {
- return typeof(el) == "string" ? $("#" + el) : $(el);
- },
-
- /**
- * gets the offset for the element object. this should return a js object like this:
- *
- * { left:xxx, top: xxx }
- */
- getOffset : function(el) {
- return el.offset();
- },
-
- getPageXY : function(eventObject) {
- return [eventObject.pageX, eventObject.pageY];
- },
-
- getParent : function(el) {
- return jsPlumb.CurrentLibrary.getElementObject(el).parent();
- },
-
- getScrollLeft : function(el) {
- return el.scrollLeft();
- },
-
- getScrollTop : function(el) {
- return el.scrollTop();
- },
-
- getSelector : function(spec) {
- return $(spec);
- },
-
- /**
- * gets the size for the element object, in an array : [ width, height ].
- */
- getSize : function(el) {
- return [el.outerWidth(), el.outerHeight()];
- },
-
- getTagName : function(el) {
- var e = jsPlumb.CurrentLibrary.getElementObject(el);
- return e.length > 0 ? e[0].tagName : null;
- },
-
- /**
- * takes the args passed to an event function and returns you an object that gives the
- * position of the object being moved, as a js object with the same params as the result of
- * getOffset, ie: { left: xxx, top: xxx }.
- *
- * different libraries have different signatures for their event callbacks.
- * see getDragObject as well
- */
- getUIPosition : function(eventArgs) {
-
- // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes
- // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect
- // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which
- // i don't like.
-
- /*if ( getBoundingClientRectSupported ) {
- var r = eventArgs[1].helper[0].getBoundingClientRect();
- return { left : r.left, top: r.top };
- } else {*/
- if (eventArgs.length == 1) {
- ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY };
- }
- else {
- var ui = eventArgs[1], _offset = ui.offset;
- ret = _offset || ui.absolutePosition;
- }
- return ret;
- },
-
- hasClass : function(el, clazz) {
- return el.hasClass(clazz);
- },
-
- /**
- * initialises the given element to be draggable.
- */
- initDraggable : function(el, options) {
- options = options || {};
- // remove helper directive if present.
- options.helper = null;
- options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
- el.draggable(options);
- },
-
- /**
- * initialises the given element to be droppable.
- */
- initDroppable : function(el, options) {
- options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
- el.droppable(options);
- },
-
- isAlreadyDraggable : function(el) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- return el.hasClass("ui-draggable");
- },
-
- /**
- * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element.
- */
- isDragSupported : function(el, options) {
- return el.draggable;
- },
-
- /**
- * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element.
- */
- isDropSupported : function(el, options) {
- return el.droppable;
- },
-
- /**
- * removes the given class from the element object.
- */
- removeClass : function(el, clazz) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- try {
- if (el[0].className.constructor == SVGAnimatedString) {
- jsPlumb.util.svg.removeClass(el[0], clazz);
- }
- }
- catch (e) {
- // SVGAnimatedString not supported; no problem.
- }
- el.removeClass(clazz);
- },
-
- removeElement : function(element, parent) {
- jsPlumb.CurrentLibrary.getElementObject(element).remove();
- },
-
- /**
- * sets the named attribute on the given element object.
- */
- setAttribute : function(el, attName, attValue) {
- el.attr(attName, attValue);
- },
-
- /**
- * sets the draggable state for the given element
- */
- setDraggable : function(el, draggable) {
- el.draggable("option", "disabled", !draggable);
- },
-
- /**
- * sets the drag scope. probably time for a setDragOption method (roll this and the one above together)
- * @param el
- * @param scope
- */
- setDragScope : function(el, scope) {
- el.draggable("option", "scope", scope);
- },
-
- setOffset : function(el, o) {
- jsPlumb.CurrentLibrary.getElementObject(el).offset(o);
- },
-
- /**
- * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself.
- * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff
- * from the originalEvent to put in an options object for YUI.
- * @param el
- * @param event
- * @param originalEvent
- */
- trigger : function(el, event, originalEvent) {
- //originalEvent.stopPropagation();
- //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent);
- var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle");
- h(originalEvent);
- //originalEvent.stopPropagation();
- },
-
- /**
- * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example,
- * uses..something else.
- */
- unbind : function(el, event, callback) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- el.unbind(event, callback);
- }
- };
-
- $(document).ready(jsPlumb.init);
-
-})(jQuery);
-
-(function(){if("undefined"==typeof Math.sgn)Math.sgn=function(a){return 0==a?0:0n?n=l:lb.location)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:r(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=t(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]}}})();
\ No newline at end of file
diff --git a/beautifulmind/mindmap/static/mindmap/js/lib/jquery.scrollTo-1.4.2.js b/beautifulmind/mindmap/static/mindmap/js/lib/jquery.scrollTo-1.4.2.js
deleted file mode 100644
index 753e62c..0000000
--- a/beautifulmind/mindmap/static/mindmap/js/lib/jquery.scrollTo-1.4.2.js
+++ /dev/null
@@ -1,215 +0,0 @@
-/**
- * jQuery.ScrollTo
- * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
- * Dual licensed under MIT and GPL.
- * Date: 5/25/2009
- *
- * @projectDescription Easy element scrolling using jQuery.
- * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
- * Works with jQuery +1.2.6. Tested on FF 2/3, IE 6/7/8, Opera 9.5/6, Safari 3, Chrome 1 on WinXP.
- *
- * @author Ariel Flesler
- * @version 1.4.2
- *
- * @id jQuery.scrollTo
- * @id jQuery.fn.scrollTo
- * @param {String, Number, DOMElement, jQuery, Object} target Where to scroll the matched elements.
- * The different options for target are:
- * - A number position (will be applied to all axes).
- * - A string position ('44', '100px', '+=90', etc ) will be applied to all axes
- * - A jQuery/DOM element ( logically, child of the element to scroll )
- * - A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc )
- * - A hash { top:x, left:y }, x and y can be any kind of number/string like above.
-* - A percentage of the container's dimension/s, for example: 50% to go to the middle.
- * - The string 'max' for go-to-end.
- * @param {Number} duration The OVERALL length of the animation, this argument can be the settings object instead.
- * @param {Object,Function} settings Optional set of settings or the onAfter callback.
- * @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'.
- * @option {Number} duration The OVERALL length of the animation.
- * @option {String} easing The easing method for the animation.
- * @option {Boolean} margin If true, the margin of the target element will be deducted from the final position.
- * @option {Object, Number} offset Add/deduct from the end position. One number for both axes or { top:x, left:y }.
- * @option {Object, Number} over Add/deduct the height/width multiplied by 'over', can be { top:x, left:y } when using both axes.
- * @option {Boolean} queue If true, and both axis are given, the 2nd axis will only be animated after the first one ends.
- * @option {Function} onAfter Function to be called after the scrolling ends.
- * @option {Function} onAfterFirst If queuing is activated, this function will be called after the first scrolling ends.
- * @return {jQuery} Returns the same jQuery object, for chaining.
- *
- * @desc Scroll to a fixed position
- * @example $('div').scrollTo( 340 );
- *
- * @desc Scroll relatively to the actual position
- * @example $('div').scrollTo( '+=340px', { axis:'y' } );
- *
- * @dec Scroll using a selector (relative to the scrolled element)
- * @example $('div').scrollTo( 'p.paragraph:eq(2)', 500, { easing:'swing', queue:true, axis:'xy' } );
- *
- * @ Scroll to a DOM element (same for jQuery object)
- * @example var second_child = document.getElementById('container').firstChild.nextSibling;
- * $('#container').scrollTo( second_child, { duration:500, axis:'x', onAfter:function(){
- * alert('scrolled!!');
- * }});
- *
- * @desc Scroll on both axes, to different values
- * @example $('div').scrollTo( { top: 300, left:'+=200' }, { axis:'xy', offset:-20 } );
- */
-;(function( $ ){
-
- var $scrollTo = $.scrollTo = function( target, duration, settings ){
- $(window).scrollTo( target, duration, settings );
- };
-
- $scrollTo.defaults = {
- axis:'xy',
- duration: parseFloat($.fn.jquery) >= 1.3 ? 0 : 1
- };
-
- // Returns the element that needs to be animated to scroll the window.
- // Kept for backwards compatibility (specially for localScroll & serialScroll)
- $scrollTo.window = function( scope ){
- return $(window)._scrollable();
- };
-
- // Hack, hack, hack :)
- // Returns the real elements to scroll (supports window/iframes, documents and regular nodes)
- $.fn._scrollable = function(){
- return this.map(function(){
- var elem = this,
- isWin = !elem.nodeName || $.inArray( elem.nodeName.toLowerCase(), ['iframe','#document','html','body'] ) != -1;
-
- if( !isWin )
- return elem;
-
- var doc = (elem.contentWindow || elem).document || elem.ownerDocument || elem;
-
- return $.browser.safari || doc.compatMode == 'BackCompat' ?
- doc.body :
- doc.documentElement;
- });
- };
-
- $.fn.scrollTo = function( target, duration, settings ){
- if( typeof duration == 'object' ){
- settings = duration;
- duration = 0;
- }
- if( typeof settings == 'function' )
- settings = { onAfter:settings };
-
- if( target == 'max' )
- target = 9e9;
-
- settings = $.extend( {}, $scrollTo.defaults, settings );
- // Speed is still recognized for backwards compatibility
- duration = duration || settings.speed || settings.duration;
- // Make sure the settings are given right
- settings.queue = settings.queue && settings.axis.length > 1;
-
- if( settings.queue )
- // Let's keep the overall duration
- duration /= 2;
- settings.offset = both( settings.offset );
- settings.over = both( settings.over );
-
- return this._scrollable().each(function(){
- var elem = this,
- $elem = $(elem),
- targ = target, toff, attr = {},
- win = $elem.is('html,body');
-
- switch( typeof targ ){
- // A number will pass the regex
- case 'number':
- case 'string':
- if( /^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(targ) ){
- targ = both( targ );
- // We are done
- break;
- }
- // Relative selector, no break!
- targ = $(targ,this);
- case 'object':
- // DOMElement / jQuery
- if( targ.is || targ.style )
- // Get the real position of the target
- toff = (targ = $(targ)).offset();
- }
- $.each( settings.axis.split(''), function( i, axis ){
- var Pos = axis == 'x' ? 'Left' : 'Top',
- pos = Pos.toLowerCase(),
- key = 'scroll' + Pos,
- old = elem[key],
- max = $scrollTo.max(elem, axis);
-
- if( toff ){// jQuery / DOMElement
- attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] );
-
- // If it's a dom element, reduce the margin
- if( settings.margin ){
- attr[key] -= parseInt(targ.css('margin'+Pos)) || 0;
- attr[key] -= parseInt(targ.css('border'+Pos+'Width')) || 0;
- }
-
- attr[key] += settings.offset[pos] || 0;
-
- if( settings.over[pos] )
- // Scroll to a fraction of its width/height
- attr[key] += targ[axis=='x'?'width':'height']() * settings.over[pos];
- }else{
- var val = targ[pos];
- // Handle percentage values
- attr[key] = val.slice && val.slice(-1) == '%' ?
- parseFloat(val) / 100 * max
- : val;
- }
-
- // Number or 'number'
- if( /^\d+$/.test(attr[key]) )
- // Check the limits
- attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max );
-
- // Queueing axes
- if( !i && settings.queue ){
- // Don't waste time animating, if there's no need.
- if( old != attr[key] )
- // Intermediate animation
- animate( settings.onAfterFirst );
- // Don't animate this axis again in the next iteration.
- delete attr[key];
- }
- });
-
- animate( settings.onAfter );
-
- function animate( callback ){
- $elem.animate( attr, duration, settings.easing, callback && function(){
- callback.call(this, target, settings);
- });
- };
-
- }).end();
- };
-
- // Max scrolling position, works on quirks mode
- // It only fails (not too badly) on IE, quirks mode.
- $scrollTo.max = function( elem, axis ){
- var Dim = axis == 'x' ? 'Width' : 'Height',
- scroll = 'scroll'+Dim;
-
- if( !$(elem).is('html,body') )
- return elem[scroll] - $(elem)[Dim.toLowerCase()]();
-
- var size = 'client' + Dim,
- html = elem.ownerDocument.documentElement,
- body = elem.ownerDocument.body;
-
- return Math.max( html[scroll], body[scroll] )
- - Math.min( html[size] , body[size] );
-
- };
-
- function both( val ){
- return typeof val == 'object' ? val : { top:val, left:val };
- };
-
-})( jQuery );
\ No newline at end of file
diff --git a/beautifulmind/mindmap/static/mindmap/js/lib/sockjs-0.3.1.js b/beautifulmind/mindmap/static/mindmap/js/lib/sockjs-0.3.1.js
deleted file mode 100644
index ef0043a..0000000
--- a/beautifulmind/mindmap/static/mindmap/js/lib/sockjs-0.3.1.js
+++ /dev/null
@@ -1,2314 +0,0 @@
-/* SockJS client, version 0.3.1, http://sockjs.org, MIT License
-
-Copyright (c) 2011-2012 VMware, Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-*/
-
-// JSON2 by Douglas Crockford (minified).
-var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c 1) {
- this._listeners[eventType] = arr.slice(0, idx).concat( arr.slice(idx+1) );
- } else {
- delete this._listeners[eventType];
- }
- return;
- }
- return;
-};
-
-REventTarget.prototype.dispatchEvent = function (event) {
- var t = event.type;
- var args = Array.prototype.slice.call(arguments, 0);
- if (this['on'+t]) {
- this['on'+t].apply(this, args);
- }
- if (this._listeners && t in this._listeners) {
- for(var i=0; i < this._listeners[t].length; i++) {
- this._listeners[t][i].apply(this, args);
- }
- }
-};
-// [*] End of lib/reventtarget.js
-
-
-// [*] Including lib/simpleevent.js
-/*
- * ***** BEGIN LICENSE BLOCK *****
- * Copyright (c) 2011-2012 VMware, Inc.
- *
- * For the license see COPYING.
- * ***** END LICENSE BLOCK *****
- */
-
-var SimpleEvent = function(type, obj) {
- this.type = type;
- if (typeof obj !== 'undefined') {
- for(var k in obj) {
- if (!obj.hasOwnProperty(k)) continue;
- this[k] = obj[k];
- }
- }
-};
-
-SimpleEvent.prototype.toString = function() {
- var r = [];
- for(var k in this) {
- if (!this.hasOwnProperty(k)) continue;
- var v = this[k];
- if (typeof v === 'function') v = '[function]';
- r.push(k + '=' + v);
- }
- return 'SimpleEvent(' + r.join(', ') + ')';
-};
-// [*] End of lib/simpleevent.js
-
-
-// [*] Including lib/eventemitter.js
-/*
- * ***** BEGIN LICENSE BLOCK *****
- * Copyright (c) 2011-2012 VMware, Inc.
- *
- * For the license see COPYING.
- * ***** END LICENSE BLOCK *****
- */
-
-var EventEmitter = function(events) {
- this.events = events || [];
-};
-EventEmitter.prototype.emit = function(type) {
- var that = this;
- var args = Array.prototype.slice.call(arguments, 1);
- if (!that.nuked && that['on'+type]) {
- that['on'+type].apply(that, args);
- }
- if (utils.arrIndexOf(that.events, type) === -1) {
- utils.log('Event ' + JSON.stringify(type) +
- ' not listed ' + JSON.stringify(that.events) +
- ' in ' + that);
- }
-};
-
-EventEmitter.prototype.nuke = function(type) {
- var that = this;
- that.nuked = true;
- for(var i=0; i= 3000 && code <= 4999);
-};
-
-// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/
-// and RFC 2988.
-utils.countRTO = function (rtt) {
- var rto;
- if (rtt > 100) {
- rto = 3 * rtt; // rto > 300msec
- } else {
- rto = rtt + 200; // 200msec < rto <= 300msec
- }
- return rto;
-}
-
-utils.log = function() {
- if (_window.console && console.log && console.log.apply) {
- console.log.apply(console, arguments);
- }
-};
-
-utils.bind = function(fun, that) {
- if (fun.bind) {
- return fun.bind(that);
- } else {
- return function() {
- return fun.apply(that, arguments);
- };
- }
-};
-
-utils.flatUrl = function(url) {
- return url.indexOf('?') === -1 && url.indexOf('#') === -1;
-};
-
-utils.amendUrl = function(url) {
- var dl = _document.location;
- if (!url) {
- throw new Error('Wrong url for SockJS');
- }
- if (!utils.flatUrl(url)) {
- throw new Error('Only basic urls are supported in SockJS');
- }
-
- // '//abc' --> 'http://abc'
- if (url.indexOf('//') === 0) {
- url = dl.protocol + url;
- }
- // '/abc' --> 'http://localhost:80/abc'
- if (url.indexOf('/') === 0) {
- url = dl.protocol + '//' + dl.host + url;
- }
- // strip trailing slashes
- url = url.replace(/[/]+$/,'');
- return url;
-};
-
-// IE doesn't support [].indexOf.
-utils.arrIndexOf = function(arr, obj){
- for(var i=0; i < arr.length; i++){
- if(arr[i] === obj){
- return i;
- }
- }
- return -1;
-};
-
-utils.arrSkip = function(arr, obj) {
- var idx = utils.arrIndexOf(arr, obj);
- if (idx === -1) {
- return arr.slice();
- } else {
- var dst = arr.slice(0, idx);
- return dst.concat(arr.slice(idx+1));
- }
-};
-
-// Via: https://gist.github.com/1133122/2121c601c5549155483f50be3da5305e83b8c5df
-utils.isArray = Array.isArray || function(value) {
- return {}.toString.call(value).indexOf('Array') >= 0
-};
-
-utils.delay = function(t, fun) {
- if(typeof t === 'function') {
- fun = t;
- t = 0;
- }
- return setTimeout(fun, t);
-};
-
-
-// Chars worth escaping, as defined by Douglas Crockford:
-// https://github.com/douglascrockford/JSON-js/blob/47a9882cddeb1e8529e07af9736218075372b8ac/json2.js#L196
-var json_escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- json_lookup = {
-"\u0000":"\\u0000","\u0001":"\\u0001","\u0002":"\\u0002","\u0003":"\\u0003",
-"\u0004":"\\u0004","\u0005":"\\u0005","\u0006":"\\u0006","\u0007":"\\u0007",
-"\b":"\\b","\t":"\\t","\n":"\\n","\u000b":"\\u000b","\f":"\\f","\r":"\\r",
-"\u000e":"\\u000e","\u000f":"\\u000f","\u0010":"\\u0010","\u0011":"\\u0011",
-"\u0012":"\\u0012","\u0013":"\\u0013","\u0014":"\\u0014","\u0015":"\\u0015",
-"\u0016":"\\u0016","\u0017":"\\u0017","\u0018":"\\u0018","\u0019":"\\u0019",
-"\u001a":"\\u001a","\u001b":"\\u001b","\u001c":"\\u001c","\u001d":"\\u001d",
-"\u001e":"\\u001e","\u001f":"\\u001f","\"":"\\\"","\\":"\\\\",
-"\u007f":"\\u007f","\u0080":"\\u0080","\u0081":"\\u0081","\u0082":"\\u0082",
-"\u0083":"\\u0083","\u0084":"\\u0084","\u0085":"\\u0085","\u0086":"\\u0086",
-"\u0087":"\\u0087","\u0088":"\\u0088","\u0089":"\\u0089","\u008a":"\\u008a",
-"\u008b":"\\u008b","\u008c":"\\u008c","\u008d":"\\u008d","\u008e":"\\u008e",
-"\u008f":"\\u008f","\u0090":"\\u0090","\u0091":"\\u0091","\u0092":"\\u0092",
-"\u0093":"\\u0093","\u0094":"\\u0094","\u0095":"\\u0095","\u0096":"\\u0096",
-"\u0097":"\\u0097","\u0098":"\\u0098","\u0099":"\\u0099","\u009a":"\\u009a",
-"\u009b":"\\u009b","\u009c":"\\u009c","\u009d":"\\u009d","\u009e":"\\u009e",
-"\u009f":"\\u009f","\u00ad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601",
-"\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f",
-"\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d",
-"\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029",
-"\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d",
-"\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061",
-"\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065",
-"\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069",
-"\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d",
-"\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0",
-"\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4",
-"\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8",
-"\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc",
-"\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"};
-
-// Some extra characters that Chrome gets wrong, and substitutes with
-// something else on the wire.
-var extra_escapable = /[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g,
- extra_lookup;
-
-// JSON Quote string. Use native implementation when possible.
-var JSONQuote = (JSON && JSON.stringify) || function(string) {
- json_escapable.lastIndex = 0;
- if (json_escapable.test(string)) {
- string = string.replace(json_escapable, function(a) {
- return json_lookup[a];
- });
- }
- return '"' + string + '"';
-};
-
-// This may be quite slow, so let's delay until user actually uses bad
-// characters.
-var unroll_lookup = function(escapable) {
- var i;
- var unrolled = {}
- var c = []
- for(i=0; i<65536; i++) {
- c.push( String.fromCharCode(i) );
- }
- escapable.lastIndex = 0;
- c.join('').replace(escapable, function (a) {
- unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- return '';
- });
- escapable.lastIndex = 0;
- return unrolled;
-};
-
-// Quote string, also taking care of unicode characters that browsers
-// often break. Especially, take care of unicode surrogates:
-// http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates
-utils.quote = function(string) {
- var quoted = JSONQuote(string);
-
- // In most cases this should be very fast and good enough.
- extra_escapable.lastIndex = 0;
- if(!extra_escapable.test(quoted)) {
- return quoted;
- }
-
- if(!extra_lookup) extra_lookup = unroll_lookup(extra_escapable);
-
- return quoted.replace(extra_escapable, function(a) {
- return extra_lookup[a];
- });
-}
-
-var _all_protocols = ['websocket',
- 'xdr-streaming',
- 'xhr-streaming',
- 'iframe-eventsource',
- 'iframe-htmlfile',
- 'xdr-polling',
- 'xhr-polling',
- 'iframe-xhr-polling',
- 'jsonp-polling'];
-
-utils.probeProtocols = function() {
- var probed = {};
- for(var i=0; i<_all_protocols.length; i++) {
- var protocol = _all_protocols[i];
- // User can have a typo in protocol name.
- probed[protocol] = SockJS[protocol] &&
- SockJS[protocol].enabled();
- }
- return probed;
-};
-
-utils.detectProtocols = function(probed, protocols_whitelist, info) {
- var pe = {},
- protocols = [];
- if (!protocols_whitelist) protocols_whitelist = _all_protocols;
- for(var i=0; i 0) {
- maybe_push(protos);
- }
- }
- }
-
- // 1. Websocket
- if (info.websocket !== false) {
- maybe_push(['websocket']);
- }
-
- // 2. Streaming
- if (pe['xhr-streaming'] && !info.null_origin) {
- protocols.push('xhr-streaming');
- } else {
- if (pe['xdr-streaming'] && !info.cookie_needed && !info.null_origin) {
- protocols.push('xdr-streaming');
- } else {
- maybe_push(['iframe-eventsource',
- 'iframe-htmlfile']);
- }
- }
-
- // 3. Polling
- if (pe['xhr-polling'] && !info.null_origin) {
- protocols.push('xhr-polling');
- } else {
- if (pe['xdr-polling'] && !info.cookie_needed && !info.null_origin) {
- protocols.push('xdr-polling');
- } else {
- maybe_push(['iframe-xhr-polling',
- 'jsonp-polling']);
- }
- }
- return protocols;
-}
-// [*] End of lib/utils.js
-
-
-// [*] Including lib/dom.js
-/*
- * ***** BEGIN LICENSE BLOCK *****
- * Copyright (c) 2011-2012 VMware, Inc.
- *
- * For the license see COPYING.
- * ***** END LICENSE BLOCK *****
- */
-
-// May be used by htmlfile jsonp and transports.
-var MPrefix = '_sockjs_global';
-utils.createHook = function() {
- var window_id = 'a' + utils.random_string(8);
- if (!(MPrefix in _window)) {
- var map = {};
- _window[MPrefix] = function(window_id) {
- if (!(window_id in map)) {
- map[window_id] = {
- id: window_id,
- del: function() {delete map[window_id];}
- };
- }
- return map[window_id];
- }
- }
- return _window[MPrefix](window_id);
-};
-
-
-
-utils.attachMessage = function(listener) {
- utils.attachEvent('message', listener);
-};
-utils.attachEvent = function(event, listener) {
- if (typeof _window.addEventListener !== 'undefined') {
- _window.addEventListener(event, listener, false);
- } else {
- // IE quirks.
- // According to: http://stevesouders.com/misc/test-postmessage.php
- // the message gets delivered only to 'document', not 'window'.
- _document.attachEvent("on" + event, listener);
- // I get 'window' for ie8.
- _window.attachEvent("on" + event, listener);
- }
-};
-
-utils.detachMessage = function(listener) {
- utils.detachEvent('message', listener);
-};
-utils.detachEvent = function(event, listener) {
- if (typeof _window.addEventListener !== 'undefined') {
- _window.removeEventListener(event, listener, false);
- } else {
- _document.detachEvent("on" + event, listener);
- _window.detachEvent("on" + event, listener);
- }
-};
-
-
-var on_unload = {};
-// Things registered after beforeunload are to be called immediately.
-var after_unload = false;
-
-var trigger_unload_callbacks = function() {
- for(var ref in on_unload) {
- on_unload[ref]();
- delete on_unload[ref];
- };
-};
-
-var unload_triggered = function() {
- if(after_unload) return;
- after_unload = true;
- trigger_unload_callbacks();
-};
-
-// Onbeforeunload alone is not reliable. We could use only 'unload'
-// but it's not working in opera within an iframe. Let's use both.
-utils.attachEvent('beforeunload', unload_triggered);
-utils.attachEvent('unload', unload_triggered);
-
-utils.unload_add = function(listener) {
- var ref = utils.random_string(8);
- on_unload[ref] = listener;
- if (after_unload) {
- utils.delay(trigger_unload_callbacks);
- }
- return ref;
-};
-utils.unload_del = function(ref) {
- if (ref in on_unload)
- delete on_unload[ref];
-};
-
-
-utils.createIframe = function (iframe_url, error_callback) {
- var iframe = _document.createElement('iframe');
- var tref, unload_ref;
- var unattach = function() {
- clearTimeout(tref);
- // Explorer had problems with that.
- try {iframe.onload = null;} catch (x) {}
- iframe.onerror = null;
- };
- var cleanup = function() {
- if (iframe) {
- unattach();
- // This timeout makes chrome fire onbeforeunload event
- // within iframe. Without the timeout it goes straight to
- // onunload.
- setTimeout(function() {
- if(iframe) {
- iframe.parentNode.removeChild(iframe);
- }
- iframe = null;
- }, 0);
- utils.unload_del(unload_ref);
- }
- };
- var onerror = function(r) {
- if (iframe) {
- cleanup();
- error_callback(r);
- }
- };
- var post = function(msg, origin) {
- try {
- // When the iframe is not loaded, IE raises an exception
- // on 'contentWindow'.
- if (iframe && iframe.contentWindow) {
- iframe.contentWindow.postMessage(msg, origin);
- }
- } catch (x) {};
- };
-
- iframe.src = iframe_url;
- iframe.style.display = 'none';
- iframe.style.position = 'absolute';
- iframe.onerror = function(){onerror('onerror');};
- iframe.onload = function() {
- // `onload` is triggered before scripts on the iframe are
- // executed. Give it few seconds to actually load stuff.
- clearTimeout(tref);
- tref = setTimeout(function(){onerror('onload timeout');}, 2000);
- };
- _document.body.appendChild(iframe);
- tref = setTimeout(function(){onerror('timeout');}, 15000);
- unload_ref = utils.unload_add(cleanup);
- return {
- post: post,
- cleanup: cleanup,
- loaded: unattach
- };
-};
-
-utils.createHtmlfile = function (iframe_url, error_callback) {
- var doc = new ActiveXObject('htmlfile');
- var tref, unload_ref;
- var iframe;
- var unattach = function() {
- clearTimeout(tref);
- };
- var cleanup = function() {
- if (doc) {
- unattach();
- utils.unload_del(unload_ref);
- iframe.parentNode.removeChild(iframe);
- iframe = doc = null;
- CollectGarbage();
- }
- };
- var onerror = function(r) {
- if (doc) {
- cleanup();
- error_callback(r);
- }
- };
- var post = function(msg, origin) {
- try {
- // When the iframe is not loaded, IE raises an exception
- // on 'contentWindow'.
- if (iframe && iframe.contentWindow) {
- iframe.contentWindow.postMessage(msg, origin);
- }
- } catch (x) {};
- };
-
- doc.open();
- doc.write('' +
- 'document.domain="' + document.domain + '";' +
- '');
- doc.close();
- doc.parentWindow[WPrefix] = _window[WPrefix];
- var c = doc.createElement('div');
- doc.body.appendChild(c);
- iframe = doc.createElement('iframe');
- c.appendChild(iframe);
- iframe.src = iframe_url;
- tref = setTimeout(function(){onerror('timeout');}, 15000);
- unload_ref = utils.unload_add(cleanup);
- return {
- post: post,
- cleanup: cleanup,
- loaded: unattach
- };
-};
-// [*] End of lib/dom.js
-
-
-// [*] Including lib/dom2.js
-/*
- * ***** BEGIN LICENSE BLOCK *****
- * Copyright (c) 2011-2012 VMware, Inc.
- *
- * For the license see COPYING.
- * ***** END LICENSE BLOCK *****
- */
-
-var AbstractXHRObject = function(){};
-AbstractXHRObject.prototype = new EventEmitter(['chunk', 'finish']);
-
-AbstractXHRObject.prototype._start = function(method, url, payload, opts) {
- var that = this;
-
- try {
- that.xhr = new XMLHttpRequest();
- } catch(x) {};
-
- if (!that.xhr) {
- try {
- that.xhr = new _window.ActiveXObject('Microsoft.XMLHTTP');
- } catch(x) {};
- }
- if (_window.ActiveXObject || _window.XDomainRequest) {
- // IE8 caches even POSTs
- url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date);
- }
-
- // Explorer tends to keep connection open, even after the
- // tab gets closed: http://bugs.jquery.com/ticket/5280
- that.unload_ref = utils.unload_add(function(){that._cleanup(true);});
- try {
- that.xhr.open(method, url, true);
- } catch(e) {
- // IE raises an exception on wrong port.
- that.emit('finish', 0, '');
- that._cleanup();
- return;
- };
-
- if (!opts || !opts.no_credentials) {
- // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest :
- // "This never affects same-site requests."
- that.xhr.withCredentials = 'true';
- }
- if (opts && opts.headers) {
- for(var key in opts.headers) {
- that.xhr.setRequestHeader(key, opts.headers[key]);
- }
- }
-
- that.xhr.onreadystatechange = function() {
- if (that.xhr) {
- var x = that.xhr;
- switch (x.readyState) {
- case 3:
- // IE doesn't like peeking into responseText or status
- // on Microsoft.XMLHTTP and readystate=3
- try {
- var status = x.status;
- var text = x.responseText;
- } catch (x) {};
- // IE does return readystate == 3 for 404 answers.
- if (text && text.length > 0) {
- that.emit('chunk', status, text);
- }
- break;
- case 4:
- that.emit('finish', x.status, x.responseText);
- that._cleanup(false);
- break;
- }
- }
- };
- that.xhr.send(payload);
-};
-
-AbstractXHRObject.prototype._cleanup = function(abort) {
- var that = this;
- if (!that.xhr) return;
- utils.unload_del(that.unload_ref);
-
- // IE needs this field to be a function
- that.xhr.onreadystatechange = function(){};
-
- if (abort) {
- try {
- that.xhr.abort();
- } catch(x) {};
- }
- that.unload_ref = that.xhr = null;
-};
-
-AbstractXHRObject.prototype.close = function() {
- var that = this;
- that.nuke();
- that._cleanup(true);
-};
-
-var XHRCorsObject = utils.XHRCorsObject = function() {
- var that = this, args = arguments;
- utils.delay(function(){that._start.apply(that, args);});
-};
-XHRCorsObject.prototype = new AbstractXHRObject();
-
-var XHRLocalObject = utils.XHRLocalObject = function(method, url, payload) {
- var that = this;
- utils.delay(function(){
- that._start(method, url, payload, {
- no_credentials: true
- });
- });
-};
-XHRLocalObject.prototype = new AbstractXHRObject();
-
-
-
-// References:
-// http://ajaxian.com/archives/100-line-ajax-wrapper
-// http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx
-var XDRObject = utils.XDRObject = function(method, url, payload) {
- var that = this;
- utils.delay(function(){that._start(method, url, payload);});
-};
-XDRObject.prototype = new EventEmitter(['chunk', 'finish']);
-XDRObject.prototype._start = function(method, url, payload) {
- var that = this;
- var xdr = new XDomainRequest();
- // IE caches even POSTs
- url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date);
-
- var onerror = xdr.ontimeout = xdr.onerror = function() {
- that.emit('finish', 0, '');
- that._cleanup(false);
- };
- xdr.onprogress = function() {
- that.emit('chunk', 200, xdr.responseText);
- };
- xdr.onload = function() {
- that.emit('finish', 200, xdr.responseText);
- that._cleanup(false);
- };
- that.xdr = xdr;
- that.unload_ref = utils.unload_add(function(){that._cleanup(true);});
- try {
- // Fails with AccessDenied if port number is bogus
- that.xdr.open(method, url);
- that.xdr.send(payload);
- } catch(x) {
- onerror();
- }
-};
-
-XDRObject.prototype._cleanup = function(abort) {
- var that = this;
- if (!that.xdr) return;
- utils.unload_del(that.unload_ref);
-
- that.xdr.ontimeout = that.xdr.onerror = that.xdr.onprogress =
- that.xdr.onload = null;
- if (abort) {
- try {
- that.xdr.abort();
- } catch(x) {};
- }
- that.unload_ref = that.xdr = null;
-};
-
-XDRObject.prototype.close = function() {
- var that = this;
- that.nuke();
- that._cleanup(true);
-};
-
-// 1. Is natively via XHR
-// 2. Is natively via XDR
-// 3. Nope, but postMessage is there so it should work via the Iframe.
-// 4. Nope, sorry.
-utils.isXHRCorsCapable = function() {
- if (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()) {
- return 1;
- }
- // XDomainRequest doesn't work if page is served from file://
- if (_window.XDomainRequest && _document.domain) {
- return 2;
- }
- if (IframeTransport.enabled()) {
- return 3;
- }
- return 4;
-};
-// [*] End of lib/dom2.js
-
-
-// [*] Including lib/sockjs.js
-/*
- * ***** BEGIN LICENSE BLOCK *****
- * Copyright (c) 2011-2012 VMware, Inc.
- *
- * For the license see COPYING.
- * ***** END LICENSE BLOCK *****
- */
-
-var SockJS = function(url, dep_protocols_whitelist, options) {
- var that = this, protocols_whitelist;
- that._options = {devel: false, debug: false, protocols_whitelist: [],
- info: undefined, rtt: undefined};
- if (options) {
- utils.objectExtend(that._options, options);
- }
- that._base_url = utils.amendUrl(url);
- that._server = that._options.server || utils.random_number_string(1000);
- if (that._options.protocols_whitelist &&
- that._options.protocols_whitelist.length) {
- protocols_whitelist = that._options.protocols_whitelist;
- } else {
- // Deprecated API
- if (typeof dep_protocols_whitelist === 'string' &&
- dep_protocols_whitelist.length > 0) {
- protocols_whitelist = [dep_protocols_whitelist];
- } else if (utils.isArray(dep_protocols_whitelist)) {
- protocols_whitelist = dep_protocols_whitelist
- } else {
- protocols_whitelist = null;
- }
- if (protocols_whitelist) {
- that._debug('Deprecated API: Use "protocols_whitelist" option ' +
- 'instead of supplying protocol list as a second ' +
- 'parameter to SockJS constructor.');
- }
- }
- that._protocols = [];
- that.protocol = null;
- that.readyState = SockJS.CONNECTING;
- that._ir = createInfoReceiver(that._base_url);
- that._ir.onfinish = function(info, rtt) {
- that._ir = null;
- if (info) {
- if (that._options.info) {
- // Override if user supplies the option
- info = utils.objectExtend(info, that._options.info);
- }
- if (that._options.rtt) {
- rtt = that._options.rtt;
- }
- that._applyInfo(info, rtt, protocols_whitelist);
- that._didClose();
- } else {
- that._didClose(1002, 'Can\'t connect to server', true);
- }
- };
-};
-// Inheritance
-SockJS.prototype = new REventTarget();
-
-SockJS.version = "0.3.1";
-
-SockJS.CONNECTING = 0;
-SockJS.OPEN = 1;
-SockJS.CLOSING = 2;
-SockJS.CLOSED = 3;
-
-SockJS.prototype._debug = function() {
- if (this._options.debug)
- utils.log.apply(utils, arguments);
-};
-
-SockJS.prototype._dispatchOpen = function() {
- var that = this;
- if (that.readyState === SockJS.CONNECTING) {
- if (that._transport_tref) {
- clearTimeout(that._transport_tref);
- that._transport_tref = null;
- }
- that.readyState = SockJS.OPEN;
- that.dispatchEvent(new SimpleEvent("open"));
- } else {
- // The server might have been restarted, and lost track of our
- // connection.
- that._didClose(1006, "Server lost session");
- }
-};
-
-SockJS.prototype._dispatchMessage = function(data) {
- var that = this;
- if (that.readyState !== SockJS.OPEN)
- return;
- that.dispatchEvent(new SimpleEvent("message", {data: data}));
-};
-
-SockJS.prototype._dispatchHeartbeat = function(data) {
- var that = this;
- if (that.readyState !== SockJS.OPEN)
- return;
- that.dispatchEvent(new SimpleEvent('heartbeat', {}));
-};
-
-SockJS.prototype._didClose = function(code, reason, force) {
- var that = this;
- if (that.readyState !== SockJS.CONNECTING &&
- that.readyState !== SockJS.OPEN &&
- that.readyState !== SockJS.CLOSING)
- throw new Error('INVALID_STATE_ERR');
- if (that._ir) {
- that._ir.nuke();
- that._ir = null;
- }
-
- if (that._transport) {
- that._transport.doCleanup();
- that._transport = null;
- }
-
- var close_event = new SimpleEvent("close", {
- code: code,
- reason: reason,
- wasClean: utils.userSetCode(code)});
-
- if (!utils.userSetCode(code) &&
- that.readyState === SockJS.CONNECTING && !force) {
- if (that._try_next_protocol(close_event)) {
- return;
- }
- close_event = new SimpleEvent("close", {code: 2000,
- reason: "All transports failed",
- wasClean: false,
- last_event: close_event});
- }
- that.readyState = SockJS.CLOSED;
-
- utils.delay(function() {
- that.dispatchEvent(close_event);
- });
-};
-
-SockJS.prototype._didMessage = function(data) {
- var that = this;
- var type = data.slice(0, 1);
- switch(type) {
- case 'o':
- that._dispatchOpen();
- break;
- case 'a':
- var payload = JSON.parse(data.slice(1) || '[]');
- for(var i=0; i < payload.length; i++){
- that._dispatchMessage(payload[i]);
- }
- break;
- case 'm':
- var payload = JSON.parse(data.slice(1) || 'null');
- that._dispatchMessage(payload);
- break;
- case 'c':
- var payload = JSON.parse(data.slice(1) || '[]');
- that._didClose(payload[0], payload[1]);
- break;
- case 'h':
- that._dispatchHeartbeat();
- break;
- }
-};
-
-SockJS.prototype._try_next_protocol = function(close_event) {
- var that = this;
- if (that.protocol) {
- that._debug('Closed transport:', that.protocol, ''+close_event);
- that.protocol = null;
- }
- if (that._transport_tref) {
- clearTimeout(that._transport_tref);
- that._transport_tref = null;
- }
-
- while(1) {
- var protocol = that.protocol = that._protocols.shift();
- if (!protocol) {
- return false;
- }
- // Some protocols require access to `body`, what if were in
- // the `head`?
- if (SockJS[protocol] &&
- SockJS[protocol].need_body === true &&
- (!_document.body ||
- (typeof _document.readyState !== 'undefined'
- && _document.readyState !== 'complete'))) {
- that._protocols.unshift(protocol);
- that.protocol = 'waiting-for-load';
- utils.attachEvent('load', function(){
- that._try_next_protocol();
- });
- return true;
- }
-
- if (!SockJS[protocol] ||
- !SockJS[protocol].enabled(that._options)) {
- that._debug('Skipping transport:', protocol);
- } else {
- var roundTrips = SockJS[protocol].roundTrips || 1;
- var to = ((that._options.rto || 0) * roundTrips) || 5000;
- that._transport_tref = utils.delay(to, function() {
- if (that.readyState === SockJS.CONNECTING) {
- // I can't understand how it is possible to run
- // this timer, when the state is CLOSED, but
- // apparently in IE everythin is possible.
- that._didClose(2007, "Transport timeouted");
- }
- });
-
- var connid = utils.random_string(8);
- var trans_url = that._base_url + '/' + that._server + '/' + connid;
- that._debug('Opening transport:', protocol, ' url:'+trans_url,
- ' RTO:'+that._options.rto);
- that._transport = new SockJS[protocol](that, trans_url,
- that._base_url);
- return true;
- }
- }
-};
-
-SockJS.prototype.close = function(code, reason) {
- var that = this;
- if (code && !utils.userSetCode(code))
- throw new Error("INVALID_ACCESS_ERR");
- if(that.readyState !== SockJS.CONNECTING &&
- that.readyState !== SockJS.OPEN) {
- return false;
- }
- that.readyState = SockJS.CLOSING;
- that._didClose(code || 1000, reason || "Normal closure");
- return true;
-};
-
-SockJS.prototype.send = function(data) {
- var that = this;
- if (that.readyState === SockJS.CONNECTING)
- throw new Error('INVALID_STATE_ERR');
- if (that.readyState === SockJS.OPEN) {
- that._transport.doSend(utils.quote('' + data));
- }
- return true;
-};
-
-SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) {
- var that = this;
- that._options.info = info;
- that._options.rtt = rtt;
- that._options.rto = utils.countRTO(rtt);
- that._options.info.null_origin = !_document.domain;
- var probed = utils.probeProtocols();
- that._protocols = utils.detectProtocols(probed, protocols_whitelist, info);
-};
-// [*] End of lib/sockjs.js
-
-
-// [*] Including lib/trans-websocket.js
-/*
- * ***** BEGIN LICENSE BLOCK *****
- * Copyright (c) 2011-2012 VMware, Inc.
- *
- * For the license see COPYING.
- * ***** END LICENSE BLOCK *****
- */
-
-var WebSocketTransport = SockJS.websocket = function(ri, trans_url) {
- var that = this;
- var url = trans_url + '/websocket';
- if (url.slice(0, 5) === 'https') {
- url = 'wss' + url.slice(5);
- } else {
- url = 'ws' + url.slice(4);
- }
- that.ri = ri;
- that.url = url;
- var Constructor = _window.WebSocket || _window.MozWebSocket;
-
- that.ws = new Constructor(that.url);
- that.ws.onmessage = function(e) {
- that.ri._didMessage(e.data);
- };
- // Firefox has an interesting bug. If a websocket connection is
- // created after onbeforeunload, it stays alive even when user
- // navigates away from the page. In such situation let's lie -
- // let's not open the ws connection at all. See:
- // https://github.com/sockjs/sockjs-client/issues/28
- // https://bugzilla.mozilla.org/show_bug.cgi?id=696085
- that.unload_ref = utils.unload_add(function(){that.ws.close()});
- that.ws.onclose = function() {
- that.ri._didMessage(utils.closeFrame(1006, "WebSocket connection broken"));
- };
-};
-
-WebSocketTransport.prototype.doSend = function(data) {
- this.ws.send('[' + data + ']');
-};
-
-WebSocketTransport.prototype.doCleanup = function() {
- var that = this;
- var ws = that.ws;
- if (ws) {
- ws.onmessage = ws.onclose = null;
- ws.close();
- utils.unload_del(that.unload_ref);
- that.unload_ref = that.ri = that.ws = null;
- }
-};
-
-WebSocketTransport.enabled = function() {
- return !!(_window.WebSocket || _window.MozWebSocket);
-};
-
-// In theory, ws should require 1 round trip. But in chrome, this is
-// not very stable over SSL. Most likely a ws connection requires a
-// separate SSL connection, in which case 2 round trips are an
-// absolute minumum.
-WebSocketTransport.roundTrips = 2;
-// [*] End of lib/trans-websocket.js
-
-
-// [*] Including lib/trans-sender.js
-/*
- * ***** BEGIN LICENSE BLOCK *****
- * Copyright (c) 2011-2012 VMware, Inc.
- *
- * For the license see COPYING.
- * ***** END LICENSE BLOCK *****
- */
-
-var BufferedSender = function() {};
-BufferedSender.prototype.send_constructor = function(sender) {
- var that = this;
- that.send_buffer = [];
- that.sender = sender;
-};
-BufferedSender.prototype.doSend = function(message) {
- var that = this;
- that.send_buffer.push(message);
- if (!that.send_stop) {
- that.send_schedule();
- }
-};
-
-// For polling transports in a situation when in the message callback,
-// new message is being send. If the sending connection was started
-// before receiving one, it is possible to saturate the network and
-// timeout due to the lack of receiving socket. To avoid that we delay
-// sending messages by some small time, in order to let receiving
-// connection be started beforehand. This is only a halfmeasure and
-// does not fix the big problem, but it does make the tests go more
-// stable on slow networks.
-BufferedSender.prototype.send_schedule_wait = function() {
- var that = this;
- var tref;
- that.send_stop = function() {
- that.send_stop = null;
- clearTimeout(tref);
- };
- tref = utils.delay(25, function() {
- that.send_stop = null;
- that.send_schedule();
- });
-};
-
-BufferedSender.prototype.send_schedule = function() {
- var that = this;
- if (that.send_buffer.length > 0) {
- var payload = '[' + that.send_buffer.join(',') + ']';
- that.send_stop = that.sender(that.trans_url,
- payload,
- function() {
- that.send_stop = null;
- that.send_schedule_wait();
- });
- that.send_buffer = [];
- }
-};
-
-BufferedSender.prototype.send_destructor = function() {
- var that = this;
- if (that._send_stop) {
- that._send_stop();
- }
- that._send_stop = null;
-};
-
-var jsonPGenericSender = function(url, payload, callback) {
- var that = this;
-
- if (!('_send_form' in that)) {
- var form = that._send_form = _document.createElement('form');
- var area = that._send_area = _document.createElement('textarea');
- area.name = 'd';
- form.style.display = 'none';
- form.style.position = 'absolute';
- form.method = 'POST';
- form.enctype = 'application/x-www-form-urlencoded';
- form.acceptCharset = "UTF-8";
- form.appendChild(area);
- _document.body.appendChild(form);
- }
- var form = that._send_form;
- var area = that._send_area;
- var id = 'a' + utils.random_string(8);
- form.target = id;
- form.action = url + '/jsonp_send?i=' + id;
-
- var iframe;
- try {
- // ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
- iframe = _document.createElement('