/** * Mixin class for diagram.js, that contains connector removal logic. */ Ext.define("Terrasoft.mixins.ConnectorRemovalMixin", { extend: "Terrasoft.BaseObject", alternateClassName: "Terrasoft.ConnectorRemovalMixin", /** * Diagram where node will be deleted. * @type {Object} * @private */ diagram: null, /** * Diagram name table that contains all diagram elements. * @type {Array} * @private */ nameTable: null, /** * Initialize util class arguments. * @param {Object} diagram Diagram where node will be deleted. */ init: function (diagram) { this.diagram = diagram; this.nameTable = diagram.nameTable; }, /** * Removes connectors with null references. * @param {Object} node Node that will be removed. * @param {Object} args Arguments object. * @param {ej.Diagram} diagram Diagram where node will be deleted. */ removeConnector: function (node, args) { var inEdges = node.inEdges; var outEdges = node.outEdges; if (!node.segments && args.deleteDependent) { if (inEdges && inEdges.length > 0) { this.removeNullConnectorsReferences(inEdges); } if (outEdges && outEdges.length > 0) { this.removeNullConnectorsReferences(outEdges); } } }, /** * Reconnects connectors to first non-recursive connector. * @param {Object} node Node that will be removed. * @param {Object} args Arguments object. * @param {Boolean} isChild Is node a child object. * @param {Object} diagram Diagram where node will be deleted. */ disconnect: function (node, args, isChild) { if (!this.getByName(node.name)) { return; } var haveInConnectors = this.getEdgesAreNotEmpty(node.inEdges); var haveOutConnectors = this.getEdgesAreNotEmpty(node.outEdges); this.removeObviousRecursion(node); this.removeHiddenRecursion(node); this.removeTransitivity(node); if (args.deleteDependent && args.adjustDependent && !isChild) { if (haveInConnectors && haveOutConnectors) { this.reconnectInConnectorsToFirstOutConnector(node); } else if (haveInConnectors) { this.removeInConnectorsReferences(node); } else if (haveOutConnectors) { this.removeOutConnectorsReferences(node); } } else { this.deleteAllConnectors(node); } }, /** * Returns true if edges are not empty. * @param {Array} edges Array of connector names. * @private * @return {Boolean} Are edges not empty? */ getEdgesAreNotEmpty: function (edges) { return edges && edges.length > 0; }, /** * Returns element from diagram nameTable. * @param {String} name Name of the object. * @private * @return {Object} Element from diagram nameTable. */ getByName: function (name) { return this.nameTable[name]; }, /** * Removes all connectors references in array, that have null references in both target or source nodes. * @param {Array} edges Array of node edges where connectors will be removed. * @private */ removeNullConnectorsReferences: function (edges) { for (var i = edges.length - 1; i >= 0; i--) { var connector = this.getByName(edges[i]); if (connector && (!connector.sourceNode || !connector.targetNode)) { this.diagram._remove(connector, true); } } }, /** * Removes all obvious recursions, where connector target and source nodes are the same. * @param {Object} node Affected node. * @private */ removeObviousRecursion: function (node) { var outs = this.getNodeOuts(node); for (var i = outs.length - 1; i >= 0; i--) { var out = outs[i]; if (out.targetNode.name === node.name) { this.deleteConnector(out.connector); } } }, /** * Removes all hidden recursions, where 'A' node (that will be deleted) is connected to node, * that is connected back to node 'A'. * @param {Object} node Affected node. * @private */ removeHiddenRecursion: function (node) { var originalOuts = this.getNodeOuts(node); originalOuts.forEach(function (originalOut) { var possibleRecursiveOuts = this.getNodeOuts(originalOut.targetNode); for (var i = possibleRecursiveOuts.length - 1; i >= 0; i--) { var possibleRecursiveOut = possibleRecursiveOuts[i]; if (possibleRecursiveOut.targetNode.name === node.name) { this.deleteConnector(possibleRecursiveOut.connector); } } }, this); }, /** * Removes all transitive connectors. If 'B' node is connected to 'A' node (that will be deleted), * 'A' is connected to 'C', 'B' is connected to 'C' too => for the sake of avoiding double connection situation, * this method remove 'A' to 'C' connection. * @param {Object} node Affected node. * @private */ removeTransitivity: function (node) { var nodes = this.getConnectedNodes(node); for (var i = nodes.incomingNodes.length - 1; i >= 0; i--) { for (var j = nodes.outgoingNodes.length - 1; j >= 0; j--) { var inNode = nodes.incomingNodes[i]; var outNode = nodes.outgoingNodes[j]; if (this.getAreNodesConnected(inNode, outNode)) { var connectorToRemove = this.getConnectorBetween(node, outNode); if (connectorToRemove) { this.deleteConnector(connectorToRemove); } } } } }, /** * Reconnects all inConnectors to first found outConnector. * @param {Object} node Affected node. * @private */ reconnectInConnectorsToFirstOutConnector: function (node) { var inConnectors = this.getInConnectors(node); var outEdge = node.outEdges[0]; var destinationConnector = outEdge ? this.getByName(outEdge) : null; if (destinationConnector) { var destinationNode = this.getByName(destinationConnector.targetNode); for (var i = inConnectors.length - 1; i >= 0; i--) { var inConnector = inConnectors[i]; inConnector.targetNode = destinationConnector.targetNode; inConnector.targetPort = destinationConnector.targetPort; destinationNode.inEdges.push(inConnector.name); ej.datavisualization.Diagram.Util.dock(inConnector, this.nameTable); ej.datavisualization.Diagram.DiagramContext.update(inConnector, this.diagram); } } else { this.removeInConnectorsReferences(node); } this.removeOutConnectorsReferences(node); }, /** * Clears all inConnectors references to connected source node, that`s why they will be removed * after ej-diagram _removeConnector() method will be called. * @param {Object} node Affected node. * @private */ removeInConnectorsReferences: function (node) { var scope = this; this.removeConnectorsReferences(node.inEdges, function (connector) { return scope.getByName(connector.sourceNode).outEdges; }); }, /** * Clears all outConnectors references to connected target node, that`s why they will be removed * after ej-diagram _removeConnector() method will be called. * @param {Object} node Affected node. * @private */ removeOutConnectorsReferences: function (node) { var scope = this; this.removeConnectorsReferences(node.outEdges, function (connector) { return scope.getByName(connector.targetNode).inEdges; }); }, /** * Clears all connectors references to connected node, that`s why they will be removed * after ej-diagram _removeConnector() method will be called. * @param {Array} connectorsNames Names of the connectors which references will be removed. * @param {Function} getEdgesWhereToRemove Function that selects edges where connectors names will be removed. * @private */ removeConnectorsReferences: function (connectorsNames, getEdgesWhereToRemove) { if (connectorsNames.length === 0) { return; } for (var i = connectorsNames.length - 1; i >= 0; i--) { var connector = this.getByName(connectorsNames[i]); Ext.Array.remove(getEdgesWhereToRemove(connector), connector.name); this.clearConnector(connector); } }, /** * Clears all out and inConnectors references to connected nodes, that`s why they will be removed * after ej-diagram _removeConnector() method will be called. * @param {Object} node Affected node. * @private */ deleteAllConnectors: function (node) { this.removeInConnectorsReferences(node); this.removeOutConnectorsReferences(node); }, /** * Clears connector target and source references. * @private * @param {Object} connector Connector that will be cleared. */ clearConnector: function (connector) { connector.sourceNode = null; connector.sourcePort = null; connector.targetNode = null; connector.targetPort = null; }, /** * Instantly removes connector and its references from diagram. * @private * @param {Object} connector Connector that will be deleted. */ deleteConnector: function (connector) { Ext.Array.remove(this.getByName(connector.targetNode).inEdges, connector.name); Ext.Array.remove(this.getByName(connector.sourceNode).outEdges, connector.name); this.clearConnector(connector); this.diagram._remove(connector, true); }, /** * Returns first found connector between two nodes. * @private * @param {Object} fromNode Source node of the connector. * @param {Object} toNode Target node of the connector. * @return {Object} Connector between two nodes. */ getConnectorBetween: function (fromNode, toNode) { var connector = null; var outEdges = fromNode.outEdges; var inEdges = toNode.inEdges; for (var i = outEdges.length - 1; i >= 0; i--) { for (var j = inEdges.length - 1; j >= 0; j--) { var outEdge = outEdges[i]; var inEdge = inEdges[j]; if (outEdge === inEdge) { connector = this.getByName(outEdge); break; } } } return connector; }, /** * Checks if two nodes are connected. * @private * @param {Object} sourceNode SourceNode of the connector. * @param {Object} targetNode TargetNode of the connector. * @return {Boolean} Are nodes connected. */ getAreNodesConnected: function (sourceNode, targetNode) { return sourceNode.outEdges.some(function (edge) { var connector = this.getByName(edge); return connector.targetNode === targetNode.name; }, this); }, /** * Returns array of node outs. Out - is a pair of connector and it`s targetNode objects. * @private * @param {Object} node Node from which outs will be taken. * @return {Array} Array contains consists of outConnector and it`s targetNode pair. */ getNodeOuts: function (node) { var outs = []; var outEdges = node.outEdges; for (var i = outEdges.length - 1; i >= 0; i--) { var destinationConnector = this.getByName(outEdges[i]); var destinationNode = this.getByName(destinationConnector.targetNode); outs.push({ connector: destinationConnector, targetNode: destinationNode }); } return outs; }, /** * Returns object that consist of array of all incoming nodes and array of all outgoing nodes. * @private * @param {Object} node Node from which connected nodes will be taken. * @return {Object} Objects consist of array of all incoming nodes and array of all outgoing nodes. */ getConnectedNodes: function (node) { var nodes = { incomingNodes: [], outgoingNodes: [] }; var outEdges = node.outEdges; var inEdges = node.inEdges; var i; for (i = outEdges.length - 1; i >= 0; i--) { var outConnector = this.getByName(outEdges[i]); nodes.outgoingNodes.push(this.getByName(outConnector.targetNode)); } for (i = inEdges.length - 1; i >= 0; i--) { var inConnector = this.getByName(inEdges[i]); nodes.incomingNodes.push(this.getByName(inConnector.sourceNode)); } return nodes; }, /** * Returns array of all inConnector objects. * @private * @param {Object} node Node from which in will be taken. * @return {Object} Objects consist of array of all incoming nodes and array of all outgoing nodes. */ getInConnectors: function (node) { var inConnectors = []; for (var i = node.inEdges.length - 1; i >= 0; i--) { inConnectors.push(this.getByName(node.inEdges[i])); } return inConnectors; } });