UNPKG

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
/** * 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" (&ouml;). * * 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