children) {
* datasoure representing a node) and returns an HTML snippet. The name of this parameter is given
* by {@code parameterName}.
*
- * Example:
+ *
+ * Example:
* setNodeTemplate("item","return ''+item.name+'';")
* configures the following JS function as node template:
* function(item) { return ''+item.name+''; }
@@ -330,4 +334,409 @@ public OrgChart getOrgChart() {
public void setCollapsedNodes() {
setChartDepth(1);
}
+
+ /**
+ * Finds the parent of a node with the given ID in the org chart.
+ * This method is used to find the parent of a node after the chart has been initialized.
+ *
+ * @param childId the ID of the child node whose parent is to be found
+ * @param root the root of the org chart from which to start searching
+ * @return the parent {@link OrgChartItem} of the node with the given ID, or {@code null} if not
+ * found
+ */
+ private OrgChartItem findParent(Integer childId, OrgChartItem root) {
+ if (root.getChildren() != null) {
+ for (OrgChartItem child : root.getChildren()) {
+ if (childId.equals(child.getId())) {
+ return root;
+ }
+ OrgChartItem parent = findParent(childId, child);
+ if (parent != null) {
+ return parent;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Converts a JsonArray of IDs to a List of Integers.
+ *
+ * @param jsonIds array of numeric IDs
+ * @return list of converted integer IDs
+ */
+ private List convertJsonArrayToIntegerList(JsonArray jsonIds) {
+ List idList = new ArrayList<>();
+ for (int i = 0; i < jsonIds.length(); i++) {
+ idList.add((int) jsonIds.getNumber(i));
+ }
+ return idList;
+ }
+
+ /**
+ * Appends a list of items to a parent node's children list.
+ *
+ * @param parentNode the node to which children will be added
+ * @param itemsToAdd the list of items to add
+ */
+ private void appendItemsToParent(OrgChartItem parentNode, List itemsToAdd) {
+ if (parentNode != null) {
+ List currentChildren = parentNode.getChildren();
+ if (currentChildren == null) {
+ currentChildren = new ArrayList<>();
+ }
+ currentChildren.addAll(itemsToAdd);
+ parentNode.setChildren(currentChildren);
+ }
+ }
+
+ /**
+ * Adds one or more sibling nodes to a target node in the chart.
+ *
+ * This method inserts the new items at the same level as the target node, under the same parent.
+ * It updates both the internal data structure and the client-side visual representation.
+ *
+ * @param nodeId the ID of the existing node that will serve as the anchor for adding siblings.
+ * This must not be the root node's ID.
+ * @param siblings a list of {@link OrgChartItem} objects to be added as siblings
+ * @throws IllegalArgumentException if the {@code nodeId} belongs to the root node of the chart,
+ * as the root cannot have siblings or if the {@code nodeId} is not found in the chart
+ */
+ public void addSiblings(Integer nodeId, List siblings) {
+ // First check if selected node is not the root node
+ if (nodeId.equals(this.orgChartItem.getId())) {
+ throw new IllegalArgumentException("Cannot add siblings to the root node.");
+ }
+ // Update the internal data structure
+ OrgChartItem targetNode = getById(nodeId, orgChartItem);
+ if (targetNode != null) {
+ // Find parent of the target node
+ OrgChartItem parentNode = findParent(nodeId, orgChartItem);
+ if (parentNode != null) {
+ // Update parent's children list with the new siblings
+ appendItemsToParent(parentNode, siblings);
+ }
+ } else {
+ throw new IllegalArgumentException("Node not found: " + nodeId);
+ }
+
+ // Update the visual representation by calling the client-side method addSiblings
+ String siblingsJson = convertToJsonObj(siblings);
+ this.getElement().executeJs("this.addSiblings($0, $1)", nodeId, siblingsJson);
+ }
+
+ /**
+ * Handles sibling addition events from the client side. Converts the received JsonArray of
+ * sibling IDs to a List and fires a {@link SiblingsAddedEvent}.
+ *
+ * @param nodeId the ID of the node that received new siblings
+ * @param siblingIds array of IDs for the newly added siblings
+ */
+ @ClientCallable
+ private void onSiblingsAdded(String nodeId, JsonArray siblingIds) {
+ // Find the node where siblings were added
+ OrgChartItem targetItem = getById(Integer.valueOf(nodeId), orgChartItem);
+ if (targetItem != null) {
+
+ // Convert the JsonArray to a simple list of integer IDs
+ List newSiblingIdList = convertJsonArrayToIntegerList(siblingIds);
+
+ // Convert the list of IDs into a list of the actual OrgChartItem objects
+ List newSiblingItems =
+ newSiblingIdList.stream().map(id -> getById(id, orgChartItem)).filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ // Fire the event with the parent and the fully populated list of child items
+ fireSiblingsAddedEvent(targetItem, newSiblingItems, true);
+ }
+ }
+
+ /**
+ * Adds a listener for sibling addition events. The listener will be notified when new siblings
+ * are added to any node in the chart.
+ *
+ * @param listener the listener to be added
+ * @return a {@link Registration} for removing the listener
+ */
+ public Registration addSiblingsAddedListener(ComponentEventListener listener) {
+ return addListener(SiblingsAddedEvent.class, listener);
+ }
+
+ /**
+ * Fires a siblings added event.
+ *
+ * @param item the node that received new siblings
+ * @param newSibling list of the newly added siblings
+ * @param fromClient whether the event originated from the client side
+ */
+ protected void fireSiblingsAddedEvent(OrgChartItem item, List newSiblings,
+ boolean fromClient) {
+ fireEvent(new SiblingsAddedEvent(this, item, newSiblings, fromClient));
+ }
+
+ /**
+ * Adds one or more child nodes to a specified parent node in the chart.
+ *
+ * This method updates both the internal data model and the client-side visuals. Note the specific
+ * client-side behavior: if the parent node has no existing children, this uses the library's
+ * {@code addChildren} function. If the parent already has children, it uses the
+ * {@code addSiblings} function on the first existing child to append the new nodes.
+ *
+ * @param nodeId the ID of the parent node to which the new children will be added
+ * @param children a list of {@link OrgChartItem} objects to be added as new children
+ * @throws IllegalArgumentException if the {@code nodeId} is not found in the chart
+ */
+ public void addChildren(Integer nodeId, List children) {
+ // Update the internal data structure
+ OrgChartItem targetNode = getById(nodeId, orgChartItem);
+ if (targetNode == null) {
+ throw new IllegalArgumentException("Node not found: " + nodeId);
+ }
+ boolean currentChildrenEmpty =
+ targetNode.getChildren() == null || targetNode.getChildren().isEmpty();
+ // Add new children while preserving existing ones
+ appendItemsToParent(targetNode, children);
+
+ // Update the visual representation
+ String itemsJson = convertToJsonObj(children);
+ if (currentChildrenEmpty) {
+ this.getElement().executeJs("this.addChildren($0, $1)", nodeId, itemsJson);
+ } else {
+ this.getElement().executeJs("this.addSiblings($0, $1)",
+ targetNode.getChildren().get(0).getId(), itemsJson);
+ }
+ }
+
+ @ClientCallable
+ private void onChildrenAdded(String nodeId, JsonArray childIds) {
+ // Find the parent node where children were added
+ OrgChartItem parentItem = getById(Integer.valueOf(nodeId), orgChartItem);
+ if (parentItem != null) {
+
+ // Convert the JsonArray to a simple list of integer IDs
+ List newChildIdList = convertJsonArrayToIntegerList(childIds);
+
+ // Convert the list of IDs into a list of the actual OrgChartItem objects
+ List newChildItems =
+ newChildIdList.stream().map(id -> getById(id, orgChartItem)).filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ // Fire the event with the parent and the fully populated list of child items
+ fireChildrenAddedEvent(parentItem, newChildItems, true);
+ }
+ }
+
+ /**
+ * Adds a listener for child addition events. The listener will be notified when new children are
+ * added to any node in the chart.
+ *
+ * @param listener the listener to be added
+ * @return a {@link Registration} for removing the listener
+ */
+ public Registration addChildrenAddedListener(
+ ComponentEventListener listener) {
+ return addListener(ChildrenAddedEvent.class, listener);
+ }
+
+ /**
+ * Fires a children added event.
+ *
+ * @param item the node that received new children
+ * @param newChildren list of the newly added children
+ * @param fromClient whether the event originated from the client side
+ */
+ protected void fireChildrenAddedEvent(OrgChartItem item, List newChildren,
+ boolean fromClient) {
+ fireEvent(new ChildrenAddedEvent(this, item, newChildren, fromClient));
+ }
+
+ /**
+ * Removes a specified node and all of its descendants from the chart.
+ *
+ * This action updates both the server-side data model and the client-side visualization. If the
+ * root node is removed, the chart will likely become empty. This operation is permanent for the
+ * current state of the chart.
+ *
+ * @param nodeId the ID of the node to remove. All children and subsequent descendants of this
+ * node will also be removed from the chart.
+ * @throws IllegalArgumentException if the {@code nodeId} is not found in the chart
+ */
+ public void removeNodes(Integer nodeId) {
+ // Find the node set for removal
+ OrgChartItem nodeToRemove = getById(nodeId, orgChartItem);
+ if (nodeToRemove != null) {
+ // Clear the removed node's children
+ nodeToRemove.setChildren(Collections.emptyList());
+ // Find parent and remove node from its children
+ OrgChartItem parentNode = findParent(nodeId, orgChartItem);
+ if (parentNode != null) {
+ List currentChildren = parentNode.getChildren();
+ currentChildren.removeIf(child -> nodeId.equals(child.getId()));
+ parentNode.setChildren(currentChildren);
+ }
+
+ // If removing the root, clear internal root reference
+ if (this.orgChartItem != null && nodeId.equals(this.orgChartItem.getId())) {
+ this.orgChartItem = null;
+ }
+
+ // Update the visual representation
+ this.getElement().executeJs("this.removeNodes($0)", nodeId);
+ } else {
+ throw new IllegalArgumentException("Node not found: " + nodeId);
+ }
+ }
+
+ @ClientCallable
+ private void onNodesRemoved(String nodeId) {
+ fireNodesRemovedEvent(Integer.valueOf(nodeId), true);
+ }
+
+ /**
+ * Adds a listener for node removal events. The listener will be notified when a node and its
+ * descendants are removed from the chart.
+ *
+ * @param listener the listener to be added
+ * @return a {@link Registration} for removing the listener
+ */
+ public Registration addNodesRemovedListener(ComponentEventListener listener) {
+ return addListener(NodesRemovedEvent.class, listener);
+ }
+
+ /**
+ * Fires a nodes removed event.
+ *
+ * @param nodeId the ID of the removed node
+ * @param fromClient whether the event originated from the client side
+ */
+ protected void fireNodesRemovedEvent(Integer nodeId, boolean fromClient) {
+ fireEvent(new NodesRemovedEvent(this, nodeId, fromClient));
+ }
+
+ /**
+ * Adds a new parent node to the organization chart. This method:
+ *
+ * - Updates the visual representation of the chart
+ * - Maintains the internal data structure by updating the root item
+ *
+ *
+ * @param newParentItem the new root item of the chart
+ */
+ public void addParent(OrgChartItem newParentItem) {
+ // Update the internal data structure
+ if (this.orgChartItem != null) {
+ // Set the old root as the only child of the new parent node
+ newParentItem.setChildren(Collections.singletonList(this.orgChartItem));
+ }
+ // Update the chart's root to point to the new parent
+ this.orgChartItem = newParentItem;
+
+ // Update the visual representation by calling the client-side method addParent
+ String parentJson = convertToJsonObj(newParentItem);
+ this.getElement().executeJs("this.addParent($0)", parentJson);
+ }
+
+ /**
+ * Handles parent addition events from the client side. The client sends the ID of the new
+ * parent/root node.
+ *
+ * @param newParentId the ID of the newly added parent node
+ */
+ @ClientCallable
+ private void onParentAdded(String newParentId) {
+ OrgChartItem newParentItem = getById(Integer.valueOf(newParentId), orgChartItem);
+ if (newParentItem != null) {
+ fireParentAddedEvent(newParentItem, true);
+ }
+ }
+
+ /**
+ * Adds a listener for parent addition event. The listener will be notified when a new parent
+ * (root) is added to the chart.
+ *
+ * @param listener the listener to be added
+ * @return a {@link Registration} for removing the listener
+ */
+ public Registration addParentAddedListener(ComponentEventListener listener) {
+ return addListener(ParentAddedEvent.class, listener);
+ }
+
+ /**
+ * Fires a parent added event.
+ *
+ * @param newParent the node that was added as the new parent/root
+ * @param fromClient whether the event originated from the client side
+ */
+ protected void fireParentAddedEvent(OrgChartItem newParent, boolean fromClient) {
+ fireEvent(new ParentAddedEvent(this, newParent, fromClient));
+ }
+
+ /**
+ * Updates a node in the chart with new data.
+ *
+ * This method updates the server-side data model and then calls the client-side function to
+ * visually redraw the node with the new information.
+ *
+ * @param nodeId the ID of the node to update
+ * @param newDataItem an {@link OrgChartItem} containing the new data to be merged. The ID of this
+ * item is ignored; only its other properties (name, title, custom data, etc) are used for
+ * the update.
+ * @throws IllegalArgumentException if the {@code nodeId} is not found in the chart
+ */
+ public void updateNode(Integer nodeId, OrgChartItem newDataItem) {
+ OrgChartItem nodeToUpdate = getById(nodeId, this.orgChartItem);
+ if (nodeToUpdate != null) {
+ // Update the server-side object
+ if (newDataItem.getName() != null) {
+ nodeToUpdate.setName(newDataItem.getName());
+ }
+ if (newDataItem.getTitle() != null) {
+ nodeToUpdate.setTitle(newDataItem.getTitle());
+ }
+ if (newDataItem.getClassName() != null) {
+ nodeToUpdate.setClassName(newDataItem.getClassName());
+ }
+ if (nodeToUpdate.isHybrid() != newDataItem.isHybrid()) {
+ nodeToUpdate.setHybrid(newDataItem.isHybrid());
+ }
+ if (newDataItem.getData() != null) {
+ newDataItem.getData().forEach(nodeToUpdate::setData);
+ }
+
+ // Call the client-side JS function to update the visual representation
+ String newDataJson = convertToJsonObj(newDataItem);
+ this.getElement().executeJs("this.updateNode($0, $1)", nodeId, newDataJson);
+ } else {
+ throw new IllegalArgumentException("Node not found: " + nodeId);
+ }
+ }
+
+ @ClientCallable
+ private void onNodeUpdated(String nodeId) {
+ OrgChartItem updatedItem = getById(Integer.valueOf(nodeId), orgChartItem);
+ if (updatedItem != null) {
+ fireNodeUpdatedEvent(updatedItem, true);
+ }
+ }
+
+ /**
+ * Adds a listener for node updated event.
+ *
+ * @param listener the listener to be added
+ * @return a {@link Registration} for removing the listener
+ */
+ public Registration addNodeUpdatedListener(ComponentEventListener listener) {
+ return addListener(NodeUpdatedEvent.class, listener);
+ }
+
+ /**
+ * Fires a node updated event.
+ *
+ * @param item the updated node
+ * @param fromClient whether the event originated from the client side
+ */
+ protected void fireNodeUpdatedEvent(OrgChartItem item, boolean fromClient) {
+ fireEvent(new NodeUpdatedEvent(this, item, fromClient));
+ }
+
}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/ChildrenAddedEvent.java b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/ChildrenAddedEvent.java
new file mode 100644
index 0000000..8c7db8f
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/ChildrenAddedEvent.java
@@ -0,0 +1,69 @@
+/*-
+ * #%L
+ * OrgChart Add-on
+ * %%
+ * Copyright (C) 2017 - 2025 Flowing Code S.A.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.orgchart.event;
+
+import com.flowingcode.vaadin.addons.orgchart.OrgChart;
+import com.flowingcode.vaadin.addons.orgchart.OrgChartItem;
+import com.vaadin.flow.component.ComponentEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Event fired when children are added to a node in the organization chart. Contains information
+ * about both the parent node and the newly added children.
+ */
+@SuppressWarnings("serial")
+public class ChildrenAddedEvent extends ComponentEvent {
+ private final OrgChartItem item;
+ private final List newChildren;
+
+ /**
+ * Creates a new children added event.
+ *
+ * @param source the chart component that fired the event
+ * @param item the node that received new children
+ * @param newChildren list of the newly added children
+ * @param fromClient whether the event originated from the client side
+ */
+ public ChildrenAddedEvent(OrgChart source, OrgChartItem item, List newChildren,
+ boolean fromClient) {
+ super(source, fromClient);
+ this.item = item;
+ this.newChildren = new ArrayList<>(newChildren);
+ }
+
+ /**
+ * Gets the node that received new children.
+ *
+ * @return the node
+ */
+ public OrgChartItem getItem() {
+ return item;
+ }
+
+ /**
+ * Gets the list of the newly added children.
+ *
+ * @return the list of new children
+ */
+ public List getNewChildren() {
+ return newChildren;
+ }
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/NodeUpdatedEvent.java b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/NodeUpdatedEvent.java
new file mode 100644
index 0000000..5f2982f
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/NodeUpdatedEvent.java
@@ -0,0 +1,36 @@
+package com.flowingcode.vaadin.addons.orgchart.event;
+
+import com.flowingcode.vaadin.addons.orgchart.OrgChart;
+import com.flowingcode.vaadin.addons.orgchart.OrgChartItem;
+import com.vaadin.flow.component.ComponentEvent;
+
+
+/**
+ * Event thrown when a node is updated.
+ */
+@SuppressWarnings("serial")
+public class NodeUpdatedEvent extends ComponentEvent {
+ private final OrgChartItem updatedItem;
+
+ /**
+ * Creates a node updated event.
+ *
+ * @param source the chart component that fired the event
+ * @param updatedItem the node being updated
+ * @param fromClient whether the event originated from the client side
+ */
+ public NodeUpdatedEvent(OrgChart source, OrgChartItem updatedItem, boolean fromClient) {
+ super(source, fromClient);
+ this.updatedItem = updatedItem;
+ }
+
+ /**
+ * Gets the updated node.
+ *
+ * @return the updated node item
+ */
+ public OrgChartItem getUpdatedItem() {
+ return updatedItem;
+ }
+
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/NodesRemovedEvent.java b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/NodesRemovedEvent.java
new file mode 100644
index 0000000..05f552e
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/NodesRemovedEvent.java
@@ -0,0 +1,54 @@
+/*-
+ * #%L
+ * OrgChart Add-on
+ * %%
+ * Copyright (C) 2017 - 2025 Flowing Code S.A.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.orgchart.event;
+
+import com.flowingcode.vaadin.addons.orgchart.OrgChart;
+import com.vaadin.flow.component.ComponentEvent;
+
+/**
+ * Event fired when a node and its descendants are removed from the organization chart. Contains
+ * information about the removed node.
+ */
+@SuppressWarnings("serial")
+public class NodesRemovedEvent extends ComponentEvent {
+ private final Integer nodeId;
+
+ /**
+ * Creates a new nodes removed event.
+ *
+ * @param source the chart component that fired the event
+ * @param nodeId the ID of the removed node
+ * @param fromClient whether the event originated from the client side
+ */
+ public NodesRemovedEvent(OrgChart source, Integer nodeId, boolean fromClient) {
+ super(source, fromClient);
+ this.nodeId = nodeId;
+ }
+
+ /**
+ * Gets the ID of the removed node.
+ *
+ * @return the node ID
+ */
+ public Integer getNodeId() {
+ return nodeId;
+ }
+
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/ParentAddedEvent.java b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/ParentAddedEvent.java
new file mode 100644
index 0000000..33856eb
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/ParentAddedEvent.java
@@ -0,0 +1,54 @@
+/*-
+ * #%L
+ * OrgChart Add-on
+ * %%
+ * Copyright (C) 2017 - 2025 Flowing Code S.A.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.orgchart.event;
+
+import com.flowingcode.vaadin.addons.orgchart.OrgChart;
+import com.flowingcode.vaadin.addons.orgchart.OrgChartItem;
+import com.vaadin.flow.component.ComponentEvent;
+
+/**
+ * Event fired when a new parent is added to the chart.
+ */
+@SuppressWarnings("serial")
+public class ParentAddedEvent extends ComponentEvent {
+
+ private final OrgChartItem newParent;
+
+ /**
+ * Creates a new event.
+ *
+ * @param source the component that fired the event
+ * @param newParent the item that was added as the new parent
+ * @param fromClient true if the event originated from the client
+ */
+ public ParentAddedEvent(OrgChart source, OrgChartItem newParent, boolean fromClient) {
+ super(source, fromClient);
+ this.newParent = newParent;
+ }
+
+ /**
+ * Gets the item that was added as the new parent/root.
+ *
+ * @return the new parent item
+ */
+ public OrgChartItem getNewParent() {
+ return newParent;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/SiblingsAddedEvent.java b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/SiblingsAddedEvent.java
new file mode 100644
index 0000000..e2e6fc3
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/orgchart/event/SiblingsAddedEvent.java
@@ -0,0 +1,70 @@
+/*-
+ * #%L
+ * OrgChart Add-on
+ * %%
+ * Copyright (C) 2017 - 2025 Flowing Code S.A.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.orgchart.event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.flowingcode.vaadin.addons.orgchart.OrgChart;
+import com.flowingcode.vaadin.addons.orgchart.OrgChartItem;
+import com.vaadin.flow.component.ComponentEvent;
+
+/**
+ * Event fired when siblings are added to a node in the organization chart. Contains information
+ * about both the target node and the newly added siblings.
+ */
+@SuppressWarnings("serial")
+public class SiblingsAddedEvent extends ComponentEvent {
+ private final OrgChartItem item;
+ private final List newSiblings;
+
+ /**
+ * Creates a new siblings added event.
+ *
+ * @param source the chart component that fired the event
+ * @param item the node that received new siblings
+ * @param newSiblings list of the newly added siblings
+ * @param fromClient whether the event originated from the client side
+ */
+ public SiblingsAddedEvent(OrgChart source, OrgChartItem item, List newSiblings,
+ boolean fromClient) {
+ super(source, fromClient);
+ this.item = item;
+ this.newSiblings = new ArrayList<>(newSiblings);
+ }
+
+ /**
+ * Gets the node that received new siblings.
+ *
+ * @return the node
+ */
+ public OrgChartItem getItem() {
+ return item;
+ }
+
+ /**
+ * Gets the list of the newly added siblings.
+ *
+ * @return the list of new sibling
+ */
+ public List getNewSiblings() {
+ return newSiblings;
+ }
+}
diff --git a/src/main/resources/META-INF/frontend/fc-orgchart.js b/src/main/resources/META-INF/frontend/fc-orgchart.js
index 6071b4e..9ddf115 100644
--- a/src/main/resources/META-INF/frontend/fc-orgchart.js
+++ b/src/main/resources/META-INF/frontend/fc-orgchart.js
@@ -146,7 +146,126 @@ class FCOrgChart extends PolymerElement {
});
}
+ this._chartInstance = orgchart;
}
+
+ /**
+ * Adds sibling nodes for designated node.
+ *
+ * @param nodeId the node id to add new siblings to
+ * @param siblings the new sibling data
+ * @see {@link https://github.com/dabeng/OrgChart/tree/v3.7.0?tab=readme-ov-file#addsiblingsnode-data|OrgChart Documentation addSiblings($node, data)}
+ */
+ addSiblings(nodeId, siblings) {
+ var $ = window.jQuery || jQuery;
+ const $node = $('#' + nodeId);
+ if ($node.length) {
+ const siblingsData = typeof siblings === 'string' ? JSON.parse(siblings) : siblings;
+ if ($node.length && this._chartInstance) {
+ try {
+ this._chartInstance.addSiblings($node, siblingsData);
+ } catch (error) {
+ // This prevents validation error from stopping the script
+ }
+
+ // Notify server about siblings added with just the IDs
+ const siblingIds = siblingsData.map(sibling => sibling.id);
+ this.$server.onSiblingsAdded(nodeId, siblingIds);
+ }
+ }
+ }
+
+ /**
+ * Adds child nodes for designed node.
+ *
+ * @param nodeId the node id to add new children to
+ * @param children the new children data
+ * @see {@link https://github.com/dabeng/OrgChart/tree/v3.7.0?tab=readme-ov-file#addchildrennode-data|OrgChart Documentation addChildren($node, data)}
+ */
+ addChildren(nodeId, children) {
+ var $ = window.jQuery || jQuery;
+ const $node = $('#' + nodeId);
+ if ($node.length) {
+ const childrenData = typeof children === 'string' ? JSON.parse(children) : children;
+ if ($node.length && this._chartInstance) {
+ this._chartInstance.addChildren($node, childrenData);
+ // Notify server about children added with just the IDs
+ const childIds = childrenData.map(child => child.id);
+ this.$server.onChildrenAdded(nodeId, childIds);
+ }
+ }
+ }
+
+ /**
+ * Removes the designated node and its descedant nodes.
+ *
+ * @param nodeId the node id to be removed
+ * @see {@link https://github.com/dabeng/OrgChart/tree/v3.7.0?tab=readme-ov-file#removenodesnode|OrgChart Documentation removeNodes($node)}
+ */
+ removeNodes(nodeId) {
+ var $ = window.jQuery || jQuery;
+ const $node = $('#' + nodeId);
+ if ($node.length && this._chartInstance) {
+ this._chartInstance.removeNodes($node);
+ this.$server.onNodesRemoved(nodeId);
+ }
+ }
+
+ /**
+ * Adds a new parent to the chart.
+ *
+ * @param parent the new parent node to be added
+ * @see {@link https://github.com/dabeng/OrgChart/tree/v3.7.0?tab=readme-ov-file#addparentdata|OrgChart Documentation addParent(data)}
+ */
+ addParent(parent) {
+ const parentData = typeof parent === 'string' ? JSON.parse(parent) : parent;
+ if (this._chartInstance) {
+ // Find the current root node element in the chart
+ const $currentRoot = this._chartInstance.$chart.find('.node:first');
+ if ($currentRoot.length) {
+ this._chartInstance.addParent($currentRoot, parentData);
+ this.$server.onParentAdded(parentData.id);
+ } else {
+ console.error('OrgChart: Could not find the current root node to attach a parent to.');
+ }
+ }
+ }
+
+ /**
+ * Updates a node with new data and redraws the chart.
+ *
+ * @param nodeId the ID of the node to update
+ * @param newData an object or JSON string containing the new data for the node
+ */
+ updateNode(nodeId, newData) {
+ if (!this._chartInstance) {
+ return;
+ }
+
+ // Get the current full data hierarchy from the chart instance
+ const hierarchy = this._chartInstance.getHierarchy(true);
+
+ // Use JSONDigger to find the node to be updated
+ const digger = new window.JSONDigger(hierarchy, this._chartInstance.options.nodeId, 'children');
+ const nodeToUpdate = digger.findNodeById(parseInt(nodeId, 10));
+
+ if (nodeToUpdate) {
+ // Parse the new data and merge it into the found node
+ const dataToMerge = typeof newData === 'string' ? JSON.parse(newData) : newData;
+ // Delete the ID from the new data to prevent it from being overwritten
+ delete dataToMerge.id;
+ // Avoid children list to be overwritten, the node should keep it's original children list
+ delete dataToMerge.children;
+ // Merge the data
+ Object.assign(nodeToUpdate, dataToMerge);
+
+ // Re-initialize the chart with the updated hierarchy
+ // This redraws the chart to reflect the changes
+ this._chartInstance.init({ 'data': hierarchy });
+ // Notify server about the node update
+ this.$server.onNodeUpdated(nodeId);
+ }
+ }
isIEBrowser() {
var sAgent = window.navigator.userAgent;
@@ -182,7 +301,7 @@ class FCOrgChart extends PolymerElement {
static get properties() {
return {
- // Declare your properties here.
+ _chartInstance: Object
};
}
}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/orgchart/EditChartDemo.java b/src/test/java/com/flowingcode/vaadin/addons/orgchart/EditChartDemo.java
new file mode 100644
index 0000000..411a89c
--- /dev/null
+++ b/src/test/java/com/flowingcode/vaadin/addons/orgchart/EditChartDemo.java
@@ -0,0 +1,483 @@
+/*-
+ * #%L
+ * OrgChart Add-on
+ * %%
+ * Copyright (C) 2017 - 2025 Flowing Code S.A.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.flowingcode.vaadin.addons.orgchart;
+
+import com.flowingcode.vaadin.addons.demo.DemoSource;
+import com.vaadin.flow.component.Component;
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.button.ButtonVariant;
+import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
+import com.vaadin.flow.component.dependency.CssImport;
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.icon.VaadinIcon;
+import com.vaadin.flow.component.notification.Notification;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
+import com.vaadin.flow.component.radiobutton.RadioGroupVariant;
+import com.vaadin.flow.component.textfield.TextField;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+/**
+ * Demo view to demonstrate how an {@code OrgChart} can be edited.
+ *
+ * This view showcases the following operations:
+ *
+ * - Adding siblings to an existing node.
+ * - Adding children to an existing node.
+ * - Adding a new parent (root) to the entire chart.
+ * - Removing nodes from the chart.
+ * - Updating the data of a selected node.
+ *
+ */
+@SuppressWarnings("serial")
+@PageTitle("Edit Chart")
+@DemoSource
+@Route(value = "orgchart/edit-chart", layout = OrgchartDemoView.class)
+@CssImport("./styles/orgchart/edit-chart-demo-styles.css")
+public class EditChartDemo extends VerticalLayout {
+
+ private static final AtomicInteger idCounter = new AtomicInteger(100);
+
+ private OrgChart orgChart;
+ private OrgChartItem selectedNode;
+ private TextField selectedNodeNameField;
+ private VerticalLayout newNodeFieldsLayout;
+ private HorizontalLayout newNodeButtonsLayout;
+ private RadioButtonGroup typeSelector;
+ private Button addButton;
+ private Button deleteButton;
+ private Button updateButton;
+ private Button resetButton;
+ private HorizontalLayout newNodeActionsColumn;
+ private RadioButtonGroup actionSelector;
+
+ public EditChartDemo() {
+ setSizeFull();
+ initChart();
+ add(orgChart, getEditionLayout());
+ }
+
+ private void initChart() {
+ orgChart = getChart();
+ orgChart.addClassNames("chart-container", "editable-chart");
+ orgChart.setChartTitle(
+ "EDIT CHART - Add Children, Add Siblings, Add Parent, Remove Nodes, Edit Selected Node");
+ orgChart.setChartNodeContent("title");
+
+ // Add listener for node click
+ orgChart.addOnNodeClickListener(e -> {
+ selectedNode = e.getClickedItem();
+ selectedNodeNameField.setValue(selectedNode.getName());
+ updateComponentStates();
+ });
+
+ // Add listener to know when siblings are added
+ orgChart.addSiblingsAddedListener(e -> {
+ OrgChartItem targetNode = e.getItem();
+ List newSiblings = e.getNewSiblings();
+ if (!newSiblings.isEmpty()) {
+ String siblingNames =
+ newSiblings.stream().map(OrgChartItem::getName).collect(Collectors.joining(", "));
+ String message =
+ String.format("New siblings \"%s\" added to %s", siblingNames, targetNode.getName());
+ Notification.show(message);
+ // Console - Show hierarchy updated after adding siblings
+ System.out.println(
+ "------ OrgChart updated: ------\n" + e.getSource().getOrgChartItem().toString());
+ }
+ });
+
+ // Add listener to know when children are added
+ orgChart.addChildrenAddedListener(e -> {
+ OrgChartItem targetNode = e.getItem();
+ List newChildren = e.getNewChildren();
+ String childrenNames =
+ newChildren.stream().map(OrgChartItem::getName).collect(Collectors.joining(", "));
+ String message =
+ String.format("New children \"%s\" added to %s", childrenNames, targetNode.getName());
+ Notification.show(message);
+ // Console - Show hierarchy updated after adding children
+ System.out.println(
+ "------ OrgChart updated: ------\n" + e.getSource().getOrgChartItem().toString());
+ });
+
+ // Add listener on nodes removal
+ orgChart.addNodesRemovedListener(e -> {
+ String message = String.format("Item with id %s (and its descedant nodes) removed from chart",
+ e.getNodeId());
+ Notification.show(message);
+ // Console - Show hierarchy updated after removing nodes
+ System.out.println(
+ "------ OrgChart updated: ------\n" + e.getSource().getOrgChartItem().toString());
+ });
+
+ // Add listener on new parent added
+ orgChart.addParentAddedListener(e -> {
+ String message =
+ String.format("New parent \"%s\" added to chart", e.getNewParent().getName());
+ Notification.show(message);
+ // Console - Show hierarchy updated after adding a new parent
+ System.out.println(
+ "------ OrgChart updated: ------\n" + e.getSource().getOrgChartItem().toString());
+ });
+
+ // Add listener when a node data is updated
+ orgChart.addNodeUpdatedListener(e -> {
+ String message = String.format("Node \"%s\" was updated.", e.getUpdatedItem().getName());
+ Notification.show(message);
+ // Console - Show hierarchy updated after editing a node
+ System.out.println(
+ "------ OrgChart updated: ------\n" + e.getSource().getOrgChartItem().toString());
+ });
+ }
+
+ private OrgChart getChart() {
+ OrgChartItem item1 = new OrgChartItem(1, "John Williams", "Director");
+ OrgChartItem item2 = new OrgChartItem(2, "Anna Thompson", "Administration");
+ OrgChartItem item3 = new OrgChartItem(3, "Timothy Jones", "Sub-Director");
+ item1.setChildren(Arrays.asList(item2, item3));
+ OrgChartItem item4 = new OrgChartItem(4, "Louise Night", "Department 1");
+ OrgChartItem item5 = new OrgChartItem(5, "John Porter", "Department 2");
+ item2.setChildren(Arrays.asList(item4, item5));
+ OrgChartItem item6 = new OrgChartItem(6, "Charles Thomas", "Department 3");
+ item5.setChildren(Arrays.asList(item6));
+ return new OrgChart(item1);
+ }
+
+ private VerticalLayout getEditionLayout() {
+ // Main container for the entire panel
+ VerticalLayout editionPanel = new VerticalLayout();
+ editionPanel.addClassName("edition-panel");
+ editionPanel.setSpacing(false);
+
+ // Action Selector
+ actionSelector = new RadioButtonGroup<>();
+ actionSelector.setLabel("Select Action");
+ actionSelector.setItems("Add", "Edit", "Delete");
+ actionSelector.addValueChangeListener(event -> updateComponentStates());
+
+ Div separator = new Div();
+ separator.addClassName("edition-panel-separator");
+ separator.setWidthFull();
+
+ // Main Controls Layout
+ HorizontalLayout mainControlsLayout = new HorizontalLayout();
+ mainControlsLayout.setWidthFull();
+ mainControlsLayout.addClassName("main-controls-layout");
+ mainControlsLayout.setAlignItems(Alignment.BASELINE);
+
+ // Selected node layout (selected node and relation type selector)
+ VerticalLayout selectedNodeColumn = createVerticalLayout();
+ selectedNodeColumn.addClassName("selected-node-layout");
+ selectedNodeColumn.setAlignItems(Alignment.START);
+
+ // Selected node name text field
+ selectedNodeNameField = new TextField("Selected Node:");
+ selectedNodeNameField.setPlaceholder("Node Name");
+ selectedNodeNameField.setWidth("180px");
+ selectedNodeNameField.setReadOnly(true);
+
+ // Relation type selector
+ typeSelector = new RadioButtonGroup();
+ typeSelector.setLabel("Select Relation Type:");
+ typeSelector.setItems("Parent(root)", "Child", "Sibling");
+ typeSelector.setValue("Child");
+ typeSelector.addThemeVariants(RadioGroupVariant.LUMO_VERTICAL);
+
+ // New node(s) layout (dynamic)
+ newNodeFieldsLayout = createVerticalLayout();
+ newNodeFieldsLayout.addClassName("new-nodes-layout");
+
+ // Action buttons
+ addButton = new Button("Add Child to Selected Node");
+ deleteButton = new Button("Delete Selected Node");
+ updateButton = new Button("Edit Selected Node");
+ resetButton = new Button("Reset Chart");
+
+ // Add value-change listener to relation type selector
+ typeSelector.addValueChangeListener(event -> {
+ switch (event.getValue()) {
+ case "Parent(root)":
+ addButton.setText("Add Parent Node");
+ break;
+ case "Sibling":
+ addButton.setText("Add Sibling to Selected Node");
+ break;
+ default:
+ addButton.setText("Add Child to Selected Node");
+ break;
+ }
+ updateComponentStates();
+ });
+
+ selectedNodeColumn.add(selectedNodeNameField, typeSelector);
+
+ // Add the first text field for a new node initially
+ TextField initialNodeField = createNewNodeTextField();
+ initialNodeField.setLabel("Add New Node Name:");
+ newNodeFieldsLayout.add(initialNodeField);
+
+ // Create add/remove buttons for new nodes
+ // These buttons will be used to add or remove text fields for new nodes
+ Button addButtonSmall = createIconButton(VaadinIcon.PLUS);
+ Button removeButtonSmall = createIconButton(VaadinIcon.MINUS);
+
+ newNodeButtonsLayout = new HorizontalLayout(addButtonSmall, removeButtonSmall);
+ newNodeButtonsLayout.addClassName("new-nodes-buttons-layout");
+ newNodeButtonsLayout.setPadding(false);
+ newNodeButtonsLayout.setSpacing(false);
+
+ // Click listener for the '+' button to add a new text field
+ addButtonSmall.addClickListener(e -> newNodeFieldsLayout.add(createNewNodeTextField()));
+
+ // Click listener for the '-' button to remove the last added text field
+ removeButtonSmall.addClickListener(e -> {
+ if (newNodeFieldsLayout.getComponentCount() > 1) {
+ Component lastField =
+ newNodeFieldsLayout.getComponentAt(newNodeFieldsLayout.getComponentCount() - 1);
+ newNodeFieldsLayout.remove(lastField);
+ }
+ });
+
+ // Adding new nodes layout
+ newNodeActionsColumn = new HorizontalLayout(newNodeFieldsLayout, newNodeButtonsLayout);
+ newNodeActionsColumn.setAlignItems(Alignment.BASELINE);
+
+ // Add listeners to actions
+ addButton.addClickListener(e -> onAddButtonClick());
+ deleteButton.addClickListener(e -> onDeleteButtonClick());
+ updateButton.addClickListener(e -> onUpdateButtonClick());
+ resetButton.addClickListener(e -> onResetButtonClick());
+
+ // Layout for action buttons
+ VerticalLayout actionButtonsColumn = createVerticalLayout();
+ actionButtonsColumn.add(addButton, deleteButton, updateButton, resetButton);
+ actionButtonsColumn.setJustifyContentMode(JustifyContentMode.END);
+ actionButtonsColumn.setAlignItems(Alignment.END);
+
+ // Add all columns to the main controls layout
+ mainControlsLayout.add(selectedNodeColumn, newNodeActionsColumn, actionButtonsColumn);
+
+ // Add the actionSelector and the main controls to the final panel
+ editionPanel.add(actionSelector, separator, mainControlsLayout);
+
+ // Set the initial state
+ updateComponentStates();
+
+ return editionPanel;
+ }
+
+ private void updateComponentStates() {
+ String action = actionSelector.getValue();
+ boolean isAdd = "Add".equals(action);
+ boolean isEdit = "Edit".equals(action);
+ boolean isDelete = "Delete".equals(action);
+ boolean nodeIsSelected = (selectedNode != null);
+
+ String relation = typeSelector.getValue();
+ boolean isParentMode = "Parent(root)".equals(relation);
+
+ if(isParentMode) {
+ resetNewNodeFields();
+ }
+
+ typeSelector.setVisible(isAdd);
+ newNodeActionsColumn.setVisible(isAdd);
+ newNodeButtonsLayout.setVisible(isAdd && !isParentMode);
+ selectedNodeNameField.setVisible(isEdit || isDelete || (isAdd && !isParentMode));
+
+ addButton.setEnabled(isAdd);
+ updateButton.setEnabled(isEdit && nodeIsSelected);
+ deleteButton.setEnabled(isDelete && nodeIsSelected);
+ }
+
+ private void onAddButtonClick() {
+ // Make sure a node is selected
+ if (selectedNode == null && selectedNodeNameField.isVisible()) {
+ Notification.show("Please select a node first.");
+ return;
+ }
+
+ // Get all non-empty names from the text fields
+ List newNodeNames =
+ newNodeFieldsLayout.getChildren().filter(component -> component instanceof TextField)
+ .map(component -> ((TextField) component).getValue())
+ .filter(name -> name != null && !name.trim().isEmpty()).collect(Collectors.toList());
+
+ if (newNodeNames.isEmpty()) {
+ Notification.show("Please enter a name for the new node(s).");
+ return;
+ }
+
+ // Create new OrgChartItem objects with unique IDs
+ List newItems = new ArrayList<>();
+ for (String name : newNodeNames) {
+ int newId = idCounter.getAndIncrement();
+ OrgChartItem newItem = new OrgChartItem(newId, name, "Undefined");
+ newItems.add(newItem);
+ }
+
+ // Add the new nodes to the chart
+ String relationType = typeSelector.getValue();
+
+ switch (relationType) {
+ case "Child":
+ orgChart.addChildren(selectedNode.getId(), newItems);
+ break;
+ case "Sibling":
+ try {
+ orgChart.addSiblings(selectedNode.getId(), newItems);
+ } catch (IllegalArgumentException ex) {
+ Notification.show(ex.getMessage());
+ }
+ break;
+ case "Parent(root)":
+ orgChart.addParent(newItems.get(0));
+ break;
+ }
+
+ // Reset the text fields for the next operation
+ resetNewNodeFields();
+ // Clear the text from the first field
+ ((TextField) newNodeFieldsLayout.getComponentAt(0)).clear();
+ }
+
+ private void onDeleteButtonClick() {
+ if (selectedNode == null) {
+ Notification.show("Please select a node to delete.");
+ return;
+ }
+
+ Integer selectedNodeId = selectedNode.getId();
+ // Check if the selected node is the root
+ boolean isRootNode = selectedNodeId.equals(orgChart.getOrgChartItem().getId());
+
+ if (isRootNode) {
+ // If it is the root, create and show a confirmation dialog
+ ConfirmDialog dialog = new ConfirmDialog();
+ dialog.setHeader("Delete Entire Chart?");
+ dialog.setText(
+ "Are you sure you want to delete the root node? This action will remove the entire chart.");
+
+ dialog.setConfirmText("Delete Chart");
+ dialog.setConfirmButtonTheme("error primary");
+ dialog.setCancelable(true);
+
+ dialog.addConfirmListener(event -> {
+ orgChart.removeNodes(selectedNodeId);
+ clearSelectedNode();
+ Notification.show("Chart deleted.");
+ });
+
+ dialog.open();
+ } else {
+ orgChart.removeNodes(selectedNodeId);
+ clearSelectedNode();
+ Notification.show("Node deleted.");
+ }
+ }
+
+ private void onUpdateButtonClick() {
+ if (selectedNode == null) {
+ Notification.show("Please select a node to update.");
+ return;
+ }
+
+ // Create a dialog for editing
+ ConfirmDialog dialog = new ConfirmDialog();
+ dialog.setHeader("Edit Node Details");
+
+ // Create fields for name and title, pre-filled with current data
+ TextField nameField = new TextField("Name");
+ nameField.setValue(selectedNode.getName());
+ nameField.setWidthFull();
+
+ TextField titleField = new TextField("Title");
+ titleField.setValue(selectedNode.getTitle());
+ titleField.setWidthFull();
+
+ dialog.add(new VerticalLayout(nameField, titleField));
+ dialog.setConfirmText("Save");
+ dialog.setConfirmButtonTheme("primary");
+ dialog.setCancelable(true);
+
+ // Add a listener for the confirm (Save) button
+ dialog.addConfirmListener(event -> {
+ // Create a new item with the updated data (using a temporary ID)
+ OrgChartItem updatedData = new OrgChartItem(0, nameField.getValue(), titleField.getValue());
+
+ // Call the updateNode method
+ orgChart.updateNode(selectedNode.getId(), updatedData);
+ });
+
+ dialog.open();
+ }
+
+ private void onResetButtonClick() {
+ OrgChart oldChart = this.orgChart;
+ initChart();
+ this.replace(oldChart, this.orgChart);
+ clearSelectedNode();
+ actionSelector.setValue(null);
+ }
+
+ private void clearSelectedNode() {
+ selectedNode = null;
+ selectedNodeNameField.clear();
+ updateComponentStates();
+ }
+
+ private Button createIconButton(VaadinIcon icon) {
+ Button iconButton = new Button(icon.create());
+ iconButton.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_SMALL,
+ ButtonVariant.LUMO_TERTIARY_INLINE);
+ return iconButton;
+ }
+
+ private TextField createNewNodeTextField() {
+ TextField newNodeTextField = new TextField();
+ newNodeTextField.setPlaceholder("Name");
+ newNodeTextField.setWidth("150px");
+ return newNodeTextField;
+ }
+
+ private VerticalLayout createVerticalLayout() {
+ VerticalLayout verticalLayout = new VerticalLayout();
+ verticalLayout.setWidth("auto");
+ verticalLayout.setSpacing(false);
+ verticalLayout.setPadding(false);
+ return verticalLayout;
+ }
+
+ private void resetNewNodeFields() {
+ // Reset the text fields for the next operation
+ while (newNodeFieldsLayout.getComponentCount() > 1) {
+ // Remove all fields except the first one
+ newNodeFieldsLayout.remove(newNodeFieldsLayout.getComponentAt(1));
+ }
+ }
+}
diff --git a/src/test/java/com/flowingcode/vaadin/addons/orgchart/OrgchartDemoView.java b/src/test/java/com/flowingcode/vaadin/addons/orgchart/OrgchartDemoView.java
index f714811..d1bb147 100644
--- a/src/test/java/com/flowingcode/vaadin/addons/orgchart/OrgchartDemoView.java
+++ b/src/test/java/com/flowingcode/vaadin/addons/orgchart/OrgchartDemoView.java
@@ -41,5 +41,6 @@ public OrgchartDemoView() {
addDemo(ImageInTitleDemo.class);
addDemo(HybridEnhancedChartDemo.class);
addDemo(HybridDataPropertyDemo.class);
+ addDemo(EditChartDemo.class);
}
}
diff --git a/src/test/resources/META-INF/resources/frontend/styles/orgchart/edit-chart-demo-styles.css b/src/test/resources/META-INF/resources/frontend/styles/orgchart/edit-chart-demo-styles.css
new file mode 100644
index 0000000..9f21b17
--- /dev/null
+++ b/src/test/resources/META-INF/resources/frontend/styles/orgchart/edit-chart-demo-styles.css
@@ -0,0 +1,53 @@
+/*-
+ * #%L
+ * OrgChart Add-on
+ * %%
+ * Copyright (C) 2017 - 2025 Flowing Code S.A.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+.edition-panel {
+ border: 1px solid var(--lumo-contrast-10pct);
+ border-radius: 8px;
+ padding-top: 0;
+}
+
+.edition-panel-separator {
+ border-top: 1px solid var(--lumo-contrast-10pct);
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+.new-nodes-layout {
+ padding-bottom: 8px;
+}
+
+.new-nodes-buttons-layout {
+ margin-left: 5px;
+}
+
+.selected-node-layout {
+ margin-left: 0;
+}
+
+.main-controls-layout {
+ flex-wrap: wrap;
+ gap: 1em;
+}
+
+/* This targets the direct children inside the layout. */
+.main-controls-layout > * {
+ flex: 1 1 0;
+ min-width: 200px;
+}
\ No newline at end of file