/**
* The class implements the display of the process elements on the diagram.
*/
Ext.define("Terrasoft.ProcessDesigner.ProcessSchemaDiagram", {
extend: "Terrasoft.Diagram",
alternateClassName: "Terrasoft.ProcessSchemaDiagram",
statics: {
/**
* Stores a link to the current instance of the designer.
* @type {Terrasoft.ProcessSchemaDiagram}
* @protected
*/
instance: null,
/**
* Returns the current instance of the designer.
* @protected
* @return {Terrasoft.ProcessSchemaDiagram}
*/
getDiagramInstance: function () {
return this.instance;
},
/**
* .
* @type {Boolean}
* @private
*/
getNodeBoundsCustomized: false,
/**
* Overlaps the logic for obtaining the position and dimensions of the element on the diagram.
* @private
*/
customizeGetNodeBounds: function () {
if (this.getNodeBoundsCustomized === true) {
return;
}
var baseBounds = ej.Diagram.Util.bounds;
ej.Diagram.Util.bounds = function (node) {
var self = Terrasoft.ProcessSchemaDiagram.getDiagramInstance();
var nodeBounds = baseBounds(node);
var nodeRectangle = ej.Diagram.Rectangle(nodeBounds.x, nodeBounds.y, nodeBounds.width, nodeBounds.height);
var nodePosition = ej.Diagram.Point(nodeBounds.x, nodeBounds.y);
var parentNode = self.getElementById(node.parent);
if (parentNode && parentNode.nodeType !== Terrasoft.diagram.UserHandlesConstraint.Lane) {
var parentNodeRectangle = ej.Diagram.Util.bounds(parentNode);
nodePosition = ej.Diagram.Geometry.translate(nodePosition, parentNodeRectangle.x, parentNodeRectangle.y);
nodeRectangle.x = nodePosition.x;
nodeRectangle.y = nodePosition.y;
return baseBounds(nodeRectangle);
}
return nodeBounds;
};
this.getNodeBoundsCustomized = true;
},
/**
* Indicates that ResizeTool has already been customized.
* @type {Boolean}
* @private
*/
resizeToolCustomized: false,
/**
* Overlaps the logic for drawing a frame and resizing a chart element.
*/
customizeResizeTool: function () {
if (this.resizeToolCustomized === true) {
return;
}
var svgContext = ej.Diagram.SvgContext;
/*jslint bitwise: true */
svgContext._renderResizeHandle = function (node, svg, scale) {
if (node.constraints & ej.Diagram.SelectorConstraints.Resizer && node.type === "pseudoGroup") {
svgContext._renderResizeBorder("resizeBorder", node, svg, scale);
}
if (node.constraints & ej.Diagram.NodeConstraints.Resize) {
this._renderResizeCorner("n-resize", node, node.width / 2 * scale, 0, svg, !(node.constraints & ej.Diagram.NodeConstraints.ResizeNorth));
this._renderResizeCorner("ne-resize", node, node.width * scale, 0, svg, !(node.constraints & ej.Diagram.NodeConstraints.ResizeNorthEast));
this._renderResizeCorner("w-resize", node, 0, node.height / 2 * scale, svg, !(node.constraints & ej.Diagram.NodeConstraints.ResizeWest));
this._renderResizeCorner("e-resize", node, node.width * scale, node.height / 2 * scale, svg, !(node.constraints & ej.Diagram.NodeConstraints.ResizeEast));
this._renderResizeCorner("sw-resize", node, 0, node.height * scale, svg, !(node.constraints & ej.Diagram.NodeConstraints.ResizeSouthWest));
this._renderResizeCorner("s-resize", node, node.width / 2 * scale, node.height * scale, svg, !(node.constraints & ej.Diagram.NodeConstraints.ResizeSouth));
this._renderResizeCorner("se-resize", node, node.width * scale, node.height * scale, svg, !(node.constraints & ej.Diagram.NodeConstraints.ResizeSouthEast));
}
};
/*jslint bitwise: false */
svgContext._renderResizeBorder = function (id, node, svg, scale) {
var handle = svg.getElementById(svg.document.id + "handle_g");
var rect = svg.rect({
"id": id,
"width": node.width * scale,
"height": node.height * scale,
"stroke": node.borderColor ? node.borderColor : "#097F7F",
"stroke-width": 1,
"stroke-dasharray": "6,3",
"fill": "none"
});
handle.appendChild(rect);
};
/*jslint bitwise: true */
svgContext._updateResizeHandle = function (node, svg, scale) {
if (node.constraints & ej.Diagram.NodeConstraints.Resize || node.type === "pseudoGroup") {
this._updateResizeCorner("n-resize", node.width / 2 * scale, 0, svg);
this._updateResizeCorner("ne-resize", node.width * scale, 0, svg);
this._updateResizeCorner("w-resize", 0, node.height / 2 * scale, svg);
this._updateResizeCorner("e-resize", node.width * scale, node.height / 2 * scale, svg);
this._updateResizeCorner("sw-resize", 0, node.height * scale, svg);
this._updateResizeCorner("s-resize", node.width / 2 * scale, node.height * scale, svg);
this._updateResizeCorner("se-resize", node.width * scale, node.height * scale, svg);
this._updateResizeBorder("resizeBorder", node, svg, scale);
}
};
/*jslint bitwise: false */
var baseResizeToolMouseUp = ej.Diagram.ResizeTool.prototype.mouseup;
ej.Diagram.ResizeTool.prototype.mouseup = function (evt) {
var item = this.selectedObject || this.diagram.selectionList[0];
baseResizeToolMouseUp.apply(this, arguments);
evt.stopPropagation();
var bounds = ej.Diagram.Util.bounds(item);
var self = Terrasoft.ProcessSchemaDiagram.getDiagramInstance();
self.fireEvent("sizeChanged", {
itemName: item.name,
size: {
width: bounds.width,
height: bounds.height
}
});
};
var baseResizeToolMouseMove = ej.Diagram.ResizeTool.prototype.mousemove;
ej.Diagram.ResizeTool.prototype.mousemove = function (evt) {
var container = this.selectedObject;
if (container && container.isContainer === true) {
var self = Terrasoft.ProcessSchemaDiagram.getDiagramInstance();
var contentBounds = self.getContainerContentRectangle(container, true);
var containerBounds = ej.Diagram.Util.bounds(container);
var isResizeAvailable = true;
if (contentBounds) {
var currentPoint = this.mousePosition(evt);
var expectedContainerBounds = self.calculateExpectedContainerRectangle(containerBounds, currentPoint, this._resizeDirection);
var contentOffsetX = contentBounds.x - containerBounds.x;
var contentOffsetY = contentBounds.y - containerBounds.y;
if (expectedContainerBounds.x !== containerBounds.x) {
contentBounds.x = expectedContainerBounds.x + contentOffsetX;
}
if (expectedContainerBounds.y !== containerBounds.y) {
contentBounds.y = expectedContainerBounds.y + contentOffsetY;
}
isResizeAvailable = ej.Diagram.Geometry.containsRect(expectedContainerBounds, contentBounds);
}
if (isResizeAvailable) {
baseResizeToolMouseMove.apply(this, arguments);
}
self.updateNodeConnectors(container, this.diagram);
}
};
svgContext._updateResizeCorner = function (corner, cx, cy, svg) {
var self = Terrasoft.ProcessSchemaDiagram.getDiagramInstance();
var attr = {
"id": corner,
"cx": cx,
"cy": cy,
"stroke": self.connectorPortColor,
"r": 3.5
};
svg.circle(attr);
var transparentAttr = {
"id": corner + "_transparent",
"cx": cx,
"cy": cy
};
svg.circle(transparentAttr);
};
svgContext._renderResizeCorner = function (corner, node, cx, cy, svg, state) {
var handle = svg.getElementById(svg.document.id + "handle_g");
var pEvnts = "default";
var fill = "white";
if (!ej.Diagram.Util.canResize(node)) {
fill = "darkgray";
}
if (state) {
fill = "darkgray";
pEvnts = "none";
}
var self = Terrasoft.ProcessSchemaDiagram.getDiagramInstance();
var attr = {
"id": corner,
"fill": fill,
"stroke": self.connectorPortColor,
"stroke-width": 1,
"cx": cx,
"cy": cy,
"r": 3.5,
"pointer-events": pEvnts
};
handle.appendChild(svg.circle(attr));
attr = {
"id": corner + "_transparent",
"class": corner,
"fill": "transparent",
"cx": cx,
"cy": cy,
"r": 10,
"pointer-events": pEvnts
};
handle.appendChild(svg.circle(attr));
};
this.resizeToolCustomized = true;
},
/**
* Overrides the logic for moving elements.
*/
customizeMoveTool: function () {
if (this.moveToolCustomized === true) {
return;
}
var baseMoveToolMouseDown = ej.Diagram.MoveTool.prototype.mousedown;
ej.Diagram.MoveTool.prototype.mousedown = function (evt) {
baseMoveToolMouseDown.apply(this, arguments);
var node = this._findNodeUnderMouse(evt);
if (node) {
this.mouseDownNode = node;
}
};
var setSelectedObject = function () {
var selectedObject = this.diagram._selectedSymbol ? this.diagram.selectionList[0] : this.mouseDownNode;
if (selectedObject) {
if (this.diagram.selectionList[0] && this.diagram.selectionList[0].type === "pseudoGroup") {
this.selectedObject = this.diagram.selectionList[0];
} else {
if (this.diagram.selectionList[0] !== selectedObject) {
this.diagram._clearSelection();
this.selectedObject = this.diagram.nameTable[selectedObject.name];
} else {
this.selectedObject = this.diagram.nameTable[this.diagram.selectionList[0].name];
}
}
}
};
var setSelectedConnector = function () {
var diagram = this.diagram;
var segment;
var element = this.newObject || this.helper;
if (element && element.type === "node") {
/*jslint bitwise: true */
var canConnect = element.toolsConstraint & Terrasoft.diagram.ToolsConstraint.ConnectionEditTool;
/*jslint bitwise: false */
var hasConnection = element.inEdges.length === 0 && element.outEdges.length === 0;
if (canConnect && hasConnection) {
var self = Terrasoft.ProcessSchemaDiagram.getDiagramInstance();
var rect = ej.Diagram.Util.bounds(element);
segment = self.findSegmentAtRect(rect);
}
}
Terrasoft.ProcessSchemaDiagram.updateSelectedConnector(segment, diagram);
};
var toolBase = ej.datavisualization.Diagram.ToolBase;
ej.Diagram.MoveTool.prototype.mousemove = function (evt) {
toolBase.prototype.mousemove.call(this, evt);
ej.datavisualization.Diagram.SnapUtil._removeGuidelines(this.diagram);
if (this._isMouseDown && !this.inAction && !this.selectedObject) {
setSelectedObject.call(this);
}
if (this.selectedObject && ej.datavisualization.Diagram.Util.canSelect(this.selectedObject)) {
if (!this.inAction) {
this.inAction = true;
this.updateCursor("move");
}
if (this.diagram && !this.diagram._isEditing) {
this._containerMouseMove(evt);
this.previousPoint = this.currentPoint;
}
}
setSelectedConnector.call(this);
};
var baseMoveToolUpdateParent = ej.Diagram.MoveTool.prototype._updateParent;
ej.Diagram.MoveTool.prototype._updateParent = function (object) {
if (object.type !== "pseudoGroup") {
baseMoveToolUpdateParent.apply(this, arguments);
}
};
var baseMoveToolChangeNodeState = ej.Diagram.MoveTool.prototype._changeNodeState;
ej.Diagram.MoveTool.prototype._changeNodeState = function () {
if (this.selectedObject && this.selectedObject.type === "pseudoGroup" && this.hasSameParent()) {
this.currentPoint = this.previousPoint;
this.diffx = 0;
this.diffy = 0;
ej.datavisualization.Diagram.SvgContext._enableSelectedNode(this.selectedObject, this.diagram._svg, this.diagram);
ej.datavisualization.Diagram.SvgContext._removeContainerHelper(this.selectedObject, this.diagram._svg, this.diagram._adornerLayer);
this._removeHighLighter();
this.seletedObject = this.diagram.nameTable[this.selectedObject.name];
var checkDropEvent = !this._checkForDropEvent(this.hoverNode);
if (checkDropEvent) {
this._removeChildParent(this.selectedObject);
this._addToDiagram();
}
} else {
return baseMoveToolChangeNodeState.apply(this, arguments);
}
};
ej.Diagram.MoveTool.prototype._updateHelperXY = function (shape, startPoint, endPoint) {
var towardsLeft = endPoint.x < startPoint.x;
var towardsTop = endPoint.y < startPoint.y;
var difx = this.diffx + (endPoint.x - startPoint.x);
var dify = this.diffy + (endPoint.y - startPoint.y);
var offset;
/*jslint bitwise: true */
if ((this.diagram.model.snapSettings.snapConstraints & ej.datavisualization.Diagram.SnapConstraints.SnapToHorizontalLines || this.diagram.model.snapSettings.snapConstraints & ej.datavisualization.Diagram.SnapConstraints.SnapToVerticalLines) && this.diagram._enableSnapToObject()) {
offset = ej.datavisualization.Diagram.SnapUtil._snapPoint(this.diagram, this.helper, towardsLeft, towardsTop, ej.datavisualization.Diagram.Point(difx, dify), endPoint, startPoint);
}
/*jslint bitwise: false */
if (!offset) {
offset = ej.datavisualization.Diagram.Point(difx, dify);
}
this.diffx = difx - offset.x;
this.diffy = dify - offset.y;
if (!ej.datavisualization.Diagram.Geometry.isEmptyPoint(offset)) {
var args = this._raiseEvent("drag", {
element: this.helper,
offset: offset,
cancel: false
});
if (!args.cancel) {
this.diagram._translate(shape, offset.x, offset.y, this.diagram.nameTable);
//Optimization. The _updateParent method is called in _endAction
//this._updateParent(shape);
//if (shape.type == "group") {
// ej.datavisualization.Diagram.Util._updateGroupBounds(shape, this.diagram);
//}
}
}
};
var baseMoveToolUpdateObject = ej.Diagram.MoveTool.prototype._updateObject;
ej.Diagram.MoveTool.prototype._updateObject = function () {
if (this.selectedObject.type !== "pseudoGroup") {
return;
}
baseMoveToolUpdateObject.apply(this, arguments);
};
this.moveToolCustomized = true;
},
/**
* Repaint selected connector.
* @param {Object} segment Selected connector segment.
* @param {String} segment.connectorName Selected connector name.
* @param {Object} diagram Diagram instance.
*/
updateSelectedConnector: function (segment, diagram) {
var self = Terrasoft.ProcessSchemaDiagram.getDiagramInstance();
var selectedSegment = diagram.selectedSegment;
var connectorName = selectedSegment ? selectedSegment.connectorName : null;
if (segment) {
if (connectorName === segment.connectorName) {
return;
}
if (selectedSegment) {
self.changeConnectorSelection(connectorName, false);
}
self.changeConnectorSelection(segment.connectorName, true);
diagram.selectedSegment = segment;
} else if (selectedSegment) {
self.changeConnectorSelection(connectorName, false);
diagram.selectedSegment = null;
}
}
},
/**
* Max label lines count.
* @protected
*/
maxLabelLinesCount: Terrasoft.getIsRtlMode() ? 1 : 3,
/**
* Regular expression for transfer of signatures by words
* @protected
*/
wrapTextRegExp: null,
/**
* Indent between tracks.
* @private
* @type {Number}
*/
laneMargin: 20,
/**
* Internal indentation of the container.
* @type {Number}
* @protected
*/
containerResizePadding: 20,
/**
* Lanes of the diagram. Stores a reference to the lane element (lane) and the lane title element (laneLine).
* @private
* @type {Object[]}
*/
lanes: null,
/**
* List of disabled functional diagrams keys Ctrl + [disabledCtrlKey]
* @type {Object[]}
* @protected
*/
disabledCtrlKeys: [Ext.EventObject.Z, Ext.EventObject.X, Ext.EventObject.Y],
/**
* List of disabled functional diagram key.
* @type {Object[]}
* @protected
*/
disabledKeys: [Ext.EventObject.LEFT, Ext.EventObject.UP, Ext.EventObject.RIGHT, Ext.EventObject.DOWN, Ext.EventObject.F2],
/**
* The symptom of the "read-only" mode of the process designer.
* @type {Boolean}
*/
readOnly: false,
/**
* Flag, feature editing element header in the diagram.
* @type {Boolean}
*/
labelEditByDblClickEnabled: false,
/**
* The color of the selected control flow
*/
connectorSelectionColor: "#5FADEA",
/**
* Offset of the connector label on the X axis.
* @type {Number}
* @protected
*/
connectorCaptionOffsetX: 0,
/**
* Offset of the connector label on the Y axis.
* @type {Number}
* @protected
*/
connectorCaptionOffsetY: 5,
/**
* Indentation of the element signature substrate.
* @type {Number}
* @protected
*/
nodeLabelBackgroundPadding: 5,
/**
* Length of an extension for source and target node bounds, which is used to determine the length
* of the first segment of the connector near source and target nodes.
* @type {Number}
* @private
*/
nodeBoundsExtension: 30,
mixins: {
droppable: "Terrasoft.Droppable",
nodeSelectionTool: "Terrasoft.NodeSelectionTool",
//TODO Если указать в обратном порядке connectionEditTool не работает
connectionEditTool: "Terrasoft.ConnectionEditTool",
nodeRemoveTool: "Terrasoft.NodeRemoveTool",
connectorRemoveTool: "Terrasoft.ConnectorRemoveTool"
},
constructor: function () {
this.initScrollContentOffset();
this.wrapTextRegExp = /(\S+)|(\s+)/g;
this.callParent(arguments);
this.lanes = [];
},
/**
* @inheritdoc Terrasoft.Diagram#init
* @protected
* @override
*/
init: function () {
this.callParent(arguments);
if (this.readOnly) {
return;
}
this.addEvents(
/**
* @event
* Event of changing the parent container of the chart elements.
* @param {Object} args The parameter of the event.
* @param {String} args.itemName The name of the chart element.
* @param {String} args.containerName The name of the new element container.
*/
"itemsContainerChanged",
/**
* @event
* Event of changing positions in chart elements.
* @param {Object} args The parameter of the event.
* @param {String} args.items Array of the configuration of the chart element.
*/
"itemsPositionChanged",
/**
* @event
* Event of changing the position of the lane.
* @param {Object} args The parameter of the event.
* @param {String} args.itemName The name of the chart element.
* @param {Object} args.position The new position of the element.
* @param {Number} args.position.x The x-axis coordinate.
* @param {Number} args.position.y The y-axis coordinate.
*/
"linePositionChanged",
/**
* @event
* Event of resizing a chart element.
* @param {Object} args The parameter of the event.
* @param {String} args.itemName The name of the chart element.
* @param {Object} args.size New element sizes.
* @param {Number} args.size.width The width of the element.
* @param {Number} args.size.height The height of the element.
*/
"sizeChanged",
/**
* @event
* Event for generating a unique name and title for the chart element.
* @param {Object} args The parameter of the event.
* @param {Terrasoft.ProcessBaseElementSchema} args.item The chart element.
*/
"generateItemNameAndCaption",
/**
* @event
* The event of changing the break points of the connector.
* @param {Object} args The parameter of the event.
* @param {String} args.name The name of the connector.
* @param {ej.Diagram.Point[]} args.polylinePointPositions An array of new breakpoints.
*/
"polylinePointPositionsChanged",
/**
* @event
* Event of inserting an element into the control flow.
* @param {Object} args The parameter of the event.
* @param {Object} args.node The item to insert.
* @param {Object} args.connector Selected sequence flow.
*/
"insertNodeInConnector",
/**
* @event itemsRemoving
* Fires when an items are removed on diagram.
* @param {Object} args Event arguments.
* @param {String} args.itemNameList Array of items.
*/
"itemsRemoving");
this.on("mouseup", this.onMouseUp, this);
this.on("drag", this.onDrag, this);
},
/**
* @inheritdoc Terrasoft.Diagram#onAddItem
* @protected
* @override
*/
onAddItem: function (item, isDataLoaded) {
var nodeConfig = item.getDiagramConfig();
this.callParent([nodeConfig, isDataLoaded]);
},
/**
* @inheritdoc Terrasoft.Diagram#getUserHandles
* @protected
* @override
*/
getUserHandles: function () {
var userHandles = this.callParent(arguments);
userHandles.push(this.mixins.nodeSelectionTool.getNodeSelectionTool.call(this));
if (!this.readOnly) {
userHandles.push(this.mixins.connectionEditTool.getConnectionEditTool.call(this));
userHandles.push(this.mixins.nodeRemoveTool.getNodeRemoveTool.call(this));
userHandles.push(this.mixins.connectorRemoveTool.getConnectorRemoveTool.call(this));
}
return userHandles;
},
/**
* @inheritdoc Terrasoft.Diagram#setPortsVisibilityOnSelectionChange
* @protected
* @override
*/
setPortsVisibilityOnSelectionChange: Terrasoft.emptyFn,
getImageUrl: function (imageName) {
return Terrasoft.ImageUrlBuilder.getUrl({
source: Terrasoft.ImageSources.RESOURCE_MANAGER,
params: {
resourceManagerName: "Terrasoft.Nui",
resourceItemName: "ProcessSchemaDesigner." + imageName
}
});
},
/**
* Overlaps the behavior of double-clicking on the diagram
* @private
*/
customizeDoubleClick: function () {
var self = this;
ej.Diagram.prototype._doubleclick = function (evt) {
if (self.readOnly) {
return;
}
if (this.activeTool.name !== "panTool") {
var args;
args = {
element: this._nodeToHit,
cancel: false,
actualObject: this.activeTool.actualObject,
evt: evt
};
if (!this._isEditing) {
this._raiseEvent("doubleClick", args);
}
if (this._nodeToHit) {
this.activeTool._doubleClick(this._nodeToHit);
}
if (!this.activeTool.inAction && (this.selectionList.length > 0 || this.activeTool.selectedObject) && !args.cancel) {
var obj = this.activeTool.selectedObject || this.selectionList[0];
if (!this._isEditing && obj.type !== "pseudoGroup" && !args.cancel) {
if (!this._isEditing && obj.shape && obj.shape.type === "text" && this._setLabelEditing(obj.shape.textBlock)) {
self.startEditCaption(obj, this);
} else if (!this._isEditing && (obj.labels.length === 0 || typeof obj.labels.length === "undefined" || obj.labels.length > 0 && this._setLabelEditing(obj.labels[0]))) {
self.startEditCaption(obj, this);
}
}
} else if (this.activeTool instanceof ej.Diagram.PolygonTool) {
this.activeTool.doubleclick(evt);
}
}
var nodeName = evt.target.attributes.nodeName;
if (nodeName) {
var node = this.findNode(nodeName.value);
if (node) {
self.startEditCaption(node, this);
}
}
};
},
/**
* Puts signature chart element in the edit state.
* @protected
* @return {Boolean} True if label editing enabled. See {@link readOnly} and {@link labelEditByDblClickEnabled}
*/
startEditCaption: function (node, context) {
if (this.readOnly || !this.labelEditByDblClickEnabled) {
return false;
}
context.scrollToNode(node);
context._startEdit(node);
return true;
},
/**
* Initializes scroll content offset beetwise last element and diagram borders.
* @private
*/
initScrollContentOffset: function () {
var labelFontSize = Terrasoft.ProcessBaseElementSchema.prototype.fontSize;
var elementLabelsOffset = labelFontSize * this.maxLabelLinesCount;
var emptySpaceOffset = 10;
var scrollContentOffset = elementLabelsOffset + emptySpaceOffset;
this.bottomScrollContentOffset = scrollContentOffset;
this.rightScrollContentOffset = scrollContentOffset;
},
/**
* Set the tspan element to textContent. If the width of the text exceeds labelWidth, then the value
* is truncated and three dots are added at the end.
* @private
* @param {Object} tspan The tspan element
* @param {String} textContent Text value
* @param {Number} labelWidth Width
*/
trimTspanLine: function (tspan, textContent, labelWidth) {
tspan.textContent = textContent;
var testWidth = tspan.getComputedTextLength();
if (testWidth <= labelWidth) {
return;
}
for (var i = textContent.length - 1; i > 0; i--) {
textContent = textContent.substr(0, i);
if (!Ext.isIEOrEdge || !Terrasoft.getIsRtlMode()) {
tspan.textContent = textContent + "...";
}
testWidth = tspan.getComputedTextLength();
if (testWidth <= labelWidth) {
return;
}
}
},
/**
* Append child tspan to text.
* @private
* @param {String} name New tspan name.
* @param {Object} text Parent text element.
* @param {Number} fontSize Font size.
* @param {Object} svg Svg element.
* @return {Object} Created tspan.
*/
appendTspan: function (name, text, fontSize, svg) {
var tspan = svg.tspan({
nodeName: name
});
tspan.style.fontSize = fontSize;
text.appendChild(tspan);
return tspan;
},
/**
* Append label's text with wrapping to text element.
* @private
* @param {Object} text Text element.
* @param {Object} label Label element.
* @param {String} tspanName Tspan's name.
* @param {Object} svg Svg element.
*/
appendWrapText: function (text, label, tspanName, svg) {
var lineCount = 1;
var self = this;
var appendLine = function (newWord) {
if (lineCount >= self.maxLabelLinesCount) {
return;
}
var newTspan = self.appendTspan(tspanName, text, label.fontSize, svg);
newTspan.textContent = newWord;
lineCount++;
return newTspan;
};
var labelText = label.text;
var maxWidth = label.width;
var words = labelText.match(this.wrapTextRegExp);
var tspan = this.appendTspan(tspanName, text, label.fontSize, svg);
var word, currentLine, testWidth;
for (var n = 0; n < words.length; n++) {
if (lineCount > this.maxLabelLinesCount) {
break;
}
word = words[n].trim();
if (word.match(/([\r\n])/)) {
tspan = appendLine("");
continue;
}
currentLine = tspan.textContent.trim();
if (!currentLine && !word) {
continue;
}
tspan.textContent += words[n];
testWidth = tspan.getComputedTextLength();
if (testWidth <= maxWidth) {
continue;
}
if (!currentLine) {
this.trimTspanLine(tspan, word, maxWidth);
if (n < words.length - 1) {
tspan = appendLine("");
}
} else {
if (lineCount < self.maxLabelLinesCount) {
this.trimTspanLine(tspan, currentLine, maxWidth);
tspan = appendLine(word);
} else {
this.trimTspanLine(tspan, tspan.textContent, maxWidth);
break;
}
}
}
if (tspan) {
this.trimTspanLine(tspan, tspan.textContent, maxWidth);
}
},
/**
* Overrides labels dividing.
* @private
*/
customizeWrapText: function () {
var self = this;
var base = ej.Diagram.SvgContext._wrapText;
ej.Diagram.SvgContext._wrapText = function (node, textBBox, text, label, svg) {
if (label.defaultRendering === true) {
return base.apply(this, arguments);
}
ej.Diagram.Util.attr(text, {
"pointer-events": "auto"
});
while (text.hasChildNodes()) {
text.removeChild(text.lastChild);
}
var childNodes = text.childNodes;
if (!childNodes || label.text.length <= 0) {
return;
}
self.appendWrapText(text, label, node.name, svg);
this._wrapTextAlign(text, childNodes, label.fontSize, label.textAlign);
};
},
/**
* Overrides labels background painting.
* @private
*/
customizeAlignTextOnLabel: function () {
var self = this;
ej.Diagram.SvgContext._alignTextOnLabel = function (node, nodeBounds, text, label, svg) {
var isConnector = node.nodeType === Terrasoft.diagram.UserHandlesConstraint.Connector;
self.utils.labelUtils.alignTextOnLabel({
node: node,
nodeBounds: nodeBounds,
text: text,
label: label,
svg: svg,
connectorCaptionOffset: {
x: self.connectorCaptionOffsetX,
y: self.connectorCaptionOffsetY
},
labelBackgroundPadding: isConnector ? 0 : self.nodeLabelBackgroundPadding
});
};
},
/**
* Overrides text redactor creation.
* @private
*/
customizeCreateEditBox: function () {
var self = this;
ej.Diagram.prototype._updateEditor = Ext.emptyFn;
var base = ej.Diagram.prototype._createEditBox;
ej.Diagram.prototype._createEditBox = function (label, x, y, width, height, shape) {
var node = self.diagram.findNode(shape.name);
if (node.offsetX) {
x = node.offsetX - label.width / 2;
} else {
x = x + width / 2 - label.width / 2;
y = y + height / 2;
}
var borderOffset = 2;
y += borderOffset;
width = label.width;
height = self.maxLabelLinesCount * label.fontSize;
base.call(this, label, x, y, width, height, shape);
};
},
/**
* Overlaps the click on the chart.
* @private
*/
customizeMouseDown: function () {
var self = this;
var base = ej.Diagram.prototype._mousedown;
ej.Diagram.prototype._mousedown = function (evt) {
var node = this._findNodeUnderMouse(evt);
if (self.readOnly) {
evt.preventDefault();
var selectedNode = this.selectionList[0];
if (selectedNode && (!node || node.name !== selectedNode.name)) {
self.changeNodeConnectorsSelection(selectedNode, false);
ej.Diagram.Util.clear(this.selectionList);
}
if (node && node.type === "node" && (!selectedNode || node.name !== selectedNode.name)) {
this.selectionList.push(node);
self.changeNodeConnectorsSelection(node, true);
}
return;
}
var selectionList;
if (this.selectionList.length > 0 && this.selectionList[0].type === "pseudoGroup") {
selectionList = this.selectionList[0].children;
}
if (!evt.ctrlKey && node && selectionList) {
if (!Ext.Array.contains(this.selectionList[0].children, node.name)) {
this._clearSelection();
}
}
var isSelectConnector = evt.ctrlKey && node && selectionList && node.nodeType === Terrasoft.diagram.UserHandlesConstraint.Connector;
if (isSelectConnector) {
if (Ext.Array.contains(selectionList, node.targetNode) && Ext.Array.contains(selectionList, node.sourceNode)) {
return;
} else {
this._clearSelection();
}
}
this.tools.ConnectorRemoveHandle.currentPoint = this._mousePosition(evt);
base.apply(this, arguments);
};
},
/**
* Override diagram mouse up.
* @private
*/
customizeMouseUp: function () {
ej.Diagram.prototype._mouseup = function (evt) {
if (!this._isPinching) {
if (this._invoke(evt)) {
var foreignObject = this._isForeignObject(evt.originalEvent.target);
if (!foreignObject) {
evt.preventDefault();
}
if (evt.target.id === this._id + "_canvas_svg") {
this._raiseEvent("click", { element: this });
}
this.activeTool.mouseup(evt);
if (this.activeTool instanceof ej.datavisualization.Diagram.PanTool) {
document.onmousemove = null;
document.onmouseup = null;
}
if (this._selectedSymbol) {
var args = { element: this.selectionList[0], cancel: false };
if (args.cancel === true) {
this._remove(args.element);
}
var node = this.selectionList[0];
if (node) {
if (node.labels.length > 0 && node.labels[0].mode === "edit") {
this._isEditing = true;
this.startLabelEdit(node, node.labels[0]);
} else {
this.element[0].focus();
}
this._raiseDropEvent(args);
this._raiseEvent("selectionChange", args);
}
var childTable = {};
if (this.selectionList && this.selectionList.length > 0) {
childTable = this._getChildTable(this.selectionList[0], childTable);
}
var entry = {
type: "collectionchanged",
object: jQuery.extend(true, {}, node),
childTable: jQuery.extend(true, {}, childTable),
changeType: "insert"
};
this.addHistoryEntry(entry);
this._selectedSymbol = null;
}
var activePolygonTool = this.activeTool instanceof ej.datavisualization.Diagram.PolygonTool;
var activeTextTool = this.activeTool instanceof ej.datavisualization.Diagram.TextTool;
var activeElement = document.activeElement;
if (!activePolygonTool && !activeTextTool && !this._isEditing && !foreignObject && activeElement && activeElement.id === "focusInput") {
this.element[0].focus();
}
this._currentCursor = this.activeTool.cursor;
this._updateCursor();
}
}
};
},
* Changes the state of the selected streams of the chart element.
* @param {ej.Diagram.Node} node The element of the diagram.
* @param {Boolean} selected The state of the element's selectivity.
*/
changeNodeConnectorsSelection: function (node, selected) {
var connectors = [].concat(node.inEdges).concat(node.outEdges);
Terrasoft.each(connectors, function (connectorName) {
this.changeConnectorSelection(connectorName, selected, "animated");
}, this);
},
/**
* Change connectors selection state.
* @param {String} connectorName Connector name.
* @param {Boolean} selected Selected state.
* @param {String} [className = selected] Selected class name.
*/
changeConnectorSelection: function (connectorName, selected, className) {
var connector = document.getElementById(connectorName);
if (!connector) {
return;
}
var classList = connector.classList;
className = className || "selected";
if (selected) {
classList.add(className);
} else {
classList.remove(className);
}
},
/**
* Overrides the drawing of the port selection in the diagram.
* @private
*/
customizeDrawPortHighlighter: function () {
var connectorPortColor = this.connectorPortColor;
ej.Diagram.SvgContext._drawPortHighlighter = function (port, node, svg, parent) {
if (!port) {
return;
}
var shape, size, point, transform;
var fill = connectorPortColor;
var border = connectorPortColor;
var borderWidth = 2;
size = ej.Diagram.Size(port.size * 2, port.size * 2);
point = ej.Diagram.Util._getPortPosition(port, ej.Diagram.Util.bounds(node, true));
var matrix = ej.Matrix.identity();
ej.Matrix.rotate(matrix, node.rotateAngle, node.offsetX, node.offsetY);
point = ej.Matrix.transform(matrix, point);
transform = "rotate(" + 0 + "," + (point.x + size.width / 2) + "," + (point.y + size.height / 2) + ")";
shape = svg.circle({
id: "portHighlighter",
class: "ej-d-port",
cx: point.x,
cy: point.y,
r: 3.5,
fill: fill,
stroke: border,
"stroke-width": borderWidth,
"pointer-events": "none",
transform: transform
});
parent.appendChild(shape);
return shape;
};
},
/**
* Overrides the F2 key press handler for control flows.
* @private
*/
customizeKeyDown: function () {
var base = ej.Diagram.prototype._keydown;
var self = this;
ej.Diagram.prototype._keydown = function (evt) {
if (this._isInternalTool(this.activeTool) && this.activeTool.inAction) {
evt.preventDefault();
return;
}
var keycode = evt.keyCode ? evt.keyCode : evt.which;
if (self.readOnly) {
if (evt.ctrlKey && !evt.altKey && !evt.shiftKey && keycode === Ext.EventObject.A) {
evt.preventDefault();
}
return;
}
if (!this._isEditing && (self.disabledKeys && self.disabledKeys.indexOf(keycode) > -1 || self.disabledCtrlKeys && evt.ctrlKey && self.disabledCtrlKeys.indexOf(keycode) > -1)) {
return;
}
switch (keycode) {
case Ext.EventObject.F2:
if (this.selectionList.length > 0) {
var node = this.selectionList[0];
if (node.nodeType === Terrasoft.diagram.UserHandlesConstraint.Connector) {
self.startEditCaption(node, this);
return;
}
}
break;
case Ext.EventObject.ESC:
var editBox = document.getElementById(this.element[0].id + "_editBox");
if (editBox) {
this._isEditing = false;
this._off($(editBox), "focusout", this._editboxfocusout);
this._off($(editBox), "keyup", this._editBoxKeyUp);
this._off($(editBox), "input", this._editBoxTextChange);
var element = $("#" + this.element[0].id + "_editBoxDiv")[0];
if (element) {
element.parentNode.removeChild(element);
}
Ext.get(this.element[0].id).focus();
return;
}
break;
case Ext.EventObject.DELETE:
if (!this._isEditing) {
self.removeSelectedItems();
return;
}
break;
}
base.apply(this, arguments);
};
},
/**
* Overrides the handler for pressing the ESC key for all tokens.
* If you click on ESC, the selection from the element is not removed.
* @private
*/
customizeToolBaseKeyDown: function () {
ej.Diagram.ToolBase.prototype.keydown = function (evt) {
var keycode = evt.keyCode ? evt.keyCode : evt.which;
if (keycode === Ext.EventObject.ESC && this.inAction) {
this.abort();
}
};
},
/**
* Overlaps the selection logic of the element in the diagram. Tracks should not stand out.
* @private
*/
customizeDecideSelectedItem: function () {
var base = ej.Diagram.ToolBase.prototype._decideSelectedItem;
ej.Diagram.ToolBase.prototype._decideSelectedItem = function (evt, node) {
if (node) {
var parentNodeName = node.parent;
if (parentNodeName) {
var parentNode = this.diagram.nameTable[parentNodeName];
if (parentNode.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane || parentNode.nodeType === Terrasoft.diagram.UserHandlesConstraint.Subprocess) {
return node;
}
}
}
return base.apply(this, arguments);
};
},
/**
* Calculates the dimensions and position of the container.
* @param {Object} containerRect The current dimensions of the container.
* @param {Number} containerRect.x Container position along the X axis.
* @param {Number} containerRect.y Container position along the Y axis.
* @param {Number} containerRect.width Container width.
* @param {Number} containerRect.height Container height.
* @param {Object} point The point for which you want to calculate.
* @param {Number} point.x The х-axis coordinate of the point.
* @param {Number} point.y The y-axis coordinate of the point.
* @param {String} resizeDiraction The direction of resizing, the following values are possible:
* - n-resize - Resize top.
* - e-resize - Resize right.
* - w-resize - Resize left.
* - s-resize - Resize bottom.
* - ne-resize - Resize top right corner
* - nw-resize - Resize top left corner.
* - se-resize - Resize bottom right corner.
* - sw-resize - Resize bottom left corner.
* @return {Object} The calculated position and dimensions of the container.
* @return {Number} return.x The position along the x-axis.
* @return {Number} return.y The position along the Y axis.
* @return {Number} return.width Width.
* @return {Number} return.height Height.
*/
calculateExpectedContainerRectangle: function (containerRect, point, resizeDiraction) {
var baseTopLeftPoint = ej.Diagram.Point(containerRect.x, containerRect.y);
var baseBottomRightPoint = ej.Diagram.Point(containerRect.x + containerRect.width, containerRect.y + containerRect.height);
var topLeftPoint = ej.Diagram.Point(containerRect.x, containerRect.y);
var bottomRightPoint = ej.Diagram.Point(containerRect.x + containerRect.width, containerRect.y + containerRect.height);
switch (resizeDiraction) {
case "n-resize":
topLeftPoint = ej.Diagram.Point(baseTopLeftPoint.x, point.y);
break;
case "e-resize":
bottomRightPoint = ej.Diagram.Point(point.x, baseBottomRightPoint.y);
break;
case "w-resize":
topLeftPoint = ej.Diagram.Point(point.x, baseTopLeftPoint.y);
break;
case "s-resize":
bottomRightPoint = ej.Diagram.Point(baseBottomRightPoint.x, point.y);
break;
case "ne-resize":
topLeftPoint = ej.Diagram.Point(baseTopLeftPoint.x, point.y);
bottomRightPoint = ej.Diagram.Point(point.x, baseBottomRightPoint.y);
break;
case "nw-resize":
topLeftPoint = ej.Diagram.Point(point.x, point.y);
break;
case "se-resize":
bottomRightPoint = ej.Diagram.Point(point.x, point.y);
break;
case "sw-resize":
topLeftPoint = ej.Diagram.Point(point.x, baseTopLeftPoint.y);
bottomRightPoint = ej.Diagram.Point(baseBottomRightPoint.x, point.y);
break;
}
return ej.Diagram.Rectangle(topLeftPoint.x, topLeftPoint.y, bottomRightPoint.x - topLeftPoint.x, bottomRightPoint.y - topLeftPoint.y);
},
/**
* Overlaps the logic of drawing the borders of the selected element in the diagram.
* @private
*/
customizeDiagramSelectors: function () {
var transformBorderElement = function (shape, svg, scale) {
if (!shape.segments) {
var borderEl = svg.getElementById(svg.document.id + "handle_g");
if (!borderEl) {
return;
}
var bounds = ej.Diagram.Util.bounds(shape);
var translateString = "translate(" + bounds.x * scale + ", " + bounds.y * scale + ")";
var rotateString = "rotate(" + shape.rotateAngle + "," + bounds.width / 2 * scale + "," + bounds.height / 2 * scale + ")";
var attributes = {
id: svg.document.id + "handle_g",
transform: translateString + ", " + rotateString
};
svg.g(attributes);
}
};
var svgContext = ej.Diagram.SvgContext;
var baseUpdateSelector = svgContext.updateSelector;
svgContext.updateSelector = function (shape, svg, scale) {
baseUpdateSelector.apply(this, arguments);
transformBorderElement(shape, svg, scale);
};
var baseRenderSelector = svgContext.renderSelector;
svgContext.renderSelector = function (shape, svg, parent, scale) {
baseRenderSelector.apply(this, arguments);
transformBorderElement(shape, svg, scale);
};
},
/**
* Drop node to selected connector.
* @param {ej.Diagram.Node} node Dropping node.
* @param {Object} segment Selected connector's segment.
* @param {Object} segment.startPoint Start point
* @param {Number} segment.startPoint.x Start point horizontal coordinate
* @param {Number} segment.startPoint.y Start point vertical coordinate.
* @param {Object} segment.endPoint End point
* @param {Number} segment.endPoint.x End point horizontal coordinate
* @param {Number} segment.endPoint.y End point vertical coordinate.
* @param {ej.Diagram} diagram Diagram.
* @param {Object} mousePosition Mouse position.
* @param {Number} mousePosition.x Horizontal coordinate.
* @param {Number} mousePosition.y Vertical coordinate.
* @param {Boolean} ctrlKey Ctrl key button state.
*/
dropNodeToConnector: function (node, segment, diagram, mousePosition, ctrlKey) {
var nodeBounds = ej.Diagram.Util.bounds(node);
var offsetX = 0;
var offsetY = 0;
if (segment.startPoint.x === segment.endPoint.x) {
offsetX = nodeBounds.center.x - segment.startPoint.x;
} else {
offsetY = nodeBounds.center.y - segment.startPoint.y;
}
node.offsetX -= offsetX;
node.offsetY -= offsetY;
diagram.updateNode(node.name, {});
mousePosition.x += offsetX;
mousePosition.y += offsetY;
this.updateItemsLocation(node, {
x: mousePosition.x,
y: mousePosition.y,
ctrlKey: ctrlKey
});
var diagramInstance = this.getInstance();
var connector = diagramInstance.nameTable[segment.connectorName];
if (this.fireEvent("insertNodeInConnector", node.name, connector) === false) {
var oldTargetNode = diagramInstance.nameTable[connector.targetNode];
var index = oldTargetNode.inEdges.indexOf(connector.name);
if (index > -1) {
oldTargetNode.inEdges.splice(index, 1);
}
connector.targetNode = node.name;
node.inEdges.push(connector.name);
ej.Diagram.Util.dock(connector, diagram.nameTable);
this.changeConnectorSelection(connector.name, false);
this.setSelection(node);
}
},
/**
* Overrides the MouseUp event handler.
* @private
*/
customizeMoveToolMouseUp: function () {
var scope = this;
var baseMouseup = ej.Diagram.MoveTool.prototype.mouseup;
ej.Diagram.MoveTool.prototype.mouseup = function (event) {
var node;
if (this.newObject) {
node = this.newObject;
this.newObject = null;
this.inAction = false;
} else {
node = this.selectedObject;
}
baseMouseup.apply(this, arguments);
if (!node) {
return;
}
var diagram = this.diagram;
var segment = diagram.selectedSegment;
if (segment) {
var mousePosition = diagram._mousePosition(event);
scope.dropNodeToConnector(node, segment, diagram, mousePosition, event.ctrlKey);
}
scope.updateNodeConnectors(node, diagram, true);
};
},
/**
* @inheritdoc Terrasoft.Diagram#customizeDiagram
* @protected
* @override
*/
customizeDiagram: function () {
this.callParent(arguments);
Terrasoft.ProcessSchemaDiagram.instance = this;
Terrasoft.ProcessSchemaDiagram.customizeResizeTool();
Terrasoft.ProcessSchemaDiagram.customizeGetNodeBounds();
Terrasoft.ProcessSchemaDiagram.customizeMoveTool();
this.customizeDoubleClick();
this.customizeWrapText();
this.customizeAlignTextOnLabel();
this.customizeMouseDown();
this.customizeMouseUp();
this.customizeDrawPortHighlighter();
this.customizeCreateEditBox();
this.customizeKeyDown();
this.customizeToolBaseKeyDown();
this.customizeDecideSelectedItem();
this.customizeDiagramSelectors();
this.customizeUpdateLabels();
this.customizeHasSameParent();
this.customizeGroup();
this.customizeSelectMouseUp();
this.customizeSelectAll();
this.customizeTranslate();
this.customizeGetProcessedObject();
this.customizeProcessCtrlKey();
this.customizePhaseToolMouseUp();
this.customizeMoveToolMouseUp();
this.customizeCheckToolToActivate();
this.customizePaste();
this.customizeCopy();
this.customizeNodeBoundsExtension();
ej.Diagram.prototype._updateLabelPosition = Ext.emptyFn;
if (this.readOnly) {
ej.Diagram.prototype._updateCursor = Ext.emptyFn;
}
},
renderToolFilter: function (node, svg) {
/*jslint bitwise: true */
if (node.constraints & ej.Diagram.NodeConstraints.Shadow) {
var filterNodeName = node.name + "_filter";
var existingFilter = svg.getElementById(filterNodeName);
if (existingFilter) {
return "url(#" + filterNodeName + ")";
}
var defs = svg.getElementsByTagName("defs")[0];
var width = node.width + 10;
var height = node.height + 10;
if (ej.browserInfo().name === "chrome") {
width = node.width / 3;
height = node.height / 3;
}
var filter = svg.filter({
id: node.name + "_filter",
width: width,
height: height
});
var offset = svg.feOffset({
result: "offOut",
"in": "SourceAlpha",
dx: "1",
dy: "1"
});
filter.appendChild(offset);
var matrix = svg.feColorMatrix({
result: "matrixOut",
"in": "offOut",
type: "matrix",
values: "0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.3 0"
});
filter.appendChild(matrix);
var blur = svg.feGaussianBlur({
result: "blurOut",
"in": "matrixOut",
stdDeviation: "2"
});
filter.appendChild(blur);
var blend = svg.feBlend({
"in": "SourceGraphic",
in2: "blurOut",
mode: "normal"
});
filter.appendChild(blend);
defs.appendChild(filter);
return "url(#" + filterNodeName + ")";
}
/*jslint bitwise: false */
return null;
},
/**
* @inheritdoc Terrasoft.Component#onAfterRender
* @protected
* @override
*/
onAfterRender: function () {
this.callParent(arguments);
this.mixins.droppable.onAfterRender.apply(this, arguments);
},
/**
* @inheritdoc Terrasoft.Component#onAfterReRender
* @protected
* @override
*/
onAfterReRender: function () {
this.callParent(arguments);
this.mixins.droppable.onAfterReRender.apply(this, arguments);
},
/**
* @inheritdoc Terrasoft.diagram#onDestroy
* @override
*/
onDestroy: function () {
this.mixins.droppable.onDestroy.apply(this, arguments);
this.callParent(arguments);
},
/**
* Updates all the connectors in the chart element.
* @param {ej.Diagram.Node} node The element of the diagram.
* @param {Object} diagram Instance of the diagram.
* @param {Boolean} resetSegments A symptom of resetting the break points of the connectors of an element.
* Auto-path is built for the connector.
* @param {String[]} [connectors] An array that will be filled with the names of the connectors of the
* processed chart elements.
*/
updateNodeConnectors: function (node, diagram, resetSegments, connectors) {
var connectorsNames = connectors || [];
if (node.isContainer === true && node.isExpanded === true) {
this.iterateItemChildrens(node, function (child) {
this.updateNodeConnectors(child, diagram, false, connectorsNames);
}, this);
} else {
diagram._updateAssociatedConnectorEnds(node, diagram.nameTable);
ej.Diagram.SvgContext._updateAssociatedConnector(node, diagram._svg, diagram);
var nodeConnectors = Ext.Array.merge(node.inEdges, node.outEdges);
nodeConnectors.forEach(function (name) {
if (connectorsNames.indexOf(name) === -1) {
connectorsNames.push(name);
}
});
}
if (resetSegments) {
this.resetSegments(connectorsNames);
}
},
/**
* Reset all breakpoints of the transferred connectors.
* @param {String[]} connectorsName An array of connector names.
*/
resetSegments: function (connectorsName) {
connectorsName.forEach(function (connectorName) {
var diagram = this.getInstance();
diagram.updateConnector(connectorName, {
segments: [{ type: "orthogonal" }]
});
}, this);
this.updateSequenceFlowPolylinePointPositions(connectorsName);
},
/**
* @inheritdoc Terrasoft.controls.Diagram#updateConnectorSelection
* @override
*/
updateConnectorSelection: Terrasoft.emptyFn,
/**
* Compares the break points of the connectors with diagrams with the process element
* {@link Terrasoft.ProcessSequenceFlowSchema}.
* If the breakpoints do not match, an event is generated {@link #polylinePointPositionsChanged}
* @param {String[]} connectorsName
*/
updateSequenceFlowPolylinePointPositions: function (connectorsName) {
var changedConnectors = [];
connectorsName.forEach(function (connectorName) {
var connectorPoints = this.getConnectorPolylinePointsPosition(connectorName);
var sequenceFlow = this.items.get(connectorName);
var serializedConnectorPoints = this.serializePoints(connectorPoints);
var serializedSequenceFlowPoints = this.serializePoints(sequenceFlow.polylinePointPositions);
if (serializedConnectorPoints !== serializedSequenceFlowPoints) {
changedConnectors.push({
name: connectorName,
polylinePointPositions: connectorPoints
});
}
}, this);
if (changedConnectors.length > 0) {
this.fireEvent("polylinePointPositionsChanged", changedConnectors);
}
},
/**
* Returns an array of fracture points of the transmitted connector.
* @param {string} connectorName The name of the connector.
* @return {ej.Diagram.Point[]}
*/
getConnectorPolylinePointsPosition: function (connectorName) {
var connector = this.getElementById(connectorName);
var connectorPoints = this.utils.labelUtils.getSequenceFlowPoints(connector);
connectorPoints.shift();
connectorPoints.pop();
return connectorPoints;
},
/**
* Returns a serialized array of points.
* @param {ej.Diagram.Point[]} points Array of points.
* @return {String}
*/
serializePoints: function (points) {
return Terrasoft.serializeObject({
source: points || [],
valueConverter: function (point) {
return "{" + point.x + ";" + point.y + "}";
}
});
},
/**
* Highlights the transferred item in the diagram.
* @param {ej.Diagram.Node} node Element of the diagram.
*/
setSelection: function (node) {
var diagram = this.getInstance();
diagram._clearSelection();
diagram.addSelection(node);
},
/**
* Returns the track of the transferred item.
* @param {ej.Diagram.Node} item Diagram element.
* @return {ej.Diagram.Node}
*/
findItemContainer: function (item) {
if (item.nodeType === Terrasoft.diagram.UserHandlesConstraint.Connector) {
return null;
}
var laneName = item.parent;
return this.getElementById(laneName);
},
/**
* Returns the lane in which the transmitted point is located.
* @param {ej.Diagram.Point} point The point for which the search is performed.
* @return {ej.Diagram.Node} Track element.
*/
findTargetLane: function (point) {
var lanesData = this.lanes.map(function (laneCfg) {
return {
lane: laneCfg.lane,
lineBounds: ej.Diagram.Util.bounds(laneCfg.laneLine)
};
});
lanesData.sort(function (item1, item2) {
return item1.lineBounds.bottom - item2.lineBounds.bottom;
});
var filteredLanesData = lanesData.filter(function (laneData) {
return laneData.lineBounds.bottom < point.y;
});
var targetLaneData = filteredLanesData.pop() || {};
return targetLaneData.lane;
},
/**
* Returns the container within which the passed point of the diagram is located.
* @param {Object} point The point on the diagram.
* @param {Number} point.x The x-axis coordinate.
* @param {Number} point.y The y-axis coordinate.
* @param {String[]} [excludeNames] The names of the elements to exclude from the search.
* @return {ej.Diagram.Node} The element of the container.
*/
findContainerAtPosition: function (point, excludeNames) {
var containers = this.items.filterByFn(function (item) {
if (item.isContainer !== true || excludeNames && excludeNames.indexOf(item.name) !== -1) {
return false;
}
if (item.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane) {
return false;
}
var node = this.getElementById(item.name);
var bounds = ej.Diagram.Util.bounds(node);
return ej.Diagram.Geometry.containsPoint(bounds, point);
}, this);
var containersCount = containers.getCount();
if (containersCount === 0) {
return this.findTargetLane(point);
}
var self = this;
containers.sortByFn(function (item1, item2) {
var node1 = self.getElementById(item1.name);
var node2 = self.getElementById(item2.name);
return node1.zOrder - node2.zOrder;
});
var container = containers.getByIndex(containersCount - 1);
return this.getElementById(container.name);
},
/**
* Find connector's segment which intersetcs with rect.
* @param {Object} rect Rect.
* @param {Number} rect.x Horizontal coordinate.
* @param {Number} rect.y Vertical coordinate.
* @param {Number} rect.width Width.
* @param {Number} rect.height Height.
* @return Object{connectorName: String, startPoint: Number, endPoint: Number}
*/
findSegmentAtRect: function (rect) {
var result = null;
var item = this.items.collection.findBy(function (item) {
if (item.nodeType !== Terrasoft.diagram.UserHandlesConstraint.Connector) {
return false;
}
var connector = this.getElementById(item.name);
var point1, point2, segmentRect;
var intersectsSegment = connector.segments.some(function (segment) {
for (var i = 1; i < segment.points.length; i++) {
point1 = segment.points[i - 1];
point2 = segment.points[i];
segmentRect = {
x: Math.min(point1.x, point2.x),
y: Math.min(point1.y, point2.y),
width: Math.max(Math.abs(point1.x - point2.x), 1),
height: Math.max(Math.abs(point1.y - point2.y), 1)
};
if (ej.Diagram.Geometry.intersectsRect(rect, segmentRect)) {
result = {
startPoint: point1,
endPoint: point2
};
return true;
}
}
});
return intersectsSegment;
}, this);
if (item) {
result.connectorName = item.name;
}
return result;
},
/**
* Changes the container for the transferred item.
* @param {ej.Diagram.Node} item Chart element.
* @param {ej.Diagram.Node} currentContainer The current container in which the element is located.
* @param {ej.Diagram.Node} targetContainer The new container into which to place the element.
*/
changeItemContainer: function (item, currentContainer, targetContainer) {
var diagram = this.getInstance();
this.removeItemBoundsCache(item);
ej.Diagram.Util.removeItem(currentContainer.children, item.name);
item.parent = targetContainer.name;
this.updateChildsOrder(item, targetContainer.zOrder + 1);
targetContainer.children.push(item.name);
ej.Diagram.SvgContext._removeFromContainer(item, diagram._svg);
this.renderItem(item, targetContainer.name);
},
updateChildsOrder: function (item, zOrder) {
item.zOrder = zOrder;
var diagram = this.getInstance();
ej.Diagram.SvgContext._updateLabels(item, diagram._svg);
if (item.isContainer) {
this.iterateItemChildrens(item, function (child) {
this.updateChildsOrder(child, zOrder + 1);
}, this);
}
},
/**
* Updates the size of the lane by its contents.
* @param {ej.Diagram.Node} lane Element of the lane.
*/
updateLaneSize: function (lane) {
var diagram = this.getInstance();
var contentRectangle = this.getLaneContentRectangle(lane, true);
var height = contentRectangle.height;
lane.height = height;
lane.offsetY = contentRectangle.y + height / 2;
ej.Diagram.DiagramContext.update(lane, diagram);
},
/**
* ВReturns the size of the contents of the track, taking into account the title of the lane.
* @param {ej.Diagram.Node} lane The lane item.
* @param {Boolean} [updateCache] Indicates the cache update.
* @return {Object} The position is returned for the upper-left corner.
* @return {Number} return.x Horizontal offset.
* @return {Number} return.y Vertical offset.
* @return {Number} return.width Width.
* @return {Number} return.height Height.
*/
getLaneContentRectangle: function (lane, updateCache) {
var laneName = lane.name;
var cachedBounds = this.itemsBounds[laneName];
if (cachedBounds && updateCache !== true) {
return cachedBounds;
}
var bounds = this.getContainerContentRectangle(lane, updateCache);
this.itemsBounds[laneName] = bounds;
return bounds;
},
/**
* Returns the size of the contents of the container.
* @param {ej.Diagram.Node} container.
* @param {Boolean} [updateCache] Cache Update Flag.
* @return {Object} The position is returned for the upper-left corner.
* @return {Number} return.x Horizontal offset.
* @return {Number} return.y Vertical offset.
* @return {Number} return.width Width.
* @return {Number} return.height Height.
*/
getContainerContentRectangle: function (container, updateCache) {
var bounds;
var excludeNodes = [];
if (container.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane) {
var lineName = container.lineName;
excludeNodes.push(lineName);
var laneLine = this.getElementById(lineName);
bounds = this.getItemBounds(laneLine, updateCache);
}
this.iterateItemChildrens(container, function (child) {
if (excludeNodes.indexOf(child.name) !== -1) {
return;
}
var childBounds = this.getItemBounds(child, updateCache);
if (bounds) {
bounds = ej.Diagram.Geometry.union(bounds, childBounds);
} else {
bounds = childBounds;
}
}, this);
return bounds;
},
/**
* The mouseup event handler.
* @param {Object} e Event Arguments MouseUp.
*/
onMouseUp: function (e) {
var selectedItem = this.getSelectedItem();
if (!selectedItem || selectedItem.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane || selectedItem.nodeType === Terrasoft.diagram.UserHandlesConstraint.Connector) {
return;
}
var diagram = this.getInstance();
if (selectedItem.type === "pseudoGroup" && (diagram.activeTool.name === "select" || !selectedItem.needUpdatePosition && diagram.activeTool.name === "move")) {
return;
} else {
selectedItem.needUpdatePosition = false;
}
if (e.isPropagationStopped() === true) {
this.fixItemPosition(selectedItem);
var itemContainer = this.getElementById(selectedItem.parent);
this.resizeContainerByContent(itemContainer);
this.updateLanes();
this.updateNodeConnectors(selectedItem, diagram);
this.setSelection(selectedItem);
this.updateItemPosition(selectedItem);
return;
}
var mousePosition = diagram._mousePosition(e);
this.updateItemsLocation(selectedItem, {
x: mousePosition.x,
y: mousePosition.y,
ctrlKey: e.ctrlKey
});
selectedItem.needUpdatePosition = false;
},
/**
* The drag event handler
* @param {Object} e Arguments for the drag event.
*/
onDrag: function (e) {
var selectedItem = e.element;
if (!selectedItem || selectedItem.type !== "pseudoGroup") {
return;
}
selectedItem.needUpdatePosition = true;
},
/**
* Checks whether container of an item needs to be changed.
* @protected
* @param {ej.Diagram.Node} currentContainer Current item container.
* @param {ej.Diagram.Node} targetContainer Target item container.
* @param {Object} cursorPoint Mouse location point.
* @returns {boolean} Returns true if item container needs to be changed.
*/
getItemContainerNeedsToBeChanged: function (currentContainer, targetContainer, cursorPoint) {
var isNotMovingBetweenLanes = !(targetContainer.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane && currentContainer.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane);
return currentContainer.name !== targetContainer.name && (cursorPoint.ctrlKey !== true || cursorPoint.ctrlKey === true && isNotMovingBetweenLanes);
},
/**
* Handles the drag event of the items.
* @param {ej.Diagram.Node} item Chart element.
* @param {Object} cursorPoint The location of the mouse cursor.
*/
updateItemsLocation: function (item, cursorPoint) {
var diagram = this.getInstance();
var items = [];
var containers = [];
var updateItems = [];
var isPseudoGroup = item.type === "pseudoGroup";
if (isPseudoGroup) {
updateItems = item.children;
} else {
updateItems.push(item.name);
}
var targetContainer = this.findContainerAtPosition(cursorPoint, updateItems);
var containerBounds = ej.Diagram.Util.bounds(targetContainer);
var offset = ej.Diagram.Point();
if (isPseudoGroup) {
offset = this.getNodeRelativePosition(item, targetContainer);
var padding = this.getContainerPadding(item);
offset.x = offset.x < 0 ? offset.x - padding : 0;
offset.y = offset.y < 0 ? offset.y - padding : 0;
}
updateItems.forEach(function (itemName) {
var childItem = this.getElementById(itemName);
if (childItem.nodeType !== Terrasoft.diagram.UserHandlesConstraint.Connector && !Ext.Array.contains(updateItems, childItem.parent)) {
var currentContainer = this.findItemContainer(childItem);
if (this.getItemContainerNeedsToBeChanged(currentContainer, targetContainer, cursorPoint)) {
var itemBounds = ej.Diagram.Util.bounds(childItem);
childItem.offsetX = itemBounds.x - containerBounds.x + itemBounds.width / 2;
childItem.offsetY = itemBounds.y - containerBounds.y + itemBounds.height / 2;
this.changeItemContainer(childItem, currentContainer, targetContainer);
containers.push({
itemName: childItem.name,
containerName: targetContainer.name
});
}
if (isPseudoGroup) {
childItem.offsetX -= offset.x;
childItem.offsetY -= offset.y;
} else {
this.fixItemPosition(childItem);
}
this.updateNodeConnectors(childItem, diagram, isPseudoGroup);
var newPosition = {
x: childItem.offsetX - childItem.width / 2,
y: childItem.offsetY - childItem.height / 2
};
var currentPosition = this.items.get(childItem.name).position;
if (newPosition.x !== currentPosition.X || newPosition.y !== currentPosition.Y) {
items.push({
itemName: childItem.name,
position: newPosition
});
}
}
}, this);
if (isPseudoGroup) {
if (offset.x !== 0 || offset.y !== 0) {
ej.Diagram.Util._updateGroupBounds(item, diagram);
diagram.updateNode(item.name, {});
}
} else {
this.setSelection(item);
}
this.resizeContainerByContent(targetContainer);
this.bringToFront();
if (containers.length > 0) {
this.fireEvent("itemsContainerChanged", containers);
}
if (items.length > 0) {
this.fireEvent("itemsPositionChanged", items);
}
},
/**
* Updates the item's position in the process if there were any changes.
* @param {ej.Diagram.Node} item The element of the diagram.
*/
updateItemPosition: function (item) {
var currentPosition = this.items.get(item.name).position;
var newPosition = {
x: item.offsetX - item.width / 2,
y: item.offsetY - item.height / 2
};
if (newPosition.x !== currentPosition.X || newPosition.y !== currentPosition.Y) {
this.bringToFront();
this.fireEvent("itemsPositionChanged", [{
itemName: item.name,
position: newPosition
}]);
}
},
/**
* Changes the size of the container by its contents. The method recursively passes through all the parents
* of the transferred element.
* @fires sizeChanged Generates a container change event.
* @param {ej.Diagram.Node} container
*/
resizeContainerByContent: function (container) {
if (!container || container.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane) {
return;
}
var diagram = this.getInstance();
var newSize = this.recalculateContainerSize(container);
var nodePosition = this.getNodeRelativePosition(container);
var width = newSize.width;
var height = newSize.height;
diagram.updateNode(container.name, {
offsetX: nodePosition.x + width / 2,
offsetY: nodePosition.y + height / 2,
width: width,
height: height
});
this.fireEvent("sizeChanged", {
itemName: container.name,
size: {
width: width,
height: height
}
});
var parentNode = this.getElementById(container.parent);
this.resizeContainerByContent(parentNode);
},
/**
* Calculates the size of the container by its contents.
* @param {ej.Diagram.Node} container Container.
* @return {Object} Dimensions of the container.
* @return {Number} return.width Width.
* @return {Number} return.height Height.
*/
recalculateContainerSize: function (container) {
var containerBounds = ej.Diagram.Util.bounds(container);
var containerContentBounds = this.getContainerContentRectangle(container, true);
var contentOffwstX = containerContentBounds.x - containerBounds.x;
var contentOffwstY = containerContentBounds.y - containerBounds.y;
var targetContanerExpectedWidth = containerContentBounds.width + contentOffwstX + this.containerResizePadding;
var targetContanerExpectedHeight = containerContentBounds.height + contentOffwstY + this.containerResizePadding;
return {
width: Math.max(containerBounds.width, targetContanerExpectedWidth),
height: Math.max(containerBounds.height, targetContanerExpectedHeight)
};
},
/**
* Updates the position of the track on the chart.
* @param {ej.Diagram.Node} lane The track element.
* @param {Object} options Optional settings.
* @param {Number} options.offsetY Vertical offset.
*/
updateLanePosition: function (lane, options) {
var offsetY = options.offsetY || 0;
var diagram = this.getInstance();
var laneLine = this.getElementById(lane.lineName);
var diagramWidth = this.getWidth();
laneLine.width = diagramWidth;
laneLine.offsetX = diagramWidth / 2;
var laneLineOffsetY = laneLine.height / 2 + offsetY;
var itemsOffsetY = laneLineOffsetY - laneLine.offsetY;
laneLine.offsetY = laneLineOffsetY;
if (Math.floor(Math.abs(itemsOffsetY))) {
this.updateItemsPosition(lane, {
offsetY: itemsOffsetY
});
}
ej.Diagram.DiagramContext.update(laneLine, diagram);
},
/**
* Updates the position of the elements on the chart.
* @param {ej.Diagram.Node} lane The track element.
* @param {Object} options Optional settings.
* @param {Number} options.offsetY Vertical offset.
*/
updateItemsPosition: function (lane, options) {
var offsetY = options.offsetY || 0;
var lineName = lane.lineName;
var diagram = this.getInstance();
this.iterateItemChildrens(lane, function (item) {
if (item.name === lineName) {
return;
}
diagram.updateNode(item.name, {
offsetY: item.offsetY + offsetY
});
this.updateItemPosition(item);
this.removeItemBoundsCache(item);
}, this);
},
/**
* Updates the sizes and positions of all the tracks in the chart.
*/
updateLanes: function () {
this.lanes.forEach(function (laneCfg, index, lanes) {
var prevLaneCfg = lanes[index - 1];
var offsetY = 0;
if (prevLaneCfg) {
var prevItemBounds = this.getItemBounds(prevLaneCfg.lane);
var prevLaneHeight = prevItemBounds.y + prevItemBounds.height;
offsetY = prevLaneHeight + this.laneMargin;
}
var lane = laneCfg.lane;
this.updateLanePosition(lane, {
offsetY: offsetY
});
this.updateLaneSize(lane);
if (offsetY !== 0) {
this.fireEvent("linePositionChanged", {
itemName: lane.name,
position: {
y: lane.offsetY - lane.height / 2
}
});
}
}, this);
},
/**
* @inheritdoc Terrasoft.controls.Diagram#updatePageSize
* @override
*/
updatePageSize: function () {
this.callParent(arguments);
this.updateLanes();
},
/**
* @inheritdoc Terrasoft.controls.Diagram#onDiagramItemsChange
* overridden
*/
onDiagramItemsChange: function (event) {
this.callParent(arguments);
var changeType = event.changeType;
if (changeType === "remove") {
var node = event.element;
if (node.isContainer) {
var children = [];
this.iterateItemChildrens(node, function (child) {
children.push(child.name);
}, this);
children.forEach(function (childName) {
this.items.removeByKey(childName);
}, this);
}
var parentNode = this.getElementById(node.parent);
if (parentNode) {
ej.Diagram.Util.removeItem(parentNode.children, node.name);
}
this.updateLanes();
}
},
/**
* @inheritdoc Terrasoft.controls.Diagram#onCollectionDataLoaded
* @override
*/
onCollectionDataLoaded: function () {
this.callParent(arguments);
var diagram = this.getInstance();
diagram._clearSelection();
this.updateLanes();
},
/**
* @inheritdoc Terrasoft.controls.Diagram#addItem
*/
addItem: function (item, isDataLoaded) {
switch (item.nodeType) {
case Terrasoft.diagram.UserHandlesConstraint.Lane:
var laneLine = item.children[0];
var diagramWidth = this.getWidth();
laneLine.width = diagramWidth;
laneLine.offsetX = diagramWidth / 2;
laneLine.offsetY = item.offsetY;
var lanes = this.lanes;
this.callParent([item]);
laneLine = this.getElementById(laneLine.name);
lanes.push({
lane: this.getElementById(item.name),
laneLine: laneLine
});
break;
case Terrasoft.diagram.UserHandlesConstraint.Connector:
return this.callParent(arguments);
default:
this.callParent(arguments);
var diagram = this.getInstance();
diagram.activateTool("move");
break;
}
if (isDataLoaded !== true) {
var node = this.getElementById(item.name);
this.fixItemPosition(node);
var itemContainer = this.getElementById(node.parent);
this.resizeContainerByContent(itemContainer);
this.updateLanes();
}
},
/**
* @inheritdoc Terrasoft.controls.Diagram#clearDiagram
*/
clearDiagram: function () {
this.lanes = [];
this.callParent(arguments);
},
/**
* Returns the item's minimum indent from the bounds of the container
* @param {ej.Diagram.Node} container Container, relative to which the position is calculated.
* @return {int}
*/
getContainerPadding: function (container) {
return container.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane ? this.laneMargin : this.containerResizePadding;
},
/**
* Returns the position of the element relative to the container. For the track will return an empty result.
* @param {ej.Diagram.Node} item Chart element.
* @param {ej.Diagram.Node} container Container, relative to which the position is calculated.
*/
getNodeRelativePosition: function (item, container) {
var containerPosition;
if (item.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane) {
return null;
}
if (!container) {
container = this.getElementById(item.parent);
}
if (container.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane) {
var laneLine = this.getElementById(container.lineName);
containerPosition = ej.Diagram.Util.bounds(laneLine);
} else {
containerPosition = ej.Diagram.Util.bounds(container);
}
var itemPosition = ej.Diagram.Util.bounds(item);
return ej.Diagram.Point(itemPosition.x - containerPosition.x, itemPosition.y - containerPosition.y);
},
/**
* Shifts the position of an element if it is between lanes.
* @param {ej.Diagram.Node} item The item to add to the chart.
*/
fixItemPosition: function (item) {
if (item.parent) {
var container = this.findItemContainer(item);
var containerPosition;
if (container.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane) {
var laneLine = this.getElementById(container.lineName);
containerPosition = ej.Diagram.Util.bounds(laneLine);
} else {
containerPosition = {
x: 0,
y: 0
};
}
var padding = this.getContainerPadding(container);
var itemPosition = this.getNodeRelativePosition(item);
var itemOffsetX = containerPosition.x + padding - itemPosition.x;
var itemOffsetY = containerPosition.y + padding - itemPosition.y;
if (itemOffsetY >= 0 || itemOffsetX >= 0) {
var offsetX = itemOffsetX >= 0 ? itemPosition.x + itemOffsetX + item.width / 2 : item.offsetX;
var offsetY = itemOffsetY >= 0 ? itemPosition.y + itemOffsetY + item.height / 2 : item.offsetY;
var diagram = this.getInstance();
diagram.updateNode(item.name, {
offsetX: offsetX,
offsetY: offsetY
});
this.updateItemPosition(item);
this.removeItemBoundsCache(item);
}
}
},
/**
* Overrides the element's caption drawing.
* @private
*/
customizeUpdateLabels: function () {
var baseRenderLabel = ej.Diagram.SvgContext._renderLabel;
ej.Diagram.SvgContext._renderLabel = function (node, label) {
if (!label.text) {
return;
}
baseRenderLabel.apply(this, arguments);
};
ej.Diagram.SvgContext._updateLabels = function (node, svg) {
var labels = node.labels;
for (var i = 0, len = labels.length; i < len; ++i) {
var label = labels[i];
var textElementId = node.name + "_" + label.name;
var text = svg.getElementById(textElementId);
if (label.text || text && !label.isTextRendered) {
if (!text) {
var g = svg.getElementById(node.name);
this._renderLabel(node, label, svg, g);
text = svg.getElementById(textElementId);
}
text.setAttribute("fill", label.fontColor);
var bounds = ej.Diagram.Util.bounds(node);
if (!label.isTextRendered) {
this._wrapText(node, bounds, text, label, svg);
}
if (!label.isTextRendered || node.nodeType === Terrasoft.diagram.UserHandlesConstraint.Connector || node.nodeType === Terrasoft.diagram.UserHandlesConstraint.Subprocess && node.isExpanded) {
this._alignTextOnLabel(node, bounds, text, label, svg);
}
label.isTextRendered = true;
}
}
};
},
/**
* Overlaps the verification of identical parents from descendants.
* @private
*/
customizeHasSameParent: function () {
ej.Diagram.ToolBase.prototype.hasSameParent = function () {
return this.selectedObject.type === "pseudoGroup";
};
},
bringToFront: function () {
var diagram = this.getInstance();
var item = diagram.selectionList[0];
var isPseudoGroup = item.type === "pseudoGroup";
var items = isPseudoGroup ? item.children : [item.name];
items.forEach(function (nodeName) {
var node = diagram.nameTable[nodeName];
var nodes = this.findOverlapNodesWithChildren(node);
if (nodes.length === 0) {
return;
}
var maxOrder = 0;
for (var n in nodes) {
var order = nodes[n].zOrder;
if (order > maxOrder) {
maxOrder = order;
}
}
this.updateChildsOrder(node, maxOrder + 1);
if (isPseudoGroup) {
return;
}
var parent = diagram.nameTable[node.parent];
var children = diagram._getChildren(parent.children);
var prevNode;
for (var m = 0; m < children.length; m++) {
if (children[m] !== node.name && children[m] !== parent.lineName) {
var child = diagram.nameTable[children[m]];
if (!prevNode || prevNode && prevNode.zOrder <= child.zOrder) {
prevNode = child;
}
}
}
if (prevNode) {
diagram._svg.getElementById(prevNode.name).parentNode.insertBefore(diagram._svg.getElementById(node.name), diagram._svg.getElementById(prevNode.name).nextSibling);
ej.Diagram.SvgContext.renderSelector(node, diagram._svg, diagram._adornerLayer, diagram._currZoom, diagram.model.selectedItems.constraints);
}
}, this);
},
findOverlapNodesWithChildren: function (node) {
var nodes = [];
if (node.parent) {
var diagram = this.getInstance();
var parent = diagram.nameTable[node.parent];
if (parent) {
nodes = this.getAllChildren(parent, nodes, node);
}
}
return nodes;
},
/**
* Returns an array of children, taking into account nesting
* @param {ej.Diagram.Node} node Diagram element.
* @param {Array} nodes Element's array.
* @param {Array} exceptNode An element excluded from the returned array.
* @returns {Array} collection Array of elements
*/
getAllChildren: function (node, nodes, exceptNode) {
var diagram = this.getInstance();
var children = diagram._getChildren(node.children);
for (var m = 0; m < children.length; m++) {
var child = diagram.nameTable[children[m]];
if (child) {
if (!exceptNode || child.name !== exceptNode.name && child.name !== node.lineName) {
nodes.push(child);
nodes = this.getAllChildren(child, nodes, exceptNode);
}
}
}
return nodes;
},
/**
* Overrides the creation of the group.
* @private
*/
customizeGroup: function () {
var base = ej.Diagram.Group;
ej.Diagram.Group = function (options) {
/*jslint bitwise: true */
if (options.type === "pseudoGroup") {
Ext.apply(options, {
constraints: ej.Diagram.NodeConstraints.Delete | ej.Diagram.NodeConstraints.Select | ej.Diagram.NodeConstraints.Drag,
borderColor: "gray"
});
}
/*jslint bitwise: false */
return base.call(this, options);
};
},
/**
* Overrides the standard behavior of the _getProcessedObject handler .
* @private
*/
customizeGetProcessedObject: function () {
ej.Diagram.ToolBase.prototype._getProcessedObject = function (name) {
var node = this.diagram.nameTable[name];
if (!node || this._isInSelection(name)) {
return null;
}
return {
add: node,
remove: null
};
};
},
/**
* Overrides the selection logic.
* @private
*/
customizeSelectMouseUp: function () {
var base = ej.Diagram.ToolBase;
var scope = this;
ej.Diagram.SelectTool.prototype.mouseup = function (evt) {
if (scope.readOnly) {
return;
}
if (this.inAction) {
this.currentPoint = this.mousePosition(evt);
var rect = ej.datavisualization.Diagram.Geometry.rect([this.startPoint, this.currentPoint]);
if (rect.width !== 0 || rect.height !== 0) {
var i;
var len;
var bounds;
var collection = [];
var childCollection = [];
var nodes = this.diagram.nodes();
for (i = 0, len = nodes.length; i < len; i++) {
var node = this.diagram.nameTable[nodes[i].name];
if (node) {
bounds = ej.datavisualization.Diagram.Util.bounds(node);
if (ej.datavisualization.Diagram.Util.canSelect(node)) {
if (ej.datavisualization.Diagram.Geometry.containsRect(rect, bounds)) {
if (scope.isParentNodeLane(node)) {
collection.push(node);
} else {
childCollection.push(node);
}
} else if (node.type === "group") {
this._checkGroupChildren(collection, node, rect);
}
}
}
}
if (collection.length > 1) {
var allChildren = [];
collection.forEach(function (node) {
scope.getAllChildren(node, allChildren);
}, this);
collection = Ext.Array.merge(collection, allChildren);
}
collection = Ext.Array.union(collection, childCollection);
if (this.diagram._hasSelection()) {
collection = this.diagram._getChildren(collection);
ej.datavisualization.Diagram.Util.removeItem(collection, "multipleSelection");
this.diagram._clearSelection();
}
if (collection.length > 0) {
var children = collection;
var selection;
if (children.length > 1) {
var pseudoGroup = ej.datavisualization.Diagram.Group({
type: "pseudoGroup",
"name": "multipleSelection"
});
for (i = 0, len = children.length; i < len; i++) {
pseudoGroup.children.push(this.diagram._getChild(children[i]));
}
this.diagram.nodes().push(pseudoGroup);
this.diagram.nameTable[pseudoGroup.name] = pseudoGroup;
selection = pseudoGroup;
} else {
if (ej.datavisualization.Diagram.Util.canDoSingleSelection(this.diagram)) {
selection = collection[0];
}
}
if (selection) {
if (selection.type === "group" || selection.type === "pseudoGroup") {
ej.datavisualization.Diagram.Util._updateGroupBounds(selection, this.diagram);
}
this.diagram._addSelection(selection);
}
}
} else {
if (this.diagram._hasSelection() && !this.diagram._isDropped) {
this.diagram._clearSelection();
}
}
} else {
if (evt.which !== 3) {
if (this.selectedObject || this.diagram._hasSelection()) {
if (evt.ctrlKey || evt.shiftKey) {
if (!ej.datavisualization.Diagram.Util.canDoMultipleSelection(this.diagram)) {
this._processCtrlKey(evt);
}
} else if (this.diagram._hasSelection()) {
this.diagram._clearSelection();
}
this.diagram._addSelection(this.selectedObject);
} else if (this.diagram._hasSelection() && !this.diagram._isDropped) {
this.diagram._clearSelection();
}
}
}
this.diagram._isDropped = false;
if (this.selectedObject) {
this._raiseEvent("click", { element: this.selectedObject });
}
base.prototype.mouseup.call(this, evt);
};
},
/**
* Overrides the selection logic for all elements.
* @private
*/
customizeSelectAll: function () {
var scope = this;
ej.Diagram.prototype.selectAll = function () {
var nodes,
i,
len,
item = null;
this._clearSelection();
var pseudoGroup = ej.datavisualization.Diagram.Group({
type: "pseudoGroup",
name: "multipleSelection"
});
nodes = this.nodes();
for (i = 0, len = nodes.length; i < len; i++) {
if (ej.datavisualization.Diagram.Util.canSelect(nodes[i])) {
item = this.nameTable[nodes[i].name];
if (item) {
pseudoGroup.children.push(item.name);
}
}
}
if (pseudoGroup.children.length > 1) {
var selectedList = pseudoGroup.children;
pseudoGroup.children = scope.getHierarchySelectedList(selectedList);
}
ej.datavisualization.Diagram.Util._updateGroupBounds(pseudoGroup, this);
this.nodes().push(pseudoGroup);
this.nameTable[pseudoGroup.name] = pseudoGroup;
if (pseudoGroup.children.length > 1) {
this._addSelection(pseudoGroup);
}
};
},
/**
* Checks whether the parent element is a track.
* @param {ej.Diagram.Node} node Element of the diagram.
* @return {boolean}
*/
isParentNodeLane: function (node) {
var diagram = this.getInstance();
var parent = diagram.nameTable[node.parent];
return parent && parent.nodeType === Terrasoft.diagram.UserHandlesConstraint.Lane;
},
/**
* Overrides the move logic of the element.
* @private
*/
customizeTranslate: function () {
var scope = this;
var base = ej.Diagram.prototype._translate;
ej.Diagram.prototype._translate = function (node, dx, dy, nameTable, isContainer, layout) {
var pseudoGroup = nameTable.multipleSelection;
if (pseudoGroup) {
var nodes = this._getChildren(pseudoGroup.children);
if (!scope.isParentNodeLane(node) && Ext.Array.contains(nodes, node.parent)) {
dx = dy = 0;
}
}
base.call(this, node, dx, dy, nameTable, isContainer, layout);
};
},
/**
* Overrides the selection logic with CTRL
* @private
*/
customizeProcessCtrlKey: function () {
var scope = this;
var base = ej.Diagram.ToolBase.prototype._processCtrlKey;
ej.Diagram.ToolBase.prototype._processCtrlKey = function () {
scope.removeSelectedConnectors();
var node = base.apply(this, arguments);
if (node && node.type === "node" && this.selectedObject && this.selectedObject.type === "pseudoGroup" && Ext.Array.contains(this.selectedObject.children, node.name) && node.children) {
var children = this.selectedObject.children;
this.selectedObject.children = scope.getHierarchySelectedList(children);
}
return node;
};
},
/**
* Removes connectors from an array of selected items.
*/
removeSelectedConnectors: function () {
var diagram = this.getInstance();
var selectionList = diagram.selectionList;
if (selectionList.length > 0 && selectionList[0].type === "pseudoGroup") {
selectionList[0].children = selectionList[0].children.filter(function (itemName) {
var item = diagram.nameTable[itemName];
return item.nodeType !== Terrasoft.diagram.UserHandlesConstraint.Connector;
}, this);
} else {
selectionList = selectionList.filter(function (item) {
return item.nodeType !== Terrasoft.diagram.UserHandlesConstraint.Connector;
});
}
diagram.selectionList = selectionList;
},
/**
* Returns an array of selected elements, taking into account the hierarchy.
* @param {Array} nodesName An array of element names.
* @returns {Array} An array of element names.
*/
getHierarchySelectedList: function (nodesName) {
var sortItems = [];
var selectedItems = [];
var diagram = this.getInstance();
nodesName.forEach(function (nodeName) {
var node = diagram.nameTable[nodeName];
var level = this.getLevelNode(node);
sortItems.push({
level: level,
node: node
});
}, this);
Ext.Array.sort(sortItems, function (first, second) {
return first.level - second.level;
});
sortItems.forEach(function (item) {
var node = item.node;
Ext.Array.include(selectedItems, node.name);
var allChildren = [];
this.getAllChildren(node, allChildren);
allChildren.forEach(function (node) {
Ext.Array.include(selectedItems, node.name);
}, this);
}, this);
return selectedItems;
},
/**
* Returns the nesting level of the element.
* @param {ej.Diagram.Node} node The element of the diagram.
* @param {int} level The nesting level of the element.
* @return {int}
*/
getLevelNode: function (node, level) {
if (!level) {
level = 0;
}
if (!node.parent) {
return level;
}
level++;
var diagram = this.getInstance();
var parentNode = diagram.nameTable[node.parent];
return this.getLevelNode(parentNode, level);
},
/**
* Overrides the MouseUp event handler for the displayed nodes.
*/
customizePhaseToolMouseUp: function () {
var base = ej.Diagram.PhaseTool.prototype.mouseup;
ej.Diagram.PhaseTool.prototype.mouseup = function () {
base.apply(this, arguments);
var diagram = this.diagram;
var shape = diagram.selectionList[0];
var isMultipleSelection = shape && shape.type === "pseudoGroup";
ej.Diagram.SvgContext.renderUserHandles(diagram.model.selectedItems.userHandles, shape, diagram._svg, isMultipleSelection, diagram._currZoom, diagram._adornerLayer);
};
},
/**
* @inheritdoc Terrasoft.Diagram#onConnectionChange
* @override
*/
onConnectionChange: function () {
// Synchronization of control flows occurs on onDiagramMouseUp
},
/**
* @inheritdoc Terrasoft.Diagram#linkConnectors
* @override
*/
linkConnectors: function (inEdgesConnectors, outEdgesConnectors) {
var connectors = (inEdgesConnectors || []).concat(outEdgesConnectors || []);
var eventArgs = [];
connectors.forEach(function (connectorName) {
var connector = this.getElementById(connectorName);
if (connector) {
eventArgs.push(connector);
}
}, this);
this.fireEvent("connectorsNodesChange", eventArgs);
},
/**
* Finds caption label of the Process element.
* @param {Object} node Process element node.
* @return {Terrasoft.Label}
*/
getNodeCaptionLabel: function (node) {
var result;
Terrasoft.each(node.labels, function (label) {
if (label.labelType === Terrasoft.LabelType.CAPTION) {
result = label;
return false;
}
}, this);
return result;
},
/**
* Sets process element title.
* @param {String} nodeName Process element name.
* @param {String} caption Process element caption.
* @param {String} captionPrefix Validation state caption.
* @return {boolean}
*/
setNodeCaption: function (nodeName, caption, captionPrefix) {
var node = this.getElementById(nodeName);
if (node) {
var label = this.getNodeCaptionLabel(node);
if (label) {
if (captionPrefix && label.text && label.text.indexOf(captionPrefix) === 0 && caption.indexOf(captionPrefix) < 0) {
label.text = captionPrefix + " " + caption;
} else {
label.text = caption;
}
label.isTextRendered = false;
var diagram = this.getInstance();
diagram.updateNode(node.name, {});
return true;
}
}
return false;
},
/**
* Sets node font color and caption.
* @param {String} nodeName Process element name.
* @param {String} caption Process element caption.
* @param {String} fontColor Process element caption text color.
*/
setNodeCaptionWithColor: function (nodeName, caption, fontColor) {
var node = this.getElementById(nodeName);
if (!node) {
return;
}
var label = this.getNodeCaptionLabel(node);
if (!label) {
return;
}
label.text = caption;
label.fontColor = fontColor;
label.isTextRendered = false;
var diagram = this.getInstance();
diagram.updateNode(node.name, {});
},
/**
* It overrides the standard behavior of the _checkToolToActivate handler .
*/
customizeCheckToolToActivate: function () {
var base = ej.Diagram.prototype._checkToolToActivate;
ej.Diagram.prototype._checkToolToActivate = function (evt) {
evt.altKey = false;
return base.apply(this, arguments);
};
},
/**
* Returns the cursor position relative to the diagram.
* @param {event} e
* @return {ej.Diagram.Point}
*/
getOffsetMousePosition: function (e) {
var diagramContainer = $("#" + this.renderToSelector);
var diagramPos = diagramContainer.position();
var scrollLeft = diagramContainer.scrollLeft();
var scrollTop = diagramContainer.scrollTop();
return ej.Diagram.Point(e.clientX + scrollLeft - diagramPos.left, e.clientY + scrollTop - diagramPos.top);
},
/**
* @inheritdoc Terrasoft.Diagram#getNodeDataItemMarker
* @override
*/
getNodeDataItemMarker: function (node) {
return node.name;
},
/**
* @inheritdoc Terrasoft.Diagram#onClick
* Prevents clicking on the diagram when move the element.
* @override
*/
onClick: function (event) {
var element = event.element || {};
var isDiagramClick = Ext.isEmpty(element.name);
var diagram = this.getInstance();
if (isDiagramClick && diagram.activeTool.name === "move") {
return;
}
this.fireEvent("click", event);
},
/**
* Customizes paste method.
* @private
*/
customizePaste: function () {
var self = this;
ej.Diagram.prototype.paste = function () {
if (this._clipboardData) {
var data = this._clipboardData;
var node = data.node;
self.fireEvent("nodesCollectionChange", {
changeType: "paste",
element: node,
localX: node.offsetX,
localY: node.offsetY
});
}
};
},
/**
* Customizes copy method.
* @private
*/
customizeCopy: function () {
var originalCopy = ej.Diagram.prototype.copy;
var self = this;
ej.Diagram.prototype.copy = function () {
var elements = self.items;
var diagram = this;
if (!Ext.isEmpty(diagram.selectionList)) {
var elementName = diagram.selectionList[0].name;
if (elementName === "multipleSelection") {
return;
}
var element = elements.get(elementName);
var hasLazyParameters = element.hasLazyParameters;
var hasAllLazyParametersLoaded = element.hasLazyParameters && element.getAllLazyParametersAreLoaded();
if (!hasLazyParameters || hasAllLazyParametersLoaded) {
originalCopy.call(this);
}
}
};
},
/**
* Customizes node bounds extension, that changes length of the first segment of the connector near source
* and target nodes.
* @protected
*/
customizeNodeBoundsExtension: function () {
var extension = this.nodeBoundsExtension;
ej.Diagram.Util.getNodeBoundsExtension = function () {
return extension;
};
},
/**
* Copies selected element to clipboard.
* @protected
*/
copyElement: function () {
var diagram = this.getInstance();
diagram.copy();
},
/**
* Pastes element from clipboard.
* @protected
*/
pasteElement: function () {
var diagram = this.getInstance();
diagram.paste();
},
/**
* @inheritdoc Terrasoft.NodeRemoveTool#nodeRemoveTool
* @override
*/
nodeRemoveTool: function (base) {
function tool(diagram) {
base.call(this, diagram);
}
ej.Diagram.extend(tool, base);
var self = this;
tool.prototype.mouseup = function () {
self.removeSelectedItems();
this.diagram.deactivateTool();
};
return tool;
},
/**
* Removes selected items.
*/
removeSelectedItems: function () {
var diagram = this.getInstance();
var selectedItem = diagram.selectionList[0];
if (selectedItem) {
var selectedItems = selectedItem.type === "pseudoGroup" ? selectedItem.children : [selectedItem.name];
this.fireEvent("itemsRemoving", {
itemNameList: selectedItems,
diagram: this
});
}
},
/**
* Clears items selection.
*/
clearSelection: function () {
var diagram = this.getInstance();
diagram._clearSelection();
},
/**
* Finds object on the diagram by name.
* @param {String} itemName
* @return {ej.Diagram.Node}
*/
findObjectByName: function (itemName) {
var diagram = this.getInstance();
var node = diagram._findObjectByName(itemName);
return node;
},
/**
* Adds selection of node on diagram.
* @param {ej.Diagram.Node} node Diagram node object.
*/
addSelection: function (node) {
var diagram = this.getInstance();
diagram._addSelection(node);
},
/**
* Update scroll offset of node.
* @param {ej.Diagram.Node} node Diagram node object.
*/
scrollToItem: function (node) {
var containerEl = Ext.get(this.renderToSelector);
var itemBounds = this.getItemBounds(node);
this.updateContainerScrollLeft(itemBounds, containerEl);
this.updateContainerScrollTop(itemBounds, containerEl);
},
/**
* Updates container scroll left by item bounds.
* @private
* @param {Object} itemBounds Item bounds.
* @param {Object} containerEl Container element.
*/
updateContainerScrollLeft: function (itemBounds, containerEl) {
var container = containerEl.dom;
var containerRect = containerEl.getBox();
var padding = 10;
var left = itemBounds.x - padding;
var right = itemBounds.x + itemBounds.width + padding;
var offsetX = right - containerRect.width;
if (left < container.scrollLeft || left < offsetX) {
container.scrollLeft = left;
} else if (offsetX - container.scrollLeft > 0) {
container.scrollLeft = offsetX;
}
},
/**
* Updates container scroll top by item bounds.
* @private
* @param {Object} itemBounds Item bounds.
* @param {Object} containerEl Container element.
*/
updateContainerScrollTop: function (itemBounds, containerEl) {
var container = containerEl.dom;
var containerRect = containerEl.getBox();
var padding = 10;
var top = itemBounds.y - padding;
var bottom = itemBounds.y + itemBounds.height + padding + this.bottomScrollContentOffset;
var offsetY = bottom - containerRect.height;
if (top < container.scrollTop || top < offsetY) {
container.scrollTop = top;
} else if (offsetY - container.scrollTop > 0) {
container.scrollTop = offsetY;
}
},
/**
* Selects item by name.
* @param {String} itemName Item name.
*/
selectItem: function (itemName) {
this.clearSelection();
var node = this.findObjectByName(itemName);
if (node) {
this.addSelection(node);
this.scrollToItem(node);
}
},
/**
* Updates items highlighter.
* @param {Terrasoft.core.collections.Collection} highlightItems Items for highlight.
* @param {String} selectedItemName The name of selected item.
*/
updateItemsHighlighter: function (highlightItems, selectedItemName) {
var diagram = this.getInstance();
var svg = diagram._svg;
var container = svg.getElementById("node-highlighter_container");
if (container) {
svg.removeChild(container, diagram._adornerLayer);
}
if (highlightItems && !highlightItems.isEmpty()) {
container = svg.g({
"id": "node-highlighter_container"
});
diagram._adornerLayer.appendChild(container);
this.items.each(function (item) {
if (highlightItems.contains(item.name)) {
var node = this.findObjectByName(item.name);
var element;
if (item instanceof Terrasoft.ProcessSequenceFlowSchema) {
element = this.drawSequenceFlowHighlighter(node, container);
} else {
element = this.drawElementHighlighter(node, container);
}
if (item.name === selectedItemName) {
element.classList.add("node-selected");
}
}
}, this);
}
},
/**
* Draws highlighter for sequence flow.
* @private
* @param {ej.Diagram.Node} node Target node.
* @param {Object} container Container for new svg shape.
* @return Created dom element.
*/
drawSequenceFlowHighlighter: function (node, container) {
var pathData = [];
for (var i = 0; i < node.segments.length; i++) {
var points = node.segments[i].points;
for (var j = 0; j < points.length; j++) {
var pathCommand = j === 0 ? "M" : "L";
pathData.push(pathCommand, points[j].x, points[j].y);
}
}
var path = pathData.join(" ");
var attr = {
"id": "nodeHighlighter_" + node.name,
"class": "node-highlighter flow-highlighter",
"d": path
};
var diagram = this.getInstance();
var element = diagram._svg.path(attr);
container.appendChild(element);
return element;
},
/**
* Draws highlighter for diagram element.
* @private
* @param {ej.Diagram.Node} node Target node.
* @param {Object} container Container for new svg shape.
* @return Created dom element.
*/
drawElementHighlighter: function (node, container) {
var element;
switch (node.nodeType) {
case Terrasoft.diagram.UserHandlesConstraint.Event:
element = this.drawEventHighlighter(node, container);
break;
case Terrasoft.diagram.UserHandlesConstraint.Gateway:
var gatewayContainer = this.drawGatewayHighlighterContainer(node, container);
element = this.drawGatewayHighlighter(node, gatewayContainer);
break;
default:
element = this.drawFlowElementHighlighter(node, container);
}
return element;
},
/**
* Returns default attributes for highlighter svg element.
* @private
* @param {ej.Diagram.Node} node Target node.
* @return {Object}
*/
getDrawHighlighterDefaultAttributes: function (node) {
var attr = {
"id": "nodeHighlighter_" + node.name,
"class": "node-highlighter",
"pointer-events": "none",
"style": "pointer-events: none",
"fill": "none"
};
return attr;
},
/**
* Returns diagram svg object.
* @private
* @return {ej.Diagram.Svg}
*/
getDiagramSvg: function () {
var diagram = this.getInstance();
var svg = diagram._svg;
return svg;
},
/**
* Draws highlighter for event element.
* @private
* @param {ej.Diagram.Node} node Target node.
* @param {Object} container Container for new svg shape.
* @return Created dom element.
*/
drawEventHighlighter: function (node, container) {
var bounds = ej.Diagram.Util.bounds(node);
var attr = this.getDrawHighlighterDefaultAttributes(node);
Ext.merge(attr, {
"cx": bounds.center.x,
"cy": bounds.center.y,
"r": (bounds.width + 7) / 2
});
var svg = this.getDiagramSvg();
var element = svg.circle(attr);
container.appendChild(element);
return element;
},
/**
* Draws highlighter container for gateway element.
* @private
* @param {ej.Diagram.Node} node Target node.
* @param {Object} container Container for new svg shape.
* @return Created dom element.
*/
drawGatewayHighlighterContainer: function (node, container) {
var bounds = ej.Diagram.Util.bounds(node);
var translate = {
x: bounds.center.x,
y: bounds.center.y - node.height / 2 - 5
};
var attr = {
id: "nodeHighlighter_rotate_g_" + node.name,
transform: Ext.String.format("translate({0} {1}) rotate(45)", translate.x, translate.y)
};
var svg = this.getDiagramSvg();
var element = svg.g(attr);
container.appendChild(element);
return element;
},
/**
* Draws highlighter for gateway element.
* @private
* @param {ej.Diagram.Node} node Target node.
* @param {Object} container Container for new svg shape.
* @return Created dom element.
*/
drawGatewayHighlighter: function (node, container) {
var size = Math.sqrt(2 * Math.pow(node.height / 2, 2)) + 7;
var attr = this.getDrawHighlighterDefaultAttributes(node);
Ext.merge(attr, {
"height": size,
"width": size
});
var svg = this.getDiagramSvg();
var element = svg.rect(attr);
container.appendChild(element);
return element;
},
/**
* Draws highlighter for flow element.
* @private
* @param {ej.Diagram.Node} node Target node.
* @param {Object} container Container for new svg shape.
* @return Created dom element.
*/
drawFlowElementHighlighter: function (node, container) {
var width = node.width + 7;
var height = node.height + 7;
var bounds = ej.Diagram.Util.bounds(node);
var attr = this.getDrawHighlighterDefaultAttributes(node);
Ext.merge(attr, {
"x": bounds.center.x - width / 2,
"y": bounds.center.y - height / 2,
"width": width,
"height": height,
"rx": 13,
"ry": 13
});
var svg = this.getDiagramSvg();
var element = svg.rect(attr);
container.appendChild(element);
return element;
}
});