How to Drag and Drop a ListBox Element into a Blazor Diagram Control
Here’s a comprehensive guide to implementing drag-and-drop functionality that transfer items from a ListBox component into a Diagram in a Blazor application. This example explains the necessary setup, event handling, and JavaScript interop required to achieve smooth drag-and-drop behavior with accurate positioning.
Overview
This example allows users to drag items from a ListBox and drop them onto a Diagram canvas. Each dropped item is transformed into a node in the Diagram with the item’s content displayed as an annotation.
Implementation Steps
Follow these steps to set up and configure the ListBox and Diagram components, handle the drag-and-drop events, and manage precise positioning using JavaScript interop.
Step 1: Set Up the Razor Page Layout
Add the following markup to a Razor page to create the layout. It includes a ListBox for draggable items and a Diagram component as the drop target.
@page "/"
@using Syncfusion.Blazor.DropDowns
@using Syncfusion.Blazor.Diagram
@inject IJSRuntime jsRuntime;
<div class="source content-container position-relative">
<div class="row">
<!-- ListBox Section -->
<div class="col-md-3" id="listbox" style="padding-left: 2rem;">
<h4>Connectors</h4>
<SfListBox @ref="listBoxObj" DataSource="@filteredData" Scope="Diagram" TItem="ExtractorUI" AllowDragAndDrop="true" TValue="string[]">
<ListBoxFieldSettings Text="Extractor" Value="yamlfile" />
<ListBoxEvents TValue="string[]" TItem="ExtractorUI" DragStart="DragStart" OnDrop="OnDrop" />
</SfListBox>
</div>
<!-- Diagram Section -->
<div class="col-md-9" style="padding-left:1rem;">
<SfDiagramComponent ID="Diagram" @ref="diagram" Height="700px" Swimlanes="@swimlaneCollections"></SfDiagramComponent>
</div>
</div>
</div>
- ListBox Section: Displays draggable items. The
Scopeis set to"Diagram", restricting drops to the Diagram component only. - Diagram Section: Serves as the target where ListBox items are dropped. Each dropped item appears as a node with an annotation.
Step 2: Define Code-Behind for Data Binding and Event Handling
- Initialize Data: Load sample data for ListBox items.
- Handle Drag and Drop Events: Implement
DragStartandOnDropto manage drag-and-drop operations.
@code {
private SfDiagramComponent diagram;
private SfListBox<string[], ExtractorUI> listBoxObj;
private List<ExtractorUI> filteredData = new List<ExtractorUI>();
private string sampleData = "[{\"Extractor\":\"Facebook Ads\",\"yamlfile\":\"version: 1\"},...]"; // Sample JSON data
protected override async Task OnInitializedAsync()
{
// Deserialize sample data into ListBox items
filteredData = JsonSerializer.Deserialize<List<ExtractorUI>>(sampleData);
}
private bool isDragging;
// Sets dragging state when drag begins
public void DragStart(DragEventArgs<ExtractorUI> args)
{
isDragging = true;
}
// Handles drop event to add ListBox items as nodes in the Diagram
public async void OnDrop(DropEventArgs<ExtractorUI> args)
{
if (!isDragging)
{
currentPosition = null;
return;
}
// Compute drop position
(double dropX, double dropY) = await GetDropPositionAsync(args);
// Build node
Node droppedNode = CreateDroppedNode(args, dropX, dropY);
// Try placing inside swimlanes
if (diagram?.Swimlanes != null && diagram.Swimlanes.Count > 0 &&
TryPlaceInSwimlanes(droppedNode, dropX, dropY))
{
isDragging = false;
currentPosition = null;
return;
}
// Fallback: add to root
await diagram.AddDiagramElementsAsync(new DiagramObjectCollection<NodeBase> { droppedNode });
isDragging = false;
currentPosition = null;
}
private async Task<(double x, double y)> GetDropPositionAsync(DropEventArgs<ExtractorUI> args)
{
string listWidthStr = await jsRuntime.InvokeAsync<string>("getlistViewWidth", "listbox");
double listWidth = Double.Parse(listWidthStr);
return (args.Left - listWidth, args.Top);
}
private static Node CreateDroppedNode(DropEventArgs<ExtractorUI> args, double x, double y)
{
return new Node
{
Height = 100,
Width = 100,
OffsetX = x,
OffsetY = y,
Annotations = new DiagramObjectCollection<ShapeAnnotation>
{
new ShapeAnnotation { Content = (args.Items.FirstOrDefault() as ExtractorUI)?.Extractor }
}
};
}
}
Explanation of Code:
-
DragStart Event: Sets the
isDraggingflag, when an item is dragged from the ListBox. -
OnDrop Event:
- Converts the dropped ListBox item into a Diagram node.
- The
GetDropPositionAsyncmethod adjusts the node’s position based on the ListBox width. It uses JavaScript interop to invoke thegetlistViewWidthfunction, retrieves the ListBox width, and calculates the correct drop coordinates for the Diagram. - Creates a Diagram node by using the dropped item’s data via the
CreateDroppedNodemethod. - Checks whether the drop location falls within an existing swimlane by calling
TryPlaceInSwimlanesmethod. - If the dropped node is inside a lane’s bounds, it is added to that lane’s
Childrencollection. If it is outside lane bounds, it is added directly to the Diagram usingAddDiagramElementsAsyncmethod .
Step 3:Add Swimlane Helper Methods
The swimlane helper methods identify whether a dropped node falls within a swimlane lane and place it in the appropriate lane.
The TryPlaceInSwimlanes iterates through all available swimlanes in the Diagram and determines whether each swimlane uses a horizontal or vertical orientation. Based on the orientation, it calls the corresponding lane-placement method.
The TryPlaceInHorizontalLanes calculates the bounds of each lane row within a horizontally oriented swimlane. It checks whether the drop coordinates fall within a lane’s content area. If a match is found, the dropped node is added to that lane’s Children collection.
The TryPlaceInVerticalLanes performs the same validation for vertically oriented swimlanes. It calculates the bounds of each lane column and adds the dropped node to the matching lane’s Children collection when the drop position is inside the lane.
The PointInRect validates whether the drop coordinates (x, y) lie within a specified rectangular boundary. This method is used to determine whether the dropped node falls inside a lane’s bounds.
// Iterates all swimlanes and attempts to place the node in the correct lane
private bool TryPlaceInSwimlanes(Node droppedNode, double x, double y)
{
foreach (Swimlane swimlane in diagram.Swimlanes)
{
if (swimlane?.Lanes == null || swimlane.Lanes.Count == 0) continue;
if (swimlane.Orientation == Orientation.Horizontal)
{
if (TryPlaceInHorizontalLanes(swimlane, droppedNode, x, y)) return true;
}
else
{
if (TryPlaceInVerticalLanes(swimlane, droppedNode, x, y)) return true;
}
}
return false;
}
// Checks each horizontal lane row for a hit and places the node inside the matching lane
private static bool TryPlaceInHorizontalLanes(Swimlane swimlane, Node droppedNode, double x, double y)
{
double left = swimlane.OffsetX - (swimlane.Width ?? 0d) / 2d;
double top = swimlane.OffsetY - (swimlane.Height ?? 0d) / 2d;
double headerWidth = swimlane.Header?.Width ?? 0d;
double contentLeft = left + headerWidth;
double contentWidth = Math.Max(0d, (swimlane.Width ?? 0d) - headerWidth);
double laneTop = top;
foreach (Lane lane in swimlane.Lanes)
{
double laneHeight = lane.Height ?? 0d;
if (laneHeight <= 0d) continue;
if (PointInRect(x, y, contentLeft, laneTop, contentWidth, laneHeight))
{
lane.Children.Add(droppedNode);
return true;
}
laneTop += laneHeight;
}
return false;
}
// Checks each vertical lane column for a hit and places the node inside the matching lane
private static bool TryPlaceInVerticalLanes(Swimlane swimlane, Node droppedNode, double x, double y)
{
double left = swimlane.OffsetX - (swimlane.Width ?? 0d) / 2d;
double top = swimlane.OffsetY - (swimlane.Height ?? 0d) / 2d;
double headerHeight = swimlane.Header?.Height ?? 0d;
double contentTop = top + headerHeight;
double contentHeight = Math.Max(0d, (swimlane.Height ?? 0d) - headerHeight);
double laneLeft = left;
foreach (Lane lane in swimlane.Lanes)
{
double laneWidth = lane.Width ?? 0d;
if (laneWidth <= 0d) continue;
if (PointInRect(x, y, laneLeft, contentTop, laneWidth, contentHeight))
{
lane.Children.Add(droppedNode);
return true;
}
laneLeft += laneWidth;
}
return false;
}
// Returns true if (x, y) falls within the given bounding rectangle
private static bool PointInRect(double x, double y, double left, double top, double width, double height)
{
return x >= left && x <= left + width && y >= top && y <= top + height;
}
Step 4: Implement JavaScript Interop for Accurate Positioning
To position dropped items correctly in the Diagram, use JavaScript to retrieve the ListBox width. This ensures the node’s position aligns with the cursor’s drop location.
Add the following JavaScript function to wwwroot/index.html (or your layout file):
// JavaScript function to get the ListBox width
function getlistViewWidth(id){
var idw = document.getElementById(id);
return idw.offsetWidth.toString();
}
Step 5: Define Supporting Classes
Define ExtractorUI to represent each ListBox item’s data model:
public class ExtractorUI
{
public string Extractor { get; set; }
public string YamlFile { get; set; }
public bool IsEdited { get; set; }
public bool IsCustom { get; set; }
}
Step 6: Configure the ListBox for Drag-and-Drop
In the SfListBox component:
- Set
AllowDragAndDrop="true"to enable dragging. - Set
Scope="Diagram"to restrict the drop target to the Diagram.
ListBox Events
DragStart: Triggers when dragging begins and sets theisDraggingflag.OnDrop: Handles the item drop, adding a node to the Diagram using the dropped item’s data.
Step 7: Verify Drag-and-Drop Functionality
- Run the application.
- Drag an item from the ListBox and drop it onto the Diagram. The item should appear as a node, with its content displayed as an annotation.
Output:
You can download the complete working sample from here.
Conclusion:
You can refer to our Blazor Diagram feature tour page to learn about its other groundbreaking features, documentation, and how to quickly get started with configuration specifications. You can also explore our Blazor Diagram example to understand how to create and manipulate diagram elements.
For current customers, our Blazor components are available on the License and Downloads page. If you are new to Syncfusion®, you can try our 30-day free trial to evaluate our Blazor Diagram and other Blazor components.
If you have any questions or require clarifications, please let us know in the comments section below. You can also contact us through our support forums, Direct-Trac,, or feedback portal. We are always happy to assist you!