UNPKG

mxgraph

Version:

mxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.

2,019 lines (1,821 loc) 75 kB
/** * Copyright (c) 2006-2019, JGraph Ltd * Copyright (c) 2006-2019, draw.io AG */ /** * Class: mxEditor * * Extends <mxEventSource> to implement an application wrapper for a graph that * adds <actions>, I/O using <mxCodec>, auto-layout using <mxLayoutManager>, * command history using <undoManager>, and standard dialogs and widgets, eg. * properties, help, outline, toolbar, and popupmenu. It also adds <templates> * to be used as cells in toolbars, auto-validation using the <validation> * flag, attribute cycling using <cycleAttributeValues>, higher-level events * such as <root>, and backend integration using <urlPost> and <urlImage>. * * Actions: * * Actions are functions stored in the <actions> array under their names. The * functions take the <mxEditor> as the first, and an optional <mxCell> as the * second argument and are invoked using <execute>. Any additional arguments * passed to execute are passed on to the action as-is. * * A list of built-in actions is available in the <addActions> description. * * Read/write Diagrams: * * To read a diagram from an XML string, for example from a textfield within the * page, the following code is used: * * (code) * var doc = mxUtils.parseXML(xmlString); * var node = doc.documentElement; * editor.readGraphModel(node); * (end) * * For reading a diagram from a remote location, use the <open> method. * * To save diagrams in XML on a server, you can set the <urlPost> variable. * This variable will be used in <getUrlPost> to construct a URL for the post * request that is issued in the <save> method. The post request contains the * XML representation of the diagram as returned by <writeGraphModel> in the * xml parameter. * * On the server side, the post request is processed using standard * technologies such as Java Servlets, CGI, .NET or ASP. * * Here are some examples of processing a post request in various languages. * * - Java: URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;") * * Note that the linefeeds should only be replaced if the XML is * processed in Java, for example when creating an image, but not * if the XML is passed back to the client-side. * * - .NET: HttpUtility.UrlDecode(context.Request.Params["xml"]) * - PHP: urldecode($_POST["xml"]) * * Creating images: * * A backend (Java, PHP or C#) is required for creating images. The * distribution contains an example for each backend (ImageHandler.java, * ImageHandler.cs and graph.php). More information about using a backend * to create images can be found in the readme.html files. Note that the * preview is implemented using VML/SVG in the browser and does not require * a backend. The backend is only required to creates images (bitmaps). * * Special characters: * * Note There are five characters that should always appear in XML content as * escapes, so that they do not interact with the syntax of the markup. These * are part of the language for all documents based on XML and for HTML. * * - &lt; (<) * - &gt; (>) * - &amp; (&) * - &quot; (") * - &apos; (') * * Although it is part of the XML language, &apos; is not defined in HTML. * For this reason the XHTML specification recommends instead the use of * &#39; if text may be passed to a HTML user agent. * * If you are having problems with special characters on the server-side then * you may want to try the <escapePostData> flag. * * For converting decimal escape sequences inside strings, a user has provided * us with the following function: * * (code) * function html2js(text) * { * var entitySearch = /&#[0-9]+;/; * var entity; * * while (entity = entitySearch.exec(text)) * { * var charCode = entity[0].substring(2, entity[0].length -1); * text = text.substring(0, entity.index) * + String.fromCharCode(charCode) * + text.substring(entity.index + entity[0].length); * } * * return text; * } * (end) * * Otherwise try using hex escape sequences and the built-in unescape function * for converting such strings. * * Local Files: * * For saving and opening local files, no standardized method exists that * works across all browsers. The recommended way of dealing with local files * is to create a backend that streams the XML data back to the browser (echo) * as an attachment so that a Save-dialog is displayed on the client-side and * the file can be saved to the local disk. * * For example, in PHP the code that does this looks as follows. * * (code) * $xml = stripslashes($_POST["xml"]); * header("Content-Disposition: attachment; filename=\"diagram.xml\""); * echo($xml); * (end) * * To open a local file, the file should be uploaded via a form in the browser * and then opened from the server in the editor. * * Cell Properties: * * The properties displayed in the properties dialog are the attributes and * values of the cell's user object, which is an XML node. The XML node is * defined in the templates section of the config file. * * The templates are stored in <mxEditor.templates> and contain cells which * are cloned at insertion time to create new vertices by use of drag and * drop from the toolbar. Each entry in the toolbar for adding a new vertex * must refer to an existing template. * * In the following example, the task node is a business object and only the * mxCell node and its mxGeometry child contain graph information: * * (code) * <Task label="Task" description=""> * <mxCell vertex="true"> * <mxGeometry as="geometry" width="72" height="32"/> * </mxCell> * </Task> * (end) * * The idea is that the XML representation is inverse from the in-memory * representation: The outer XML node is the user object and the inner node is * the cell. This means the user object of the cell is the Task node with no * children for the above example: * * (code) * <Task label="Task" description=""/> * (end) * * The Task node can have any tag name, attributes and child nodes. The * <mxCodec> will use the XML hierarchy as the user object, while removing the * "known annotations", such as the mxCell node. At save-time the cell data * will be "merged" back into the user object. The user object is only modified * via the properties dialog during the lifecycle of the cell. * * In the default implementation of <createProperties>, the user object's * attributes are put into a form for editing. Attributes are changed using * the <mxCellAttributeChange> action in the model. The dialog can be replaced * by overriding the <createProperties> hook or by replacing the showProperties * action in <actions>. Alternatively, the entry in the config file's popupmenu * section can be modified to invoke a different action. * * If you want to displey the properties dialog on a doubleclick, you can set * <mxEditor.dblClickAction> to showProperties as follows: * * (code) * editor.dblClickAction = 'showProperties'; * (end) * * Popupmenu and Toolbar: * * The toolbar and popupmenu are typically configured using the respective * sections in the config file, that is, the popupmenu is defined as follows: * * (code) * <mxEditor> * <mxDefaultPopupMenu as="popupHandler"> * <add as="cut" action="cut" icon="images/cut.gif"/> * ... * (end) * * New entries can be added to the toolbar by inserting an add-node into the * above configuration. Existing entries may be removed and changed by * modifying or removing the respective entries in the configuration. * The configuration is read by the <mxDefaultPopupMenuCodec>, the format of the * configuration is explained in <mxDefaultPopupMenu.decode>. * * The toolbar is defined in the mxDefaultToolbar section. Items can be added * and removed in this section. * * (code) * <mxEditor> * <mxDefaultToolbar> * <add as="save" action="save" icon="images/save.gif"/> * <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"/> * ... * (end) * * The format of the configuration is described in * <mxDefaultToolbarCodec.decode>. * * Ids: * * For the IDs, there is an implicit behaviour in <mxCodec>: It moves the Id * from the cell to the user object at encoding time and vice versa at decoding * time. For example, if the Task node from above has an id attribute, then * the <mxCell.id> of the corresponding cell will have this value. If there * is no Id collision in the model, then the cell may be retrieved using this * Id with the <mxGraphModel.getCell> function. If there is a collision, a new * Id will be created for the cell using <mxGraphModel.createId>. At encoding * time, this new Id will replace the value previously stored under the id * attribute in the Task node. * * See <mxEditorCodec>, <mxDefaultToolbarCodec> and <mxDefaultPopupMenuCodec> * for information about configuring the editor and user interface. * * Programmatically inserting cells: * * For inserting a new cell, say, by clicking a button in the document, * the following code can be used. This requires an reference to the editor. * * (code) * var userObject = new Object(); * var parent = editor.graph.getDefaultParent(); * var model = editor.graph.model; * model.beginUpdate(); * try * { * editor.graph.insertVertex(parent, null, userObject, 20, 20, 80, 30); * } * finally * { * model.endUpdate(); * } * (end) * * If a template cell from the config file should be inserted, then a clone * of the template can be created as follows. The clone is then inserted using * the add function instead of addVertex. * * (code) * var template = editor.templates['task']; * var clone = editor.graph.model.cloneCell(template); * (end) * * Resources: * * resources/editor - Language resources for mxEditor * * Callback: onInit * * Called from within the constructor. In the callback, * "this" refers to the editor instance. * * Cookie: mxgraph=seen * * Set when the editor is started. Never expires. Use * <resetFirstTime> to reset this cookie. This cookie * only exists if <onInit> is implemented. * * Event: mxEvent.OPEN * * Fires after a file was opened in <open>. The <code>filename</code> property * contains the filename that was used. The same value is also available in * <filename>. * * Event: mxEvent.SAVE * * Fires after the current file was saved in <save>. The <code>url</code> * property contains the URL that was used for saving. * * Event: mxEvent.POST * * Fires if a successful response was received in <postDiagram>. The * <code>request</code> property contains the <mxXmlRequest>, the * <code>url</code> and <code>data</code> properties contain the URL and the * data that were used in the post request. * * Event: mxEvent.ROOT * * Fires when the current root has changed, or when the title of the current * root has changed. This event has no properties. * * Event: mxEvent.BEFORE_ADD_VERTEX * * Fires before a vertex is added in <addVertex>. The <code>vertex</code> * property contains the new vertex and the <code>parent</code> property * contains its parent. * * Event: mxEvent.ADD_VERTEX * * Fires between begin- and endUpdate in <addVertex>. The <code>vertex</code> * property contains the vertex that is being inserted. * * Event: mxEvent.AFTER_ADD_VERTEX * * Fires after a vertex was inserted and selected in <addVertex>. The * <code>vertex</code> property contains the new vertex. * * Example: * * For starting an in-place edit after a new vertex has been added to the * graph, the following code can be used. * * (code) * editor.addListener(mxEvent.AFTER_ADD_VERTEX, function(sender, evt) * { * var vertex = evt.getProperty('vertex'); * * if (editor.graph.isCellEditable(vertex)) * { * editor.graph.startEditingAtCell(vertex); * } * }); * (end) * * Event: mxEvent.ESCAPE * * Fires when the escape key is pressed. The <code>event</code> property * contains the key event. * * Constructor: mxEditor * * Constructs a new editor. This function invokes the <onInit> callback * upon completion. * * Example: * * (code) * var config = mxUtils.load('config/diagrameditor.xml').getDocumentElement(); * var editor = new mxEditor(config); * (end) * * Parameters: * * config - Optional XML node that contains the configuration. */ function mxEditor(config) { this.actions = []; this.addActions(); // Executes the following only if a document has been instanciated. // That is, don't execute when the editorcodec is setup. if (document.body != null) { // Defines instance fields this.cycleAttributeValues = []; this.popupHandler = new mxDefaultPopupMenu(); this.undoManager = new mxUndoManager(); // Creates the graph and toolbar without the containers this.graph = this.createGraph(); this.toolbar = this.createToolbar(); // Creates the global keyhandler (requires graph instance) this.keyHandler = new mxDefaultKeyHandler(this); // Configures the editor using the URI // which was passed to the ctor this.configure(config); // Assigns the swimlaneIndicatorColorAttribute on the graph this.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName; // Checks if the <onInit> hook has been set if (this.onInit != null) { // Invokes the <onInit> hook this.onInit(); } // Automatic deallocation of memory if (mxClient.IS_IE) { mxEvent.addListener(window, 'unload', mxUtils.bind(this, function() { this.destroy(); })); } } }; /** * Installs the required language resources at class * loading time. */ if (mxLoadResources) { mxResources.add(mxClient.basePath + '/resources/editor'); } else { mxClient.defaultBundles.push(mxClient.basePath + '/resources/editor'); } /** * Extends mxEventSource. */ mxEditor.prototype = new mxEventSource(); mxEditor.prototype.constructor = mxEditor; /** * Group: Controls and Handlers */ /** * Variable: askZoomResource * * Specifies the resource key for the zoom dialog. If the resource for this * key does not exist then the value is used as the error message. Default * is 'askZoom'. */ mxEditor.prototype.askZoomResource = (mxClient.language != 'none') ? 'askZoom' : ''; /** * Variable: lastSavedResource * * Specifies the resource key for the last saved info. If the resource for * this key does not exist then the value is used as the error message. * Default is 'lastSaved'. */ mxEditor.prototype.lastSavedResource = (mxClient.language != 'none') ? 'lastSaved' : ''; /** * Variable: currentFileResource * * Specifies the resource key for the current file info. If the resource for * this key does not exist then the value is used as the error message. * Default is 'currentFile'. */ mxEditor.prototype.currentFileResource = (mxClient.language != 'none') ? 'currentFile' : ''; /** * Variable: propertiesResource * * Specifies the resource key for the properties window title. If the * resource for this key does not exist then the value is used as the * error message. Default is 'properties'. */ mxEditor.prototype.propertiesResource = (mxClient.language != 'none') ? 'properties' : ''; /** * Variable: tasksResource * * Specifies the resource key for the tasks window title. If the * resource for this key does not exist then the value is used as the * error message. Default is 'tasks'. */ mxEditor.prototype.tasksResource = (mxClient.language != 'none') ? 'tasks' : ''; /** * Variable: helpResource * * Specifies the resource key for the help window title. If the * resource for this key does not exist then the value is used as the * error message. Default is 'help'. */ mxEditor.prototype.helpResource = (mxClient.language != 'none') ? 'help' : ''; /** * Variable: outlineResource * * Specifies the resource key for the outline window title. If the * resource for this key does not exist then the value is used as the * error message. Default is 'outline'. */ mxEditor.prototype.outlineResource = (mxClient.language != 'none') ? 'outline' : ''; /** * Variable: outline * * Reference to the <mxWindow> that contains the outline. The <mxOutline> * is stored in outline.outline. */ mxEditor.prototype.outline = null; /** * Variable: graph * * Holds a <mxGraph> for displaying the diagram. The graph * is created in <setGraphContainer>. */ mxEditor.prototype.graph = null; /** * Variable: graphRenderHint * * Holds the render hint used for creating the * graph in <setGraphContainer>. See <mxGraph>. * Default is null. */ mxEditor.prototype.graphRenderHint = null; /** * Variable: toolbar * * Holds a <mxDefaultToolbar> for displaying the toolbar. The * toolbar is created in <setToolbarContainer>. */ mxEditor.prototype.toolbar = null; /** * Variable: status * * DOM container that holds the statusbar. Default is null. * Use <setStatusContainer> to set this value. */ mxEditor.prototype.status = null; /** * Variable: popupHandler * * Holds a <mxDefaultPopupMenu> for displaying * popupmenus. */ mxEditor.prototype.popupHandler = null; /** * Variable: undoManager * * Holds an <mxUndoManager> for the command history. */ mxEditor.prototype.undoManager = null; /** * Variable: keyHandler * * Holds a <mxDefaultKeyHandler> for handling keyboard events. * The handler is created in <setGraphContainer>. */ mxEditor.prototype.keyHandler = null; /** * Group: Actions and Options */ /** * Variable: actions * * Maps from actionnames to actions, which are functions taking * the editor and the cell as arguments. Use <addAction> * to add or replace an action and <execute> to execute an action * by name, passing the cell to be operated upon as the second * argument. */ mxEditor.prototype.actions = null; /** * Variable: dblClickAction * * Specifies the name of the action to be executed * when a cell is double clicked. Default is 'edit'. * * To handle a singleclick, use the following code. * * (code) * editor.graph.addListener(mxEvent.CLICK, function(sender, evt) * { * var e = evt.getProperty('event'); * var cell = evt.getProperty('cell'); * * if (cell != null && !e.isConsumed()) * { * // Do something useful with cell... * e.consume(); * } * }); * (end) */ mxEditor.prototype.dblClickAction = 'edit'; /** * Variable: swimlaneRequired * * Specifies if new cells must be inserted * into an existing swimlane. Otherwise, cells * that are not swimlanes can be inserted as * top-level cells. Default is false. */ mxEditor.prototype.swimlaneRequired = false; /** * Variable: disableContextMenu * * Specifies if the context menu should be disabled in the graph container. * Default is true. */ mxEditor.prototype.disableContextMenu = true; /** * Group: Templates */ /** * Variable: insertFunction * * Specifies the function to be used for inserting new * cells into the graph. This is assigned from the * <mxDefaultToolbar> if a vertex-tool is clicked. */ mxEditor.prototype.insertFunction = null; /** * Variable: forcedInserting * * Specifies if a new cell should be inserted on a single * click even using <insertFunction> if there is a cell * under the mousepointer, otherwise the cell under the * mousepointer is selected. Default is false. */ mxEditor.prototype.forcedInserting = false; /** * Variable: templates * * Maps from names to protoype cells to be used * in the toolbar for inserting new cells into * the diagram. */ mxEditor.prototype.templates = null; /** * Variable: defaultEdge * * Prototype edge cell that is used for creating * new edges. */ mxEditor.prototype.defaultEdge = null; /** * Variable: defaultEdgeStyle * * Specifies the edge style to be returned in <getEdgeStyle>. * Default is null. */ mxEditor.prototype.defaultEdgeStyle = null; /** * Variable: defaultGroup * * Prototype group cell that is used for creating * new groups. */ mxEditor.prototype.defaultGroup = null; /** * Variable: groupBorderSize * * Default size for the border of new groups. If null, * then then <mxGraph.gridSize> is used. Default is * null. */ mxEditor.prototype.groupBorderSize = null; /** * Group: Backend Integration */ /** * Variable: filename * * Contains the URL of the last opened file as a string. * Default is null. */ mxEditor.prototype.filename = null; /** * Variable: lineFeed * * Character to be used for encoding linefeeds in <save>. Default is '&#xa;'. */ mxEditor.prototype.linefeed = '&#xa;'; /** * Variable: postParameterName * * Specifies if the name of the post parameter that contains the diagram * data in a post request to the server. Default is 'xml'. */ mxEditor.prototype.postParameterName = 'xml'; /** * Variable: escapePostData * * Specifies if the data in the post request for saving a diagram * should be converted using encodeURIComponent. Default is true. */ mxEditor.prototype.escapePostData = true; /** * Variable: urlPost * * Specifies the URL to be used for posting the diagram * to a backend in <save>. */ mxEditor.prototype.urlPost = null; /** * Variable: urlImage * * Specifies the URL to be used for creating a bitmap of * the graph in the image action. */ mxEditor.prototype.urlImage = null; /** * Group: Autolayout */ /** * Variable: horizontalFlow * * Specifies the direction of the flow * in the diagram. This is used in the * layout algorithms. Default is false, * ie. vertical flow. */ mxEditor.prototype.horizontalFlow = false; /** * Variable: layoutDiagram * * Specifies if the top-level elements in the * diagram should be layed out using a vertical * or horizontal stack depending on the setting * of <horizontalFlow>. The spacing between the * swimlanes is specified by <swimlaneSpacing>. * Default is false. * * If the top-level elements are swimlanes, then * the intra-swimlane layout is activated by * the <layoutSwimlanes> switch. */ mxEditor.prototype.layoutDiagram = false; /** * Variable: swimlaneSpacing * * Specifies the spacing between swimlanes if * automatic layout is turned on in * <layoutDiagram>. Default is 0. */ mxEditor.prototype.swimlaneSpacing = 0; /** * Variable: maintainSwimlanes * * Specifies if the swimlanes should be kept at the same * width or height depending on the setting of * <horizontalFlow>. Default is false. * * For horizontal flows, all swimlanes * have the same height and for vertical flows, all swimlanes * have the same width. Furthermore, the swimlanes are * automatically "stacked" if <layoutDiagram> is true. */ mxEditor.prototype.maintainSwimlanes = false; /** * Variable: layoutSwimlanes * * Specifies if the children of swimlanes should * be layed out, either vertically or horizontally * depending on <horizontalFlow>. * Default is false. */ mxEditor.prototype.layoutSwimlanes = false; /** * Group: Attribute Cycling */ /** * Variable: cycleAttributeValues * * Specifies the attribute values to be cycled when * inserting new swimlanes. Default is an empty * array. */ mxEditor.prototype.cycleAttributeValues = null; /** * Variable: cycleAttributeIndex * * Index of the last consumed attribute index. If a new * swimlane is inserted, then the <cycleAttributeValues> * at this index will be used as the value for * <cycleAttributeName>. Default is 0. */ mxEditor.prototype.cycleAttributeIndex = 0; /** * Variable: cycleAttributeName * * Name of the attribute to be assigned a <cycleAttributeValues> * when inserting new swimlanes. Default is 'fillColor'. */ mxEditor.prototype.cycleAttributeName = 'fillColor'; /** * Group: Windows */ /** * Variable: tasks * * Holds the <mxWindow> created in <showTasks>. */ mxEditor.prototype.tasks = null; /** * Variable: tasksWindowImage * * Icon for the tasks window. */ mxEditor.prototype.tasksWindowImage = null; /** * Variable: tasksTop * * Specifies the top coordinate of the tasks window in pixels. * Default is 20. */ mxEditor.prototype.tasksTop = 20; /** * Variable: help * * Holds the <mxWindow> created in <showHelp>. */ mxEditor.prototype.help = null; /** * Variable: helpWindowImage * * Icon for the help window. */ mxEditor.prototype.helpWindowImage = null; /** * Variable: urlHelp * * Specifies the URL to be used for the contents of the * Online Help window. This is usually specified in the * resources file under urlHelp for language-specific * online help support. */ mxEditor.prototype.urlHelp = null; /** * Variable: helpWidth * * Specifies the width of the help window in pixels. * Default is 300. */ mxEditor.prototype.helpWidth = 300; /** * Variable: helpHeight * * Specifies the height of the help window in pixels. * Default is 260. */ mxEditor.prototype.helpHeight = 260; /** * Variable: propertiesWidth * * Specifies the width of the properties window in pixels. * Default is 240. */ mxEditor.prototype.propertiesWidth = 240; /** * Variable: propertiesHeight * * Specifies the height of the properties window in pixels. * If no height is specified then the window will be automatically * sized to fit its contents. Default is null. */ mxEditor.prototype.propertiesHeight = null; /** * Variable: movePropertiesDialog * * Specifies if the properties dialog should be automatically * moved near the cell it is displayed for, otherwise the * dialog is not moved. This value is only taken into * account if the dialog is already visible. Default is false. */ mxEditor.prototype.movePropertiesDialog = false; /** * Variable: validating * * Specifies if <mxGraph.validateGraph> should automatically be invoked after * each change. Default is false. */ mxEditor.prototype.validating = false; /** * Variable: modified * * True if the graph has been modified since it was last saved. */ mxEditor.prototype.modified = false; /** * Function: isModified * * Returns <modified>. */ mxEditor.prototype.isModified = function () { return this.modified; }; /** * Function: setModified * * Sets <modified> to the specified boolean value. */ mxEditor.prototype.setModified = function (value) { this.modified = value; }; /** * Function: addActions * * Adds the built-in actions to the editor instance. * * save - Saves the graph using <urlPost>. * print - Shows the graph in a new print preview window. * show - Shows the graph in a new window. * exportImage - Shows the graph as a bitmap image using <getUrlImage>. * refresh - Refreshes the graph's display. * cut - Copies the current selection into the clipboard * and removes it from the graph. * copy - Copies the current selection into the clipboard. * paste - Pastes the clipboard into the graph. * delete - Removes the current selection from the graph. * group - Puts the current selection into a new group. * ungroup - Removes the selected groups and selects the children. * undo - Undoes the last change on the graph model. * redo - Redoes the last change on the graph model. * zoom - Sets the zoom via a dialog. * zoomIn - Zooms into the graph. * zoomOut - Zooms out of the graph * actualSize - Resets the scale and translation on the graph. * fit - Changes the scale so that the graph fits into the window. * showProperties - Shows the properties dialog. * selectAll - Selects all cells. * selectNone - Clears the selection. * selectVertices - Selects all vertices. * selectEdges = Selects all edges. * edit - Starts editing the current selection cell. * enterGroup - Drills down into the current selection cell. * exitGroup - Moves up in the drilling hierachy * home - Moves to the topmost parent in the drilling hierarchy * selectPrevious - Selects the previous cell. * selectNext - Selects the next cell. * selectParent - Selects the parent of the selection cell. * selectChild - Selects the first child of the selection cell. * collapse - Collapses the currently selected cells. * expand - Expands the currently selected cells. * bold - Toggle bold text style. * italic - Toggle italic text style. * underline - Toggle underline text style. * alignCellsLeft - Aligns the selection cells at the left. * alignCellsCenter - Aligns the selection cells in the center. * alignCellsRight - Aligns the selection cells at the right. * alignCellsTop - Aligns the selection cells at the top. * alignCellsMiddle - Aligns the selection cells in the middle. * alignCellsBottom - Aligns the selection cells at the bottom. * alignFontLeft - Sets the horizontal text alignment to left. * alignFontCenter - Sets the horizontal text alignment to center. * alignFontRight - Sets the horizontal text alignment to right. * alignFontTop - Sets the vertical text alignment to top. * alignFontMiddle - Sets the vertical text alignment to middle. * alignFontBottom - Sets the vertical text alignment to bottom. * toggleTasks - Shows or hides the tasks window. * toggleHelp - Shows or hides the help window. * toggleOutline - Shows or hides the outline window. * toggleConsole - Shows or hides the console window. */ mxEditor.prototype.addActions = function () { this.addAction('save', function(editor) { editor.save(); }); this.addAction('print', function(editor) { var preview = new mxPrintPreview(editor.graph, 1); preview.open(); }); this.addAction('show', function(editor) { mxUtils.show(editor.graph, null, 10, 10); }); this.addAction('exportImage', function(editor) { var url = editor.getUrlImage(); if (url == null || mxClient.IS_LOCAL) { editor.execute('show'); } else { var node = mxUtils.getViewXml(editor.graph, 1); var xml = mxUtils.getXml(node, '\n'); mxUtils.submit(url, editor.postParameterName + '=' + encodeURIComponent(xml), document, '_blank'); } }); this.addAction('refresh', function(editor) { editor.graph.refresh(); }); this.addAction('cut', function(editor) { if (editor.graph.isEnabled()) { mxClipboard.cut(editor.graph); } }); this.addAction('copy', function(editor) { if (editor.graph.isEnabled()) { mxClipboard.copy(editor.graph); } }); this.addAction('paste', function(editor) { if (editor.graph.isEnabled()) { mxClipboard.paste(editor.graph); } }); this.addAction('delete', function(editor) { if (editor.graph.isEnabled()) { editor.graph.removeCells(); } }); this.addAction('group', function(editor) { if (editor.graph.isEnabled()) { editor.graph.setSelectionCell(editor.groupCells()); } }); this.addAction('ungroup', function(editor) { if (editor.graph.isEnabled()) { editor.graph.setSelectionCells(editor.graph.ungroupCells()); } }); this.addAction('removeFromParent', function(editor) { if (editor.graph.isEnabled()) { editor.graph.removeCellsFromParent(); } }); this.addAction('undo', function(editor) { if (editor.graph.isEnabled()) { editor.undo(); } }); this.addAction('redo', function(editor) { if (editor.graph.isEnabled()) { editor.redo(); } }); this.addAction('zoomIn', function(editor) { editor.graph.zoomIn(); }); this.addAction('zoomOut', function(editor) { editor.graph.zoomOut(); }); this.addAction('actualSize', function(editor) { editor.graph.zoomActual(); }); this.addAction('fit', function(editor) { editor.graph.fit(); }); this.addAction('showProperties', function(editor, cell) { editor.showProperties(cell); }); this.addAction('selectAll', function(editor) { if (editor.graph.isEnabled()) { editor.graph.selectAll(); } }); this.addAction('selectNone', function(editor) { if (editor.graph.isEnabled()) { editor.graph.clearSelection(); } }); this.addAction('selectVertices', function(editor) { if (editor.graph.isEnabled()) { editor.graph.selectVertices(); } }); this.addAction('selectEdges', function(editor) { if (editor.graph.isEnabled()) { editor.graph.selectEdges(); } }); this.addAction('edit', function(editor, cell) { if (editor.graph.isEnabled() && editor.graph.isCellEditable(cell)) { editor.graph.startEditingAtCell(cell); } }); this.addAction('toBack', function(editor, cell) { if (editor.graph.isEnabled()) { editor.graph.orderCells(true); } }); this.addAction('toFront', function(editor, cell) { if (editor.graph.isEnabled()) { editor.graph.orderCells(false); } }); this.addAction('enterGroup', function(editor, cell) { editor.graph.enterGroup(cell); }); this.addAction('exitGroup', function(editor) { editor.graph.exitGroup(); }); this.addAction('home', function(editor) { editor.graph.home(); }); this.addAction('selectPrevious', function(editor) { if (editor.graph.isEnabled()) { editor.graph.selectPreviousCell(); } }); this.addAction('selectNext', function(editor) { if (editor.graph.isEnabled()) { editor.graph.selectNextCell(); } }); this.addAction('selectParent', function(editor) { if (editor.graph.isEnabled()) { editor.graph.selectParentCell(); } }); this.addAction('selectChild', function(editor) { if (editor.graph.isEnabled()) { editor.graph.selectChildCell(); } }); this.addAction('collapse', function(editor) { if (editor.graph.isEnabled()) { editor.graph.foldCells(true); } }); this.addAction('collapseAll', function(editor) { if (editor.graph.isEnabled()) { var cells = editor.graph.getChildVertices(); editor.graph.foldCells(true, false, cells); } }); this.addAction('expand', function(editor) { if (editor.graph.isEnabled()) { editor.graph.foldCells(false); } }); this.addAction('expandAll', function(editor) { if (editor.graph.isEnabled()) { var cells = editor.graph.getChildVertices(); editor.graph.foldCells(false, false, cells); } }); this.addAction('bold', function(editor) { if (editor.graph.isEnabled()) { editor.graph.toggleCellStyleFlags( mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_BOLD); } }); this.addAction('italic', function(editor) { if (editor.graph.isEnabled()) { editor.graph.toggleCellStyleFlags( mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_ITALIC); } }); this.addAction('underline', function(editor) { if (editor.graph.isEnabled()) { editor.graph.toggleCellStyleFlags( mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_UNDERLINE); } }); this.addAction('alignCellsLeft', function(editor) { if (editor.graph.isEnabled()) { editor.graph.alignCells(mxConstants.ALIGN_LEFT); } }); this.addAction('alignCellsCenter', function(editor) { if (editor.graph.isEnabled()) { editor.graph.alignCells(mxConstants.ALIGN_CENTER); } }); this.addAction('alignCellsRight', function(editor) { if (editor.graph.isEnabled()) { editor.graph.alignCells(mxConstants.ALIGN_RIGHT); } }); this.addAction('alignCellsTop', function(editor) { if (editor.graph.isEnabled()) { editor.graph.alignCells(mxConstants.ALIGN_TOP); } }); this.addAction('alignCellsMiddle', function(editor) { if (editor.graph.isEnabled()) { editor.graph.alignCells(mxConstants.ALIGN_MIDDLE); } }); this.addAction('alignCellsBottom', function(editor) { if (editor.graph.isEnabled()) { editor.graph.alignCells(mxConstants.ALIGN_BOTTOM); } }); this.addAction('alignFontLeft', function(editor) { editor.graph.setCellStyles( mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT); }); this.addAction('alignFontCenter', function(editor) { if (editor.graph.isEnabled()) { editor.graph.setCellStyles( mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER); } }); this.addAction('alignFontRight', function(editor) { if (editor.graph.isEnabled()) { editor.graph.setCellStyles( mxConstants.STYLE_ALIGN, mxConstants.ALIGN_RIGHT); } }); this.addAction('alignFontTop', function(editor) { if (editor.graph.isEnabled()) { editor.graph.setCellStyles( mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_TOP); } }); this.addAction('alignFontMiddle', function(editor) { if (editor.graph.isEnabled()) { editor.graph.setCellStyles( mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE); } }); this.addAction('alignFontBottom', function(editor) { if (editor.graph.isEnabled()) { editor.graph.setCellStyles( mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_BOTTOM); } }); this.addAction('zoom', function(editor) { var current = editor.graph.getView().scale*100; var scale = parseFloat(mxUtils.prompt( mxResources.get(editor.askZoomResource) || editor.askZoomResource, current))/100; if (!isNaN(scale)) { editor.graph.getView().setScale(scale); } }); this.addAction('toggleTasks', function(editor) { if (editor.tasks != null) { editor.tasks.setVisible(!editor.tasks.isVisible()); } else { editor.showTasks(); } }); this.addAction('toggleHelp', function(editor) { if (editor.help != null) { editor.help.setVisible(!editor.help.isVisible()); } else { editor.showHelp(); } }); this.addAction('toggleOutline', function(editor) { if (editor.outline == null) { editor.showOutline(); } else { editor.outline.setVisible(!editor.outline.isVisible()); } }); this.addAction('toggleConsole', function(editor) { mxLog.setVisible(!mxLog.isVisible()); }); }; /** * Function: configure * * Configures the editor using the specified node. To load the * configuration from a given URL the following code can be used to obtain * the XML node. * * (code) * var node = mxUtils.load(url).getDocumentElement(); * (end) * * Parameters: * * node - XML node that contains the configuration. */ mxEditor.prototype.configure = function (node) { if (node != null) { // Creates a decoder for the XML data // and uses it to configure the editor var dec = new mxCodec(node.ownerDocument); dec.decode(node, this); // Resets the counters, modified state and // command history this.resetHistory(); } }; /** * Function: resetFirstTime * * Resets the cookie that is used to remember if the editor has already * been used. */ mxEditor.prototype.resetFirstTime = function () { document.cookie = 'mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/'; }; /** * Function: resetHistory * * Resets the command history, modified state and counters. */ mxEditor.prototype.resetHistory = function () { this.lastSnapshot = new Date().getTime(); this.undoManager.clear(); this.ignoredChanges = 0; this.setModified(false); }; /** * Function: addAction * * Binds the specified actionname to the specified function. * * Parameters: * * actionname - String that specifies the name of the action * to be added. * funct - Function that implements the new action. The first * argument of the function is the editor it is used * with, the second argument is the cell it operates * upon. * * Example: * (code) * editor.addAction('test', function(editor, cell) * { * mxUtils.alert("test "+cell); * }); * (end) */ mxEditor.prototype.addAction = function (actionname, funct) { this.actions[actionname] = funct; }; /** * Function: execute * * Executes the function with the given name in <actions> passing the * editor instance and given cell as the first and second argument. All * additional arguments are passed to the action as well. This method * contains a try-catch block and displays an error message if an action * causes an exception. The exception is re-thrown after the error * message was displayed. * * Example: * * (code) * editor.execute("showProperties", cell); * (end) */ mxEditor.prototype.execute = function (actionname, cell, evt) { var action = this.actions[actionname]; if (action != null) { try { // Creates the array of arguments by replacing the actionname // with the editor instance in the args of this function var args = arguments; args[0] = this; // Invokes the function on the editor using the args action.apply(this, args); } catch (e) { mxUtils.error('Cannot execute ' + actionname + ': ' + e.message, 280, true); throw e; } } else { mxUtils.error('Cannot find action '+actionname, 280, true); } }; /** * Function: addTemplate * * Adds the specified template under the given name in <templates>. */ mxEditor.prototype.addTemplate = function (name, template) { this.templates[name] = template; }; /** * Function: getTemplate * * Returns the template for the given name. */ mxEditor.prototype.getTemplate = function (name) { return this.templates[name]; }; /** * Function: createGraph * * Creates the <graph> for the editor. The graph is created with no * container and is initialized from <setGraphContainer>. */ mxEditor.prototype.createGraph = function () { var graph = new mxGraph(null, null, this.graphRenderHint); // Enables rubberband, tooltips, panning graph.setTooltips(true); graph.setPanning(true); // Overrides the dblclick method on the graph to // invoke the dblClickAction for a cell and reset // the selection tool in the toolbar this.installDblClickHandler(graph); // Installs the command history this.installUndoHandler(graph); // Installs the handlers for the root event this.installDrillHandler(graph); // Installs the handler for validation this.installChangeHandler(graph); // Installs the handler for calling the // insert function and consume the // event if an insert function is defined this.installInsertHandler(graph); // Redirects the function for creating the // popupmenu items graph.popupMenuHandler.factoryMethod = mxUtils.bind(this, function(menu, cell, evt) { return this.createPopupMenu(menu, cell, evt); }); // Redirects the function for creating // new connections in the diagram graph.connectionHandler.factoryMethod = mxUtils.bind(this, function(source, target) { return this.createEdge(source, target); }); // Maintains swimlanes and installs autolayout this.createSwimlaneManager(graph); this.createLayoutManager(graph); return graph; }; /** * Function: createSwimlaneManager * * Sets the graph's container using <mxGraph.init>. */ mxEditor.prototype.createSwimlaneManager = function (graph) { var swimlaneMgr = new mxSwimlaneManager(graph, false); swimlaneMgr.isHorizontal = mxUtils.bind(this, function() { return this.horizontalFlow; }); swimlaneMgr.isEnabled = mxUtils.bind(this, function() { return this.maintainSwimlanes; }); return swimlaneMgr; }; /** * Function: createLayoutManager * * Creates a layout manager for the swimlane and diagram layouts, that * is, the locally defined inter- and intraswimlane layouts. */ mxEditor.prototype.createLayoutManager = function (graph) { var layoutMgr = new mxLayoutManager(graph); var self = this; // closure layoutMgr.getLayout = function(cell) { var layout = null; var model = self.graph.getModel(); if (model.getParent(cell) != null) { // Executes the swimlane layout if a child of // a swimlane has been changed. The layout is // lazy created in createSwimlaneLayout. if (self.layoutSwimlanes && graph.isSwimlane(cell)) { if (self.swimlaneLayout == null) { self.swimlaneLayout = self.createSwimlaneLayout(); } layout = self.swimlaneLayout; } // Executes the diagram layout if the modified // cell is a top-level cell. The layout is // lazy created in createDiagramLayout. else if (self.layoutDiagram && (graph.isValidRoot(cell) || model.getParent(model.getParent(cell)) == null)) { if (self.diagramLayout == null) { self.diagramLayout = self.createDiagramLayout(); } layout = self.diagramLayout; } } return layout; }; return layoutMgr; }; /** * Function: setGraphContainer * * Sets the graph's container using <mxGraph.init>. */ mxEditor.prototype.setGraphContainer = function (container) { if (this.graph.container == null) { // Creates the graph instance inside the given container and render hint //this.graph = new mxGraph(container, null, this.graphRenderHint); this.graph.init(container); // Install rubberband selection as the last // action handler in the chain this.rubberband = new mxRubberband(this.graph); // Disables the context menu if (this.disableContextMenu) { mxEvent.disableContextMenu(container); } // Workaround for stylesheet directives in IE if (mxClient.IS_QUIRKS) { new mxDivResizer(container); } } }; /** * Function: installDblClickHandler * * Overrides <mxGraph.dblClick> to invoke <dblClickAction> * on a cell and reset the selection tool in the toolbar. */ mxEditor.prototype.installDblClickHandler = function (graph) { // Installs a listener for double click events graph.addListener(mxEvent.DOUBLE_CLICK, mxUtils.bind(this, function(sender, evt) { var cell = evt.getProperty('cell'); if (cell != null && graph.isEnabled() && this.dblClickAction != null) { this.execute(this.dblClickAction, cell); evt.consume(); } }) ); }; /** * Function: installUndoHandler * * Adds the <undoManager> to the graph model and the view. */ mxEditor.prototype.installUndoHandler = function (graph) { var listener = mxUtils.bind(this, function(sender, evt) { var edit = evt.getProperty('edit'); this.undoManager.undoableEditHappened(edit); }); graph.getModel().addListener(mxEvent.UNDO, listener); graph.getView().addListener(mxEvent.UNDO, listener); // Keeps the selection state in sync var undoHandler = function(sender, evt) { var changes = evt.getProperty('edit').changes; graph.setSelectionCells(graph.getSelectionCellsForChanges(changes)); }; this.undoManager.addListener(mxEvent.UNDO, undoHandler); this.undoManager.addListener(mxEvent.REDO, undoHandler); }; /** * Function: installDrillHandler * * Installs listeners for dispatching the <root> event. */ mxEditor.prototype.installDrillHandler = function (graph) { var listener = mxUtils.bind(this, function(sender) { this.fireEvent(new mxEventObject(mxEvent.ROOT)); }); graph.getView().addListener(mxEvent.DOWN, listener); graph.getView().addListener(mxEvent.UP, listener); }; /** * Function: installChangeHandler * * Installs the listeners required to automatically validate * the graph. On each change of the root, this implementation * fires a <root> event. */ mxEditor.prototype.installChangeHandler = function (graph) { var listener = mxUtils.bind(this, function(sender, evt) { // Updates the modified state this.setModified(true); // Automatically validates the graph // after each change if (this.validating == true) { graph.validateGraph(); } // Checks if the root has been changed var changes = evt.getProperty('edit').changes; for (var i = 0; i < changes.length; i++) { var change = changes[i]; if (change instanceof mxRootChange || (change instanceof mxValueChange && change.cell == this.graph.model.root) || (change instanceof mxCellAttributeChange && change.cell == this.graph.model.root)) { this.fireEvent(new mxEventObject(mxEvent.ROOT)); break; } } }); graph.getModel().addListener(mxEvent.CHANGE, listener); }; /** * Function: installInsertHandler * * Installs the handler for invoking <insertFunction> if * one is defined. */ mxEditor.prototype.installInsertHandler = function (graph) { var self = this; // closure var insertHandler = { mouseDown: function(sender, me) { if (self.insertFunction != null && !me.isPopupTrigger() && (self.forcedInserting || me.getState() == null)) { self.graph.clearSelection(); self.insertFunction(me.getEvent(), me.getCell()); // Consumes the rest of the events // for this gesture (down, move, up) this.isActive = true; me.consume(); } }, mouseMove: function(sender, me) { if (this.isActive) { me.consume(); } }, mouseUp: function(sender, me) { if (this.isActive) { this.isActive = false; me.consume(); } } }; graph.addMouseListener(insertHandler); }; /** * Function: createDiagramLayout * * Creates the layout instance used to layout the * swimlanes in the diagram. */ mxEditor.prototype.createDiagramLayout = function () { var gs = this.graph.gridSize; var layout = new mxStackLayout(this.graph, !this.horizontalFlow, this.swimlaneSpacing, 2*gs, 2*gs); // Overrides isIgnored to only take into account swimlanes layout.isVertexIgnored = function(cell) { return !layout.graph.isSwimlane(cell); }; return layout; }; /** * Function: createSwimlaneLayout * * Creates the layout instance used to layout the * children of each swimlane. */ mxEditor.prototype.createSwimlaneLayout = function () { return new mxCompactTreeLayout(this.graph, this.horizontalFlow); }; /** * Function: createToolbar * * Creates the <toolbar> with no container. */ mxEditor.prototype.createToolbar = function () { return new mxDefaultToolbar(null, this); }; /** * Function: setToolbarContainer * * Initializes the toolbar for the given container. */ mxEditor.prototype.setToolbarContainer = function (container) { this.toolbar.init(container); // Workaround for stylesheet directives in IE if (mxClient.IS_QUIRKS) { new mxDivResizer(container); } }; /** * Function: setStatusContainer * * Creates the <status> using the specified container. * * This implementation adds listeners in the editor to * display the last saved time and the current filename * in the status bar. * * Parameters: * * container - DOM node that will contain the statusbar. */ mxEditor.prototype.setStatusContainer = function (container) { if (this.status == null) { this.status = container; // Prints the last saved time in the status bar // when files are saved this.addListener(mxEvent.SAVE, mxUtils.bind(this, function() { var tstamp = new Date().toLocaleString(); this.setStatus((mxResou