mxgraph
Version:
mxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.
2,131 lines (1,925 loc) • 2.25 MB
JavaScript
/**
* Copyright (c) 2006-2017, JGraph Ltd
* Copyright (c) 2006-2017, Gaudenz Alder
*/
var mxClient =
{
/**
* Class: mxClient
*
* Bootstrapping mechanism for the mxGraph thin client. The production version
* of this file contains all code required to run the mxGraph thin client, as
* well as global constants to identify the browser and operating system in
* use. You may have to load chrome://global/content/contentAreaUtils.js in
* your page to disable certain security restrictions in Mozilla.
*
* Variable: VERSION
*
* Contains the current version of the mxGraph library. The strings that
* communicate versions of mxGraph use the following format.
*
* versionMajor.versionMinor.buildNumber.revisionNumber
*
* Current version is 4.2.1.
*/
VERSION: '4.2.1',
/**
* Variable: IS_IE
*
* True if the current browser is Internet Explorer 10 or below. Use <mxClient.IS_IE11>
* to detect IE 11.
*/
IS_IE: navigator.userAgent != null && navigator.userAgent.indexOf('MSIE') >= 0,
/**
* Variable: IS_IE6
*
* True if the current browser is Internet Explorer 6.x.
*/
IS_IE6: navigator.userAgent != null && navigator.userAgent.indexOf('MSIE 6') >= 0,
/**
* Variable: IS_IE11
*
* True if the current browser is Internet Explorer 11.x.
*/
IS_IE11: navigator.userAgent != null && !!navigator.userAgent.match(/Trident\/7\./),
/**
* Variable: IS_EDGE
*
* True if the current browser is Microsoft Edge.
*/
IS_EDGE: navigator.userAgent != null && !!navigator.userAgent.match(/Edge\//),
/**
* Variable: IS_QUIRKS
*
* True if the current browser is Internet Explorer and it is in quirks mode.
*/
IS_QUIRKS: navigator.userAgent != null && navigator.userAgent.indexOf('MSIE') >= 0 &&
(document.documentMode == null || document.documentMode == 5),
/**
* Variable: IS_EM
*
* True if the browser is IE11 in enterprise mode (IE8 standards mode).
*/
IS_EM: 'spellcheck' in document.createElement('textarea') && document.documentMode == 8,
/**
* Variable: VML_PREFIX
*
* Prefix for VML namespace in node names. Default is 'v'.
*/
VML_PREFIX: 'v',
/**
* Variable: OFFICE_PREFIX
*
* Prefix for VML office namespace in node names. Default is 'o'.
*/
OFFICE_PREFIX: 'o',
/**
* Variable: IS_NS
*
* True if the current browser is Netscape (including Firefox).
*/
IS_NS: navigator.userAgent != null &&
navigator.userAgent.indexOf('Mozilla/') >= 0 &&
navigator.userAgent.indexOf('MSIE') < 0 &&
navigator.userAgent.indexOf('Edge/') < 0,
/**
* Variable: IS_OP
*
* True if the current browser is Opera.
*/
IS_OP: navigator.userAgent != null &&
(navigator.userAgent.indexOf('Opera/') >= 0 ||
navigator.userAgent.indexOf('OPR/') >= 0),
/**
* Variable: IS_OT
*
* True if -o-transform is available as a CSS style, ie for Opera browsers
* based on a Presto engine with version 2.5 or later.
*/
IS_OT: navigator.userAgent != null &&
navigator.userAgent.indexOf('Presto/') >= 0 &&
navigator.userAgent.indexOf('Presto/2.4.') < 0 &&
navigator.userAgent.indexOf('Presto/2.3.') < 0 &&
navigator.userAgent.indexOf('Presto/2.2.') < 0 &&
navigator.userAgent.indexOf('Presto/2.1.') < 0 &&
navigator.userAgent.indexOf('Presto/2.0.') < 0 &&
navigator.userAgent.indexOf('Presto/1.') < 0,
/**
* Variable: IS_SF
*
* True if the current browser is Safari.
*/
IS_SF: /Apple Computer, Inc/.test(navigator.vendor),
/**
* Variable: IS_ANDROID
*
* Returns true if the user agent contains Android.
*/
IS_ANDROID: navigator.appVersion.indexOf('Android') >= 0,
/**
* Variable: IS_IOS
*
* Returns true if the user agent is an iPad, iPhone or iPod.
*/
IS_IOS: (/iP(hone|od|ad)/.test(navigator.platform)),
/**
* Variable: IS_GC
*
* True if the current browser is Google Chrome.
*/
IS_GC: /Google Inc/.test(navigator.vendor),
/**
* Variable: IS_CHROMEAPP
*
* True if the this is running inside a Chrome App.
*/
IS_CHROMEAPP: window.chrome != null && chrome.app != null && chrome.app.runtime != null,
/**
* Variable: IS_FF
*
* True if the current browser is Firefox.
*/
IS_FF: typeof InstallTrigger !== 'undefined',
/**
* Variable: IS_MT
*
* True if -moz-transform is available as a CSS style. This is the case
* for all Firefox-based browsers newer than or equal 3, such as Camino,
* Iceweasel, Seamonkey and Iceape.
*/
IS_MT: (navigator.userAgent.indexOf('Firefox/') >= 0 &&
navigator.userAgent.indexOf('Firefox/1.') < 0 &&
navigator.userAgent.indexOf('Firefox/2.') < 0) ||
(navigator.userAgent.indexOf('Iceweasel/') >= 0 &&
navigator.userAgent.indexOf('Iceweasel/1.') < 0 &&
navigator.userAgent.indexOf('Iceweasel/2.') < 0) ||
(navigator.userAgent.indexOf('SeaMonkey/') >= 0 &&
navigator.userAgent.indexOf('SeaMonkey/1.') < 0) ||
(navigator.userAgent.indexOf('Iceape/') >= 0 &&
navigator.userAgent.indexOf('Iceape/1.') < 0),
/**
* Variable: IS_VML
*
* True if the browser supports VML.
*/
IS_VML: navigator.appName.toUpperCase() == 'MICROSOFT INTERNET EXPLORER',
/**
* Variable: IS_SVG
*
* True if the browser supports SVG.
*/
IS_SVG: navigator.appName.toUpperCase() != 'MICROSOFT INTERNET EXPLORER',
/**
* Variable: NO_FO
*
* True if foreignObject support is not available. This is the case for
* Opera, older SVG-based browsers and all versions of IE.
*/
NO_FO: !document.createElementNS || document.createElementNS('http://www.w3.org/2000/svg',
'foreignObject') != '[object SVGForeignObjectElement]' || navigator.userAgent.indexOf('Opera/') >= 0,
/**
* Variable: IS_WIN
*
* True if the client is a Windows.
*/
IS_WIN: navigator.appVersion.indexOf('Win') > 0,
/**
* Variable: IS_MAC
*
* True if the client is a Mac.
*/
IS_MAC: navigator.appVersion.indexOf('Mac') > 0,
/**
* Variable: IS_CHROMEOS
*
* True if the client is a Chrome OS.
*/
IS_CHROMEOS: /\bCrOS\b/.test(navigator.appVersion),
/**
* Variable: IS_TOUCH
*
* True if this device supports touchstart/-move/-end events (Apple iOS,
* Android, Chromebook and Chrome Browser on touch-enabled devices).
*/
IS_TOUCH: 'ontouchstart' in document.documentElement,
/**
* Variable: IS_POINTER
*
* True if this device supports Microsoft pointer events (always false on Macs).
*/
IS_POINTER: window.PointerEvent != null && !(navigator.appVersion.indexOf('Mac') > 0),
/**
* Variable: IS_LOCAL
*
* True if the documents location does not start with http:// or https://.
*/
IS_LOCAL: document.location.href.indexOf('http://') < 0 &&
document.location.href.indexOf('https://') < 0,
/**
* Variable: defaultBundles
*
* Contains the base names of the default bundles if mxLoadResources is false.
*/
defaultBundles: [],
/**
* Function: isBrowserSupported
*
* Returns true if the current browser is supported, that is, if
* <mxClient.IS_VML> or <mxClient.IS_SVG> is true.
*
* Example:
*
* (code)
* if (!mxClient.isBrowserSupported())
* {
* mxUtils.error('Browser is not supported!', 200, false);
* }
* (end)
*/
isBrowserSupported: function()
{
return mxClient.IS_VML || mxClient.IS_SVG;
},
/**
* Function: link
*
* Adds a link node to the head of the document. Use this
* to add a stylesheet to the page as follows:
*
* (code)
* mxClient.link('stylesheet', filename);
* (end)
*
* where filename is the (relative) URL of the stylesheet. The charset
* is hardcoded to ISO-8859-1 and the type is text/css.
*
* Parameters:
*
* rel - String that represents the rel attribute of the link node.
* href - String that represents the href attribute of the link node.
* doc - Optional parent document of the link node.
* id - unique id for the link element to check if it already exists
*/
link: function(rel, href, doc, id)
{
doc = doc || document;
// Workaround for Operation Aborted in IE6 if base tag is used in head
if (mxClient.IS_IE6)
{
doc.write('<link rel="' + rel + '" href="' + href + '" charset="UTF-8" type="text/css"/>');
}
else
{
var link = doc.createElement('link');
link.setAttribute('rel', rel);
link.setAttribute('href', href);
link.setAttribute('charset', 'UTF-8');
link.setAttribute('type', 'text/css');
if (id)
{
link.setAttribute('id', id);
}
var head = doc.getElementsByTagName('head')[0];
head.appendChild(link);
}
},
/**
* Function: loadResources
*
* Helper method to load the default bundles if mxLoadResources is false.
*
* Parameters:
*
* fn - Function to call after all resources have been loaded.
* lan - Optional string to pass to <mxResources.add>.
*/
loadResources: function(fn, lan)
{
var pending = mxClient.defaultBundles.length;
function callback()
{
if (--pending == 0)
{
fn();
}
}
for (var i = 0; i < mxClient.defaultBundles.length; i++)
{
mxResources.add(mxClient.defaultBundles[i], lan, callback);
}
},
/**
* Function: include
*
* Dynamically adds a script node to the document header.
*
* In production environments, the includes are resolved in the mxClient.js
* file to reduce the number of requests required for client startup. This
* function should only be used in development environments, but not in
* production systems.
*/
include: function(src)
{
document.write('<script src="'+src+'"></script>');
}
};
/**
* Variable: mxLoadResources
*
* Optional global config variable to toggle loading of the two resource files
* in <mxGraph> and <mxEditor>. Default is true. NOTE: This is a global variable,
* not a variable of mxClient. If this is false, you can use <mxClient.loadResources>
* with its callback to load the default bundles asynchronously.
*
* (code)
* <script type="text/javascript">
* var mxLoadResources = false;
* </script>
* <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
* (end)
*/
if (typeof(mxLoadResources) == 'undefined')
{
mxLoadResources = true;
}
/**
* Variable: mxForceIncludes
*
* Optional global config variable to force loading the JavaScript files in
* development mode. Default is undefined. NOTE: This is a global variable,
* not a variable of mxClient.
*
* (code)
* <script type="text/javascript">
* var mxLoadResources = true;
* </script>
* <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
* (end)
*/
if (typeof(mxForceIncludes) == 'undefined')
{
mxForceIncludes = false;
}
/**
* Variable: mxResourceExtension
*
* Optional global config variable to specify the extension of resource files.
* Default is true. NOTE: This is a global variable, not a variable of mxClient.
*
* (code)
* <script type="text/javascript">
* var mxResourceExtension = '.txt';
* </script>
* <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
* (end)
*/
if (typeof(mxResourceExtension) == 'undefined')
{
mxResourceExtension = '.txt';
}
/**
* Variable: mxLoadStylesheets
*
* Optional global config variable to toggle loading of the CSS files when
* the library is initialized. Default is true. NOTE: This is a global variable,
* not a variable of mxClient.
*
* (code)
* <script type="text/javascript">
* var mxLoadStylesheets = false;
* </script>
* <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
* (end)
*/
if (typeof(mxLoadStylesheets) == 'undefined')
{
mxLoadStylesheets = true;
}
/**
* Variable: basePath
*
* Basepath for all URLs in the core without trailing slash. Default is '.'.
* Set mxBasePath prior to loading the mxClient library as follows to override
* this setting:
*
* (code)
* <script type="text/javascript">
* mxBasePath = '/path/to/core/directory';
* </script>
* <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
* (end)
*
* When using a relative path, the path is relative to the URL of the page that
* contains the assignment. Trailing slashes are automatically removed.
*/
if (typeof(mxBasePath) != 'undefined' && mxBasePath.length > 0)
{
// Adds a trailing slash if required
if (mxBasePath.substring(mxBasePath.length - 1) == '/')
{
mxBasePath = mxBasePath.substring(0, mxBasePath.length - 1);
}
mxClient.basePath = mxBasePath;
}
else
{
mxClient.basePath = '.';
}
/**
* Variable: imageBasePath
*
* Basepath for all images URLs in the core without trailing slash. Default is
* <mxClient.basePath> + '/images'. Set mxImageBasePath prior to loading the
* mxClient library as follows to override this setting:
*
* (code)
* <script type="text/javascript">
* mxImageBasePath = '/path/to/image/directory';
* </script>
* <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
* (end)
*
* When using a relative path, the path is relative to the URL of the page that
* contains the assignment. Trailing slashes are automatically removed.
*/
if (typeof(mxImageBasePath) != 'undefined' && mxImageBasePath.length > 0)
{
// Adds a trailing slash if required
if (mxImageBasePath.substring(mxImageBasePath.length - 1) == '/')
{
mxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length - 1);
}
mxClient.imageBasePath = mxImageBasePath;
}
else
{
mxClient.imageBasePath = mxClient.basePath + '/images';
}
/**
* Variable: language
*
* Defines the language of the client, eg. en for english, de for german etc.
* The special value 'none' will disable all built-in internationalization and
* resource loading. See <mxResources.getSpecialBundle> for handling identifiers
* with and without a dash.
*
* Set mxLanguage prior to loading the mxClient library as follows to override
* this setting:
*
* (code)
* <script type="text/javascript">
* mxLanguage = 'en';
* </script>
* <script type="text/javascript" src="js/mxClient.js"></script>
* (end)
*
* If internationalization is disabled, then the following variables should be
* overridden to reflect the current language of the system. These variables are
* cleared when i18n is disabled.
* <mxEditor.askZoomResource>, <mxEditor.lastSavedResource>,
* <mxEditor.currentFileResource>, <mxEditor.propertiesResource>,
* <mxEditor.tasksResource>, <mxEditor.helpResource>, <mxEditor.outlineResource>,
* <mxElbowEdgeHandler.doubleClickOrientationResource>, <mxUtils.errorResource>,
* <mxUtils.closeResource>, <mxGraphSelectionModel.doneResource>,
* <mxGraphSelectionModel.updatingSelectionResource>, <mxGraphView.doneResource>,
* <mxGraphView.updatingDocumentResource>, <mxCellRenderer.collapseExpandResource>,
* <mxGraph.containsValidationErrorsResource> and
* <mxGraph.alreadyConnectedResource>.
*/
if (typeof(mxLanguage) != 'undefined' && mxLanguage != null)
{
mxClient.language = mxLanguage;
}
else
{
mxClient.language = (mxClient.IS_IE) ? navigator.userLanguage : navigator.language;
}
/**
* Variable: defaultLanguage
*
* Defines the default language which is used in the common resource files. Any
* resources for this language will only load the common resource file, but not
* the language-specific resource file. Default is 'en'.
*
* Set mxDefaultLanguage prior to loading the mxClient library as follows to override
* this setting:
*
* (code)
* <script type="text/javascript">
* mxDefaultLanguage = 'de';
* </script>
* <script type="text/javascript" src="js/mxClient.js"></script>
* (end)
*/
if (typeof(mxDefaultLanguage) != 'undefined' && mxDefaultLanguage != null)
{
mxClient.defaultLanguage = mxDefaultLanguage;
}
else
{
mxClient.defaultLanguage = 'en';
}
// Adds all required stylesheets and namespaces
if (mxLoadStylesheets)
{
mxClient.link('stylesheet', mxClient.basePath + '/css/common.css');
}
/**
* Variable: languages
*
* Defines the optional array of all supported language extensions. The default
* language does not have to be part of this list. See
* <mxResources.isLanguageSupported>.
*
* (code)
* <script type="text/javascript">
* mxLanguages = ['de', 'it', 'fr'];
* </script>
* <script type="text/javascript" src="js/mxClient.js"></script>
* (end)
*
* This is used to avoid unnecessary requests to language files, ie. if a 404
* will be returned.
*/
if (typeof(mxLanguages) != 'undefined' && mxLanguages != null)
{
mxClient.languages = mxLanguages;
}
// Adds required namespaces, stylesheets and memory handling for older IE browsers
if (mxClient.IS_VML)
{
if (mxClient.IS_SVG)
{
mxClient.IS_VML = false;
}
else
{
// Enables support for IE8 standards mode. Note that this requires all attributes for VML
// elements to be set using direct notation, ie. node.attr = value, not setAttribute.
if (document.namespaces != null)
{
if (document.documentMode == 8)
{
document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml', '#default#VML');
document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office', '#default#VML');
}
else
{
document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml');
document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office');
}
}
// Workaround for limited number of stylesheets in IE (does not work in standards mode)
if (mxClient.IS_QUIRKS && document.styleSheets.length >= 30)
{
(function()
{
var node = document.createElement('style');
node.type = 'text/css';
node.styleSheet.cssText = mxClient.VML_PREFIX + '\\:*{behavior:url(#default#VML)}' +
mxClient.OFFICE_PREFIX + '\\:*{behavior:url(#default#VML)}';
document.getElementsByTagName('head')[0].appendChild(node);
})();
}
else
{
document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{behavior:url(#default#VML)}' +
mxClient.OFFICE_PREFIX + '\\:*{behavior:url(#default#VML)}';
}
if (mxLoadStylesheets)
{
mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css');
}
}
}
/**
* Copyright (c) 2006-2015, JGraph Ltd
* Copyright (c) 2006-2015, Gaudenz Alder
*/
var mxLog =
{
/**
* Class: mxLog
*
* A singleton class that implements a simple console.
*
* Variable: consoleName
*
* Specifies the name of the console window. Default is 'Console'.
*/
consoleName: 'Console',
/**
* Variable: TRACE
*
* Specified if the output for <enter> and <leave> should be visible in the
* console. Default is false.
*/
TRACE: false,
/**
* Variable: DEBUG
*
* Specifies if the output for <debug> should be visible in the console.
* Default is true.
*/
DEBUG: true,
/**
* Variable: WARN
*
* Specifies if the output for <warn> should be visible in the console.
* Default is true.
*/
WARN: true,
/**
* Variable: buffer
*
* Buffer for pre-initialized content.
*/
buffer: '',
/**
* Function: init
*
* Initializes the DOM node for the console. This requires document.body to
* point to a non-null value. This is called from within <setVisible> if the
* log has not yet been initialized.
*/
init: function()
{
if (mxLog.window == null && document.body != null)
{
var title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION;
// Creates a table that maintains the layout
var table = document.createElement('table');
table.setAttribute('width', '100%');
table.setAttribute('height', '100%');
var tbody = document.createElement('tbody');
var tr = document.createElement('tr');
var td = document.createElement('td');
td.style.verticalAlign = 'top';
// Adds the actual console as a textarea
mxLog.textarea = document.createElement('textarea');
mxLog.textarea.setAttribute('wrap', 'off');
mxLog.textarea.setAttribute('readOnly', 'true');
mxLog.textarea.style.height = '100%';
mxLog.textarea.style.resize = 'none';
mxLog.textarea.value = mxLog.buffer;
// Workaround for wrong width in standards mode
if (mxClient.IS_NS && document.compatMode != 'BackCompat')
{
mxLog.textarea.style.width = '99%';
}
else
{
mxLog.textarea.style.width = '100%';
}
td.appendChild(mxLog.textarea);
tr.appendChild(td);
tbody.appendChild(tr);
// Creates the container div
tr = document.createElement('tr');
mxLog.td = document.createElement('td');
mxLog.td.style.verticalAlign = 'top';
mxLog.td.setAttribute('height', '30px');
tr.appendChild(mxLog.td);
tbody.appendChild(tr);
table.appendChild(tbody);
// Adds various debugging buttons
mxLog.addButton('Info', function (evt)
{
mxLog.info();
});
mxLog.addButton('DOM', function (evt)
{
var content = mxUtils.getInnerHtml(document.body);
mxLog.debug(content);
});
mxLog.addButton('Trace', function (evt)
{
mxLog.TRACE = !mxLog.TRACE;
if (mxLog.TRACE)
{
mxLog.debug('Tracing enabled');
}
else
{
mxLog.debug('Tracing disabled');
}
});
mxLog.addButton('Copy', function (evt)
{
try
{
mxUtils.copy(mxLog.textarea.value);
}
catch (err)
{
mxUtils.alert(err);
}
});
mxLog.addButton('Show', function (evt)
{
try
{
mxUtils.popup(mxLog.textarea.value);
}
catch (err)
{
mxUtils.alert(err);
}
});
mxLog.addButton('Clear', function (evt)
{
mxLog.textarea.value = '';
});
// Cross-browser code to get window size
var h = 0;
var w = 0;
if (typeof(window.innerWidth) === 'number')
{
h = window.innerHeight;
w = window.innerWidth;
}
else
{
h = (document.documentElement.clientHeight || document.body.clientHeight);
w = document.body.clientWidth;
}
mxLog.window = new mxWindow(title, table, Math.max(0, w - 320), Math.max(0, h - 210), 300, 160);
mxLog.window.setMaximizable(true);
mxLog.window.setScrollable(false);
mxLog.window.setResizable(true);
mxLog.window.setClosable(true);
mxLog.window.destroyOnClose = false;
// Workaround for ignored textarea height in various setups
if (((mxClient.IS_NS || mxClient.IS_IE) && !mxClient.IS_GC &&
!mxClient.IS_SF && document.compatMode != 'BackCompat') ||
document.documentMode == 11)
{
var elt = mxLog.window.getElement();
var resizeHandler = function(sender, evt)
{
mxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70) + 'px';
};
mxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler);
mxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler);
mxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler);
mxLog.textarea.style.height = '92px';
}
}
},
/**
* Function: info
*
* Writes the current navigator information to the console.
*/
info: function()
{
mxLog.writeln(mxUtils.toString(navigator));
},
/**
* Function: addButton
*
* Adds a button to the console using the given label and function.
*/
addButton: function(lab, funct)
{
var button = document.createElement('button');
mxUtils.write(button, lab);
mxEvent.addListener(button, 'click', funct);
mxLog.td.appendChild(button);
},
/**
* Function: isVisible
*
* Returns true if the console is visible.
*/
isVisible: function()
{
if (mxLog.window != null)
{
return mxLog.window.isVisible();
}
return false;
},
/**
* Function: show
*
* Shows the console.
*/
show: function()
{
mxLog.setVisible(true);
},
/**
* Function: setVisible
*
* Shows or hides the console.
*/
setVisible: function(visible)
{
if (mxLog.window == null)
{
mxLog.init();
}
if (mxLog.window != null)
{
mxLog.window.setVisible(visible);
}
},
/**
* Function: enter
*
* Writes the specified string to the console
* if <TRACE> is true and returns the current
* time in milliseconds.
*
* Example:
*
* (code)
* mxLog.show();
* var t0 = mxLog.enter('Hello');
* // Do something
* mxLog.leave('World!', t0);
* (end)
*/
enter: function(string)
{
if (mxLog.TRACE)
{
mxLog.writeln('Entering '+string);
return new Date().getTime();
}
},
/**
* Function: leave
*
* Writes the specified string to the console
* if <TRACE> is true and computes the difference
* between the current time and t0 in milliseconds.
* See <enter> for an example.
*/
leave: function(string, t0)
{
if (mxLog.TRACE)
{
var dt = (t0 != 0) ? ' ('+(new Date().getTime() - t0)+' ms)' : '';
mxLog.writeln('Leaving '+string+dt);
}
},
/**
* Function: debug
*
* Adds all arguments to the console if <DEBUG> is enabled.
*
* Example:
*
* (code)
* mxLog.show();
* mxLog.debug('Hello, World!');
* (end)
*/
debug: function()
{
if (mxLog.DEBUG)
{
mxLog.writeln.apply(this, arguments);
}
},
/**
* Function: warn
*
* Adds all arguments to the console if <WARN> is enabled.
*
* Example:
*
* (code)
* mxLog.show();
* mxLog.warn('Hello, World!');
* (end)
*/
warn: function()
{
if (mxLog.WARN)
{
mxLog.writeln.apply(this, arguments);
}
},
/**
* Function: write
*
* Adds the specified strings to the console.
*/
write: function()
{
var string = '';
for (var i = 0; i < arguments.length; i++)
{
string += arguments[i];
if (i < arguments.length - 1)
{
string += ' ';
}
}
if (mxLog.textarea != null)
{
mxLog.textarea.value = mxLog.textarea.value + string;
// Workaround for no update in Presto 2.5.22 (Opera 10.5)
if (navigator.userAgent != null &&
navigator.userAgent.indexOf('Presto/2.5') >= 0)
{
mxLog.textarea.style.visibility = 'hidden';
mxLog.textarea.style.visibility = 'visible';
}
mxLog.textarea.scrollTop = mxLog.textarea.scrollHeight;
}
else
{
mxLog.buffer += string;
}
},
/**
* Function: writeln
*
* Adds the specified strings to the console, appending a linefeed at the
* end of each string.
*/
writeln: function()
{
var string = '';
for (var i = 0; i < arguments.length; i++)
{
string += arguments[i];
if (i < arguments.length - 1)
{
string += ' ';
}
}
mxLog.write(string + '\n');
}
};
/**
* Copyright (c) 2006-2015, JGraph Ltd
* Copyright (c) 2006-2015, Gaudenz Alder
*/
var mxObjectIdentity =
{
/**
* Class: mxObjectIdentity
*
* Identity for JavaScript objects and functions. This is implemented using
* a simple incrementing counter which is stored in each object under
* <FIELD_NAME>.
*
* The identity for an object does not change during its lifecycle.
*
* Variable: FIELD_NAME
*
* Name of the field to be used to store the object ID. Default is
* <code>mxObjectId</code>.
*/
FIELD_NAME: 'mxObjectId',
/**
* Variable: counter
*
* Current counter.
*/
counter: 0,
/**
* Function: get
*
* Returns the ID for the given object or function or null if no object
* is specified.
*/
get: function(obj)
{
if (obj != null)
{
if (obj[mxObjectIdentity.FIELD_NAME] == null)
{
if (typeof obj === 'object')
{
var ctor = mxUtils.getFunctionName(obj.constructor);
obj[mxObjectIdentity.FIELD_NAME] = ctor + '#' + mxObjectIdentity.counter++;
}
else if (typeof obj === 'function')
{
obj[mxObjectIdentity.FIELD_NAME] = 'Function#' + mxObjectIdentity.counter++;
}
}
return obj[mxObjectIdentity.FIELD_NAME];
}
return null;
},
/**
* Function: clear
*
* Deletes the ID from the given object or function.
*/
clear: function(obj)
{
if (typeof(obj) === 'object' || typeof obj === 'function')
{
delete obj[mxObjectIdentity.FIELD_NAME];
}
}
};
/**
* Copyright (c) 2006-2015, JGraph Ltd
* Copyright (c) 2006-2015, Gaudenz Alder
*/
/**
* Class: mxDictionary
*
* A wrapper class for an associative array with object keys. Note: This
* implementation uses <mxObjectIdentitiy> to turn object keys into strings.
*
* Constructor: mxEventSource
*
* Constructs a new dictionary which allows object to be used as keys.
*/
function mxDictionary()
{
this.clear();
};
/**
* Function: map
*
* Stores the (key, value) pairs in this dictionary.
*/
mxDictionary.prototype.map = null;
/**
* Function: clear
*
* Clears the dictionary.
*/
mxDictionary.prototype.clear = function()
{
this.map = {};
};
/**
* Function: get
*
* Returns the value for the given key.
*/
mxDictionary.prototype.get = function(key)
{
var id = mxObjectIdentity.get(key);
return this.map[id];
};
/**
* Function: put
*
* Stores the value under the given key and returns the previous
* value for that key.
*/
mxDictionary.prototype.put = function(key, value)
{
var id = mxObjectIdentity.get(key);
var previous = this.map[id];
this.map[id] = value;
return previous;
};
/**
* Function: remove
*
* Removes the value for the given key and returns the value that
* has been removed.
*/
mxDictionary.prototype.remove = function(key)
{
var id = mxObjectIdentity.get(key);
var previous = this.map[id];
delete this.map[id];
return previous;
};
/**
* Function: getKeys
*
* Returns all keys as an array.
*/
mxDictionary.prototype.getKeys = function()
{
var result = [];
for (var key in this.map)
{
result.push(key);
}
return result;
};
/**
* Function: getValues
*
* Returns all values as an array.
*/
mxDictionary.prototype.getValues = function()
{
var result = [];
for (var key in this.map)
{
result.push(this.map[key]);
}
return result;
};
/**
* Function: visit
*
* Visits all entries in the dictionary using the given function with the
* following signature: function(key, value) where key is a string and
* value is an object.
*
* Parameters:
*
* visitor - A function that takes the key and value as arguments.
*/
mxDictionary.prototype.visit = function(visitor)
{
for (var key in this.map)
{
visitor(key, this.map[key]);
}
};
/**
* Copyright (c) 2006-2016, JGraph Ltd
* Copyright (c) 2006-2016, Gaudenz Alder
*/
var mxResources =
{
/**
* Class: mxResources
*
* Implements internationalization. You can provide any number of
* resource files on the server using the following format for the
* filename: name[-en].properties. The en stands for any lowercase
* 2-character language shortcut (eg. de for german, fr for french).
*
* If the optional language extension is omitted, then the file is used as a
* default resource which is loaded in all cases. If a properties file for a
* specific language exists, then it is used to override the settings in the
* default resource. All entries in the file are of the form key=value. The
* values may then be accessed in code via <get>. Lines without
* equal signs in the properties files are ignored.
*
* Resource files may either be added programmatically using
* <add> or via a resource tag in the UI section of the
* editor configuration file, eg:
*
* (code)
* <mxEditor>
* <ui>
* <resource basename="examples/resources/mxWorkflow"/>
* (end)
*
* The above element will load examples/resources/mxWorkflow.properties as well
* as the language specific file for the current language, if it exists.
*
* Values may contain placeholders of the form {1}...{n} where each placeholder
* is replaced with the value of the corresponding array element in the params
* argument passed to <mxResources.get>. The placeholder {1} maps to the first
* element in the array (at index 0).
*
* See <mxClient.language> for more information on specifying the default
* language or disabling all loading of resources.
*
* Lines that start with a # sign will be ignored.
*
* Special characters
*
* To use unicode characters, use the standard notation (eg. \u8fd1) or %u as a
* prefix (eg. %u20AC will display a Euro sign). For normal hex encoded strings,
* use % as a prefix, eg. %F6 will display a "o umlaut" (ö).
*
* See <resourcesEncoded> to disable this. If you disable this, make sure that
* your files are UTF-8 encoded.
*
* Asynchronous loading
*
* By default, the core adds two resource files synchronously at load time.
* To load these files asynchronously, set <mxLoadResources> to false
* before loading mxClient.js and use <mxResources.loadResources> instead.
*
* Variable: resources
*
* Object that maps from keys to values.
*/
resources: {},
/**
* Variable: extension
*
* Specifies the extension used for language files. Default is <mxResourceExtension>.
*/
extension: mxResourceExtension,
/**
* Variable: resourcesEncoded
*
* Specifies whether or not values in resource files are encoded with \u or
* percentage. Default is false.
*/
resourcesEncoded: false,
/**
* Variable: loadDefaultBundle
*
* Specifies if the default file for a given basename should be loaded.
* Default is true.
*/
loadDefaultBundle: true,
/**
* Variable: loadDefaultBundle
*
* Specifies if the specific language file file for a given basename should
* be loaded. Default is true.
*/
loadSpecialBundle: true,
/**
* Function: isLanguageSupported
*
* Hook for subclassers to disable support for a given language. This
* implementation returns true if lan is in <mxClient.languages>.
*
* Parameters:
*
* lan - The current language.
*/
isLanguageSupported: function(lan)
{
if (mxClient.languages != null)
{
return mxUtils.indexOf(mxClient.languages, lan) >= 0;
}
return true;
},
/**
* Function: getDefaultBundle
*
* Hook for subclassers to return the URL for the special bundle. This
* implementation returns basename + <extension> or null if
* <loadDefaultBundle> is false.
*
* Parameters:
*
* basename - The basename for which the file should be loaded.
* lan - The current language.
*/
getDefaultBundle: function(basename, lan)
{
if (mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(lan))
{
return basename + mxResources.extension;
}
else
{
return null;
}
},
/**
* Function: getSpecialBundle
*
* Hook for subclassers to return the URL for the special bundle. This
* implementation returns basename + '_' + lan + <extension> or null if
* <loadSpecialBundle> is false or lan equals <mxClient.defaultLanguage>.
*
* If <mxResources.languages> is not null and <mxClient.language> contains
* a dash, then this method checks if <isLanguageSupported> returns true
* for the full language (including the dash). If that returns false the
* first part of the language (up to the dash) will be tried as an extension.
*
* If <mxResources.language> is null then the first part of the language is
* used to maintain backwards compatibility.
*
* Parameters:
*
* basename - The basename for which the file should be loaded.
* lan - The language for which the file should be loaded.
*/
getSpecialBundle: function(basename, lan)
{
if (mxClient.languages == null || !this.isLanguageSupported(lan))
{
var dash = lan.indexOf('-');
if (dash > 0)
{
lan = lan.substring(0, dash);
}
}
if (mxResources.loadSpecialBundle && mxResources.isLanguageSupported(lan) && lan != mxClient.defaultLanguage)
{
return basename + '_' + lan + mxResources.extension;
}
else
{
return null;
}
},
/**
* Function: add
*
* Adds the default and current language properties file for the specified
* basename. Existing keys are overridden as new files are added. If no
* callback is used then the request is synchronous.
*
* Example:
*
* At application startup, additional resources may be
* added using the following code:
*
* (code)
* mxResources.add('resources/editor');
* (end)
*
* Parameters:
*
* basename - The basename for which the file should be loaded.
* lan - The language for which the file should be loaded.
* callback - Optional callback for asynchronous loading.
*/
add: function(basename, lan, callback)
{
lan = (lan != null) ? lan : ((mxClient.language != null) ?
mxClient.language.toLowerCase() : mxConstants.NONE);
if (lan != mxConstants.NONE)
{
var defaultBundle = mxResources.getDefaultBundle(basename, lan);
var specialBundle = mxResources.getSpecialBundle(basename, lan);
var loadSpecialBundle = function()
{
if (specialBundle != null)
{
if (callback)
{
mxUtils.get(specialBundle, function(req)
{
mxResources.parse(req.getText());
callback();
}, function()
{
callback();
});
}
else
{
try
{
var req = mxUtils.load(specialBundle);
if (req.isReady())
{
mxResources.parse(req.getText());
}
}
catch (e)
{
// ignore
}
}
}
else if (callback != null)
{
callback();
}
}
if (defaultBundle != null)
{
if (callback)
{
mxUtils.get(defaultBundle, function(req)
{
mxResources.parse(req.getText());
loadSpecialBundle();
}, function()
{
loadSpecialBundle();
});
}
else
{
try
{
var req = mxUtils.load(defaultBundle);
if (req.isReady())
{
mxResources.parse(req.getText());
}
loadSpecialBundle();
}
catch (e)
{
// ignore
}
}
}
else
{
// Overlays the language specific file (_lan-extension)
loadSpecialBundle();
}
}
},
/**
* Function: parse
*
* Parses the key, value pairs in the specified
* text and stores them as local resources.
*/
parse: function(text)
{
if (text != null)
{
var lines = text.split('\n');
for (var i = 0; i < lines.length; i++)
{
if (lines[i].charAt(0) != '#')
{
var index = lines[i].indexOf('=');
if (index > 0)
{
var key = lines[i].substring(0, index);
var idx = lines[i].length;
if (lines[i].charCodeAt(idx - 1) == 13)
{
idx--;
}
var value = lines[i].substring(index + 1, idx);
if (this.resourcesEncoded)
{
value = value.replace(/\\(?=u[a-fA-F\d]{4})/g,"%");
mxResources.resources[key] = unescape(value);
}
else
{
mxResources.resources[key] = value;
}
}
}
}
}
},
/**
* Function: get
*
* Returns the value for the specified resource key.
*
* Example:
* To read the value for 'welomeMessage', use the following:
* (code)
* var result = mxResources.get('welcomeMessage') || '';
* (end)
*
* This would require an entry of the following form in
* one of the English language resource files:
* (code)
* welcomeMessage=Welcome to mxGraph!
* (end)
*
* The part behind the || is the string value to be used if the given
* resource is not available.
*
* Parameters:
*
* key - String that represents the key of the resource to be returned.
* params - Array of the values for the placeholders of the form {1}...{n}
* to be replaced with in the resulting string.
* defaultValue - Optional string that specifies the default return value.
*/
get: function(key, params, defaultValue)
{
var value = mxResources.resources[key];
// Applies the default value if no resource was found
if (value == null)
{
value = defaultValue;
}
// Replaces the placeholders with the values in the array
if (value != null && params != null)
{
value = mxResources.replacePlaceholders(value, params);
}
return value;
},
/**
* Function: replacePlaceholders
*
* Replaces the given placeholders with the given parameters.
*
* Parameters:
*
* value - String that contains the placeholders.
* params - Array of the values for the placeholders of the form {1}...{n}
* to be replaced with in the resulting string.
*/
replacePlaceholders: function(value, params)
{
var result = [];
var index = null;
for (var i = 0; i < value.length; i++)
{
var c = value.charAt(i);
if (c == '{')
{
index = '';
}
else if (index != null && c == '}')
{
index = parseInt(index)-1;
if (index >= 0 && index < params.length)
{
result.push(params[index]);
}
index = null;
}
else if (index != null)
{
index += c;
}
else
{
result.push(c);
}
}
return result.join('');
},
/**
* Function: loadResources
*
* Loads all required resources asynchronously. Use this to load the graph and
* editor resources if <mxLoadResources> is false.
*
* Parameters:
*
* callback - Callback function for asynchronous loading.
*/
loadResources: function(callback)
{
mxResources.add(mxClient.basePath+'/resources/editor', null, function()
{
mxResources.add(mxClient.basePath+'/resources/graph', null, callback);
});
}
};
/**
* Copyright (c) 2006-2015, JGraph Ltd
* Copyright (c) 2006-2015, Gaudenz Alder
*/
/**
* Class: mxPoint
*
* Implements a 2-dimensional vector with double precision coordinates.
*
* Constructor: mxPoint
*
* Constructs a new point for the optional x and y coordinates. If no
* coordinates are given, then the default values for <x> and <y> are used.
*/
function mxPoint(x, y)
{
this.x = (x != null) ? x : 0;
this.y = (y != null) ? y : 0;
};
/**
* Variable: x
*
* Holds the x-coordinate of the point. Default is 0.
*/
mxPoint.prototype.x = null;
/**
* Variable: y
*
* Holds the y-coordinate of the point. Default is 0.
*/
mxPoint.prototype.y = null;
/**
* Function: equals
*
* Returns true if the given object equals this point.
*/
mxPoint.prototype.equals = function(obj)
{
return obj != null && obj.x == this.x && obj.y == this.y;
};
/**
* Function: clone
*
* Returns a clone of this <mxPoint>.
*/
mxPoint.prototype.clone = function()
{
// Handles subclasses as well
return mxUtils.clone(this);
};
/**
* Copyright (c) 2006-2015, JGraph Ltd
* Copyright (c) 2006-2015, Gaudenz Alder
*/
/**
* Class: mxRectangle
*
* Extends <mxPoint> to implement a 2-dimensional rectangle with double
* precision coordinates.
*
* Constructor: mxRectangle
*
* Constructs a new rectangle for the optional parameters. If no parameters
* are given then the respective default values are used.
*/
function mxRectangle(x, y, width, height)
{
mxPoint.call(this, x, y);
this.width = (width != null) ? width : 0;
this.height = (height != null) ? height : 0;
};
/**
* Extends mxPoint.
*/
mxRectangle.prototype = new mxPoint();
mxRectangle.prototype.constructor = mxRectangle;
/**
* Variable: width
*
* Holds the width of the rectangle. Default is 0.
*/
mxRectangle.prototype.width = null;
/**
* Variable: height
*
* Holds the height of the rectangle. Default is 0.
*/
mxRectangle.prototype.height = null;
/**
* Function: setRect
*
* Sets this rectangle to the specified values
*/
mxRectangle.prototype.setRect = function(x, y, w, h)
{
this.x = x;
this.y = y;
this.width = w;
this.height = h;
};
/**
* Function: getCenterX
*
* Returns the x-coordinate of the center point.
*/
mxRectangle.prototype.getCenterX = function ()
{
return this.x + this.width/2;
};
/**
* Function: getCenterY
*
* Returns the y-coordinate of the center point.
*/
mxRectangle.prototype.getCenterY = function ()
{
return this.y + this.height/2;
};
/**
* Function: add
*
* Adds the given rectangle to this rectangle.
*/
mxRectangle.prototype.add = function(rect)
{
if (rect != null)
{
var minX = Math.min(this.x, rect.x);
var minY = Math.min(this.y, rect.y);
var maxX = Math.max(this.x + this.width, rect.x + rect.width);
var maxY = Math.max(this.y + this.height, rect.y + rect.height);
this.x = minX;
this.y = minY;
this.width = maxX - minX;
this.height = maxY - minY;
}
};
/**
* Function: intersect
*
* Changes this rectangle to where it overlaps with the given rectangle.
*/
mxRectangle.prototype.intersect = function(rect)
{
if (rect != null)
{
var r1 = this.x + this.width;
var r2 = rect.x + rect.width;
var b1 = this.y + this.height;
var b2 = rect.y + rect.height;
this.x = Math.max(this.x, rect.x);
this.y = Math.max(this.y, rect.y);
this.width = Math.min(r1, r2) - this.x;
this.height = Math.min(b1, b2) - this.y;
}
};
/**
* Function: grow
*
* Grows the rectangle by the given amount, that is, this method subtracts
* the given amount from the x- and y-coordinates and adds twice the amount
* to the width and height.
*/
mxRectangle.prototype.grow = function(amount)
{
this.x -= amount;
this.y -= amount;
this.width += 2 * amount;
this.height += 2 * amount;
return this;
};
/**
* Function: getPoint
*
* Returns the top, left corner as a new <mxPoint>.
*/
mxRectangle.prototype.getPoint = function()
{
return new mxPoint(this.x, this.y);
};
/**
* Function: rotate90
*
* Rotates this rectangle by 90 degree around its center point.
*/
mxRectangle.prototype.rotate90 = function()
{
var t = (this.width - this.height) / 2;
this.x += t;
this.y -= t;
var tmp = this.width;
this.width = this.height;
this.height = tmp;
};
/**
* Function: equals
*
* Returns true if the given object equals this rectangle.
*/
mxRectangle.prototype.equals = function(obj)
{
return obj != null && obj.x == this.x && obj.y == this.y &&
obj.width == this.width && obj.height == this.height;
};
/**
* Function: fromRectangle
*
* Returns a new <mxRectangle> which is a copy of the given rectangle.
*/
mxRectangle.fromRectangle = function(rect)
{
return new mxRectangle(rect.x, rect.y, rect.width, rect.height);
};
/**
* Copyright (c) 2006-2015, JGraph Ltd
* Copyright (c) 2006-2015, Gaudenz Alder
*/
var mxEffects =
{
/**
* Class: mxEffects
*
* Provides animation effects.
*/
/**
* Function: animateChanges
*
* Asynchronous animated move operation. See also: <mxMorphing>.
*
* Example:
*
* (code)
* graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
* {
* var changes = evt.getProperty('edit').changes;
*
* if (changes.length < 10)
* {
* mxEffects.animateChanges(graph, changes);
* }
* });
* (end)
*
* Parameters:
*
* graph - <mxGraph> that received the changes.
* changes - Array of changes to be animated.
* done - Optional function argument that is invoked after the
* last step of the animation.
*/
animateChanges: function(graph, changes, done)
{
var maxStep = 10;
var step = 0;
var animate = function()
{
var isRequired = false;
for (var i = 0; i < changes.length; i++)
{
var change = changes[i];
if (change instanceof mxGeometryChange ||
change instanceof mxTerminalChange ||
change instanceof mxValueChange ||
change instanceof mxChildChange ||
change instanceof mxStyleChange)
{
var state = graph.getView().getState(change.cell || change.child, false);
if (state != null)
{
isRequired = true;
if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell))
{
mxUtils.setOpacity(state.shape.node, 100 * step / maxStep);
}
else
{
var scale = graph.getView().scale;
var dx = (change.geometry.x - change.previous.x) * scale;
var dy = (change.geometry.y - change.previous.y) * scale;
var sx = (change.geometry.width - change.previous.width) * scale;
var sy = (change.geometry.height - change.previous.height) * scale;
if (step == 0)
{
state.x -= dx;
state.y -= dy;
state.width -= sx;
state.height -= sy;
}
else
{
state.x += dx / maxStep;
state.y += dy / maxStep;
state.width += sx / maxStep;
state.height += sy / maxStep;
}
graph.cellRenderer.redraw(state);
// Fades all connected edges and children
mxEffects.cascadeOpacity(graph, change.cell, 100 * step / maxStep);
}
}
}
}
if (step < maxStep && isRequired)
{
step++;
window.setTimeout(animate, delay);
}
else if (done != null)
{
done();
}
};
var delay = 30;
animate();
},
/**
* Function: cascadeOpacity
*
* Sets the opacity on the given cell and its descendants.
*
* Parameters:
*
* graph - <mxGraph> that contains the cells.
* cell - <mxCell> to set the opacity for.
* opacity - New value for the opacity in %.
*/
cascadeOpacity: function(graph, cell, opacity)
{
// Fades all children
var childCount = graph.model.getChildCount(cell);
for (var i=0; i<childCount; i++)
{
var child = graph.model.getChildAt(cell, i);
var childState = graph.getView().getState(child);
if (childState != null)
{
mxUtils.setOpacity(childState.shape.node, opacity);
mxEffects.cascadeOpacity(graph, child, opacity);
}
}
// Fades all connected edges
var edges = graph.model.getEdges(cell);
if (edges != null)
{
for (var i=0; i<edges.length; i++)
{
var edgeState = graph.getView().getState(edges[i]);
if (edgeState != null)
{
mxUtils.setOpacity(edgeState.shape.node, opacity);
}
}
}
},
/**
* Function: fadeOut
*
* Asynchronous fade-out operation.
*/
fadeOut: function(node, from, remove, step, delay, isEnabled)
{
step = step || 40;
delay = delay || 30;
var opacity = from || 100;
mxUtils.setOpacity(node, opacity);
if (isEnabled || isEnabled == null)
{
var f = function()
{
opacity = Math.max(opacity-step, 0);
mxUtils.setOpacity(node, opa