diff --git a/packages/joint-core/src/dia/Graph.mjs b/packages/joint-core/src/dia/Graph.mjs index a027fbd3c..4ae3244e4 100644 --- a/packages/joint-core/src/dia/Graph.mjs +++ b/packages/joint-core/src/dia/Graph.mjs @@ -396,6 +396,41 @@ export const Graph = Model.extend({ this.get('cells').remove(cell, { silent: true }); }, + transferCellHierarchy: function(sourceCell, targetCell) { + + // Embed children of the source cell in the target cell. + const children = sourceCell.getEmbeddedCells(); + if (children.length > 0) { + sourceCell.unembed(children); + targetCell.embed(children); + } + + // Embed the target cell in the parent of the source cell, if any. + const parent = sourceCell.getParentCell(); + if (parent) { + parent.unembed(sourceCell); + parent.embed(targetCell); + } + + return this; + }, + + transferCellLinks: function(sourceCell, targetCell) { + + // Reconnect all the links connected to the old cell to the new cell. + const connectedLinks = this.getConnectedLinks(sourceCell); + connectedLinks.forEach((link) => { + + if (link.getSourceCell() === sourceCell) { + link.prop(['source', 'id'], targetCell.id); + } else { + link.prop(['target', 'id'], targetCell.id); + } + }); + + return this; + }, + // Get a cell by `id`. getCell: function(id) { diff --git a/packages/joint-core/test/jointjs/graph.js b/packages/joint-core/test/jointjs/graph.js index 6a7475fa0..a905ac51f 100644 --- a/packages/joint-core/test/jointjs/graph.js +++ b/packages/joint-core/test/jointjs/graph.js @@ -1546,4 +1546,128 @@ QUnit.module('graph', function(hooks) { assert.notOk(graph.hasActiveBatch()); }); }); + + QUnit.module('graph.transferCellHierarchy()', function() { + + QUnit.test('should transfer hierarchy of elements', function(assert) { + + const topLevelElement = new joint.shapes.standard.Rectangle(); + const originalElement = new joint.shapes.standard.Rectangle(); + const child = new joint.shapes.standard.Rectangle(); + const replacementElement = new joint.shapes.standard.Rectangle(); + + topLevelElement.embed(originalElement); + originalElement.embed(child); + + this.graph.addCells([topLevelElement, originalElement, child, replacementElement]); + this.graph.transferCellHierarchy(originalElement, replacementElement); + + assert.equal(replacementElement.getParentCell(), topLevelElement); + assert.equal(replacementElement.getEmbeddedCells()[0], child); + }); + + QUnit.test('should transfer hierarchy of links', function(assert) { + + const parent = new joint.shapes.standard.Rectangle(); + const originalLink = new joint.shapes.standard.Link(); + const replacementLink = new joint.shapes.standard.Link(); + + parent.embed(originalLink); + + this.graph.addCells([parent, originalLink, replacementLink]); + this.graph.transferCellHierarchy(originalLink, replacementLink); + + assert.equal(replacementLink.getParentCell(), parent); + }); + + QUnit.test('should work when transferring hierarchy from a link to an element', function(assert) { + + const parent = new joint.shapes.standard.Rectangle(); + const link = new joint.shapes.standard.Link(); + const element = new joint.shapes.standard.Rectangle(); + + parent.embed(link); + + this.graph.addCells([parent, link, element]); + this.graph.transferCellHierarchy(link, element); + + assert.equal(element.getParentCell(), parent); + }); + + QUnit.test('should work when transferring hierarchy from an element to a link', function(assert) { + + const parent = new joint.shapes.standard.Rectangle(); + const link = new joint.shapes.standard.Link(); + const child = new joint.shapes.standard.Rectangle(); + const element = new joint.shapes.standard.Rectangle(); + + element.embed(child); + parent.embed(element); + + this.graph.addCells([parent, link, child, element]); + this.graph.transferCellHierarchy(element, link); + + assert.equal(link.getParentCell(), parent); + assert.equal(link.getEmbeddedCells()[0], child); + }); + }); + + QUnit.module('graph.transferCellLinks()', function() { + + QUnit.test('should transfer links of an element', function(assert) { + + const originalElement = new joint.shapes.standard.Rectangle(); + const link1 = new joint.shapes.standard.Link({ source: { id: originalElement.id }}); + const link2 = new joint.shapes.standard.Link({ target: { id: originalElement.id }}); + const replacementElement = new joint.shapes.standard.Rectangle(); + + this.graph.addCells([originalElement, link1, link2]); + this.graph.transferCellLinks(originalElement, replacementElement); + + assert.equal(link1.source().id, replacementElement.id); + assert.equal(link2.target().id, replacementElement.id); + }); + + QUnit.test('should transfer links of a link', function(assert) { + + const originalLink = new joint.shapes.standard.Link(); + const link1 = new joint.shapes.standard.Link({ source: { id: originalLink.id }}); + const link2 = new joint.shapes.standard.Link({ target: { id: originalLink.id }}); + const replacementLink = new joint.shapes.standard.Link(); + + this.graph.addCells([originalLink, link1, link2]); + this.graph.transferCellLinks(originalLink, replacementLink); + + assert.equal(link1.source().id, replacementLink.id); + assert.equal(link2.target().id, replacementLink.id); + }); + + QUnit.test('should work when transferring links from a link to an element', function(assert) { + + const originalLink = new joint.shapes.standard.Link(); + const link1 = new joint.shapes.standard.Link({ source: { id: originalLink.id }}); + const link2 = new joint.shapes.standard.Link({ target: { id: originalLink.id }}); + const element = new joint.shapes.standard.Rectangle(); + + this.graph.addCells([originalLink, link1, link2, element]); + this.graph.transferCellLinks(originalLink, element); + + assert.equal(link1.source().id, element.id); + assert.equal(link2.target().id, element.id); + }); + + QUnit.test('should work when transferring links from an element to a link', function(assert) { + + const originalElement = new joint.shapes.standard.Rectangle(); + const link1 = new joint.shapes.standard.Link({ source: { id: originalElement.id }}); + const link2 = new joint.shapes.standard.Link({ target: { id: originalElement.id }}); + const replacementLink = new joint.shapes.standard.Link(); + + this.graph.addCells([originalElement, link1, link2, replacementLink]); + this.graph.transferCellLinks(originalElement, replacementLink); + + assert.equal(link1.source().id, replacementLink.id); + assert.equal(link2.target().id, replacementLink.id); + }); + }); }); diff --git a/packages/joint-core/types/joint.d.ts b/packages/joint-core/types/joint.d.ts index 64acef7f1..812201b7c 100644 --- a/packages/joint-core/types/joint.d.ts +++ b/packages/joint-core/types/joint.d.ts @@ -263,6 +263,10 @@ export namespace dia { removeCells(cells: Cell[], opt?: Cell.DisconnectableOptions): this; + transferCellHierarchy(sourceCell: Cell, targetCell: Cell): this; + + transferCellLinks(sourceCell: Cell, targetCell: Cell): this; + resize(width: number, height: number, opt?: S): this; resizeCells(width: number, height: number, cells: Cell[], opt?: S): this;