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('