Overview
The JJArroyo library provides building blocks for creating custom draggable components. This guide covers:
Using JDraggableField for sidebar items
Creating custom draggable components
Implementing drag-and-drop interactions
Building component hierarchies
JDraggableField Component
JDraggableField is a pre-built draggable sidebar item that extends JSidebarItem with drag-and-drop capabilities.
Basic Usage
import com.jjarroyo.components.JDraggableField;
import com.jjarroyo.components.JIcon;
// Create a draggable field
JDraggableField field = new JDraggableField (
"product_name" , // Field ID (transferred during drag)
"Product Name" , // Display name
JIcon . TAG // Icon
);
// Add to sidebar or container
VBox sidebar = new VBox ( 10 );
sidebar . getChildren (). add (field);
Constructor Parameters
Unique identifier for the field. This value is transferred during drag operations.
Display name shown in the UI. Human-readable label for the field.
Optional icon displayed alongside the field name. Pass null for no icon.
Properties
// Get field properties
String id = field . getFieldId (); // "product_name"
String name = field . getFieldName (); // "Product Name"
// Properties are read-only after construction
Drag and Drop Implementation
Understanding how JDraggableField implements drag-and-drop:
Source Code (JDraggableField.java:22-38)
Key Concepts
private void initDraggable () {
getStyleClass (). add ( "j-draggable-field" );
// Drag and Drop Logic
setOnDragDetected (event -> {
Dragboard db = startDragAndDrop ( TransferMode . COPY );
ClipboardContent content = new ClipboardContent ();
content . putString (fieldId);
db . setContent (content);
db . setDragView ( this . snapshot ( null , null ));
event . consume ();
});
}
Drag Process Flow
Drag Detected
User clicks and drags the JDraggableField setOnDragDetected (event -> {
Dragboard db = startDragAndDrop ( TransferMode . COPY );
// ...
});
Data Transfer Setup
Field ID is added to clipboard content ClipboardContent content = new ClipboardContent ();
content . putString (fieldId); // "product_name"
db . setContent (content);
Visual Feedback
Snapshot of component becomes drag image db . setDragView ( this . snapshot ( null , null ));
Drop Target Receives
Target (e.g., JDesignCanvas) receives the field ID String fieldId = event . getDragboard (). getString ();
// Use fieldId to update UI
Creating Custom Draggable Components
Build your own draggable components following this pattern:
import javafx.scene.input. * ;
import javafx.scene.layout.HBox;
import com.jjarroyo.components.JIcon;
import com.jjarroyo.components.JLabel;
public class CustomDraggableItem extends HBox {
private final String itemId ;
private final String itemData ;
public CustomDraggableItem ( String itemId , String label ) {
this . itemId = itemId;
this . itemData = "custom_data" ;
// Setup UI
setSpacing ( 8 );
setPadding ( new javafx. geometry . Insets ( 8 ));
getStyleClass (). add ( "custom-draggable" );
JLabel labelNode = new JLabel (label);
getChildren (). addAll (
JIcon . GRIP_VERTICAL . view (),
labelNode
);
// Enable drag and drop
initDragAndDrop ();
}
private void initDragAndDrop () {
setOnDragDetected (event -> {
Dragboard db = startDragAndDrop ( TransferMode . COPY );
ClipboardContent content = new ClipboardContent ();
// Transfer custom data structure
content . putString (itemId + ":" + itemData);
db . setContent (content);
// Custom drag view (optional)
db . setDragView ( this . snapshot ( null , null ));
event . consume ();
});
// Optional: Add drag feedback
setOnDragDone (event -> {
if ( event . getTransferMode () == TransferMode . COPY ) {
System . out . println ( "Item copied: " + itemId);
}
event . consume ();
});
}
public String getItemId () {
return itemId;
}
}
Advanced Drag Features
Custom Data Formats
Transfer complex data using custom formats:
import javafx.scene.input.DataFormat;
import java.io.Serializable;
public class FieldData implements Serializable {
public String id ;
public String type ;
public Map < String , Object > properties ;
}
// Register custom data format
DataFormat FIELD_FORMAT = new DataFormat ( "application/x-field-data" );
// In drag source:
setOnDragDetected (event -> {
Dragboard db = startDragAndDrop ( TransferMode . COPY );
FieldData data = new FieldData ();
data . id = "field_1" ;
data . type = "text" ;
data . properties = Map . of ( "fontSize" , 14 );
ClipboardContent content = new ClipboardContent ();
content . put (FIELD_FORMAT, data);
db . setContent (content);
event . consume ();
});
// In drop target:
setOnDragDropped (event -> {
Dragboard db = event . getDragboard ();
if ( db . hasContent (FIELD_FORMAT)) {
FieldData data = (FieldData) db . getContent (FIELD_FORMAT);
// Use data...
}
});
Visual Drag Feedback
Customize the drag image:
import javafx.scene.SnapshotParameters;
import javafx.scene.paint.Color;
setOnDragDetected (event -> {
Dragboard db = startDragAndDrop ( TransferMode . COPY );
// Custom snapshot with transparency
SnapshotParameters params = new SnapshotParameters ();
params . setFill ( Color . TRANSPARENT );
WritableImage snapshot = this . snapshot (params, null );
db . setDragView (snapshot);
// Offset drag view from cursor
db . setDragViewOffsetX ( 20 );
db . setDragViewOffsetY ( 20 );
// ... set content
event . consume ();
});
Source Visual Feedback
Show visual changes during drag:
setOnDragDetected (event -> {
// Reduce opacity during drag
setOpacity ( 0.5 );
// ... start drag
});
setOnDragDone (event -> {
// Restore opacity when done
setOpacity ( 1.0 );
event . consume ();
});
Drop Target Implementation
Create custom drop targets that accept draggable items:
import javafx.scene.layout.StackPane;
public class CustomDropTarget extends StackPane {
public CustomDropTarget () {
getStyleClass (). add ( "custom-drop-target" );
setupDropHandling ();
}
private void setupDropHandling () {
// Accept drag over
setOnDragOver (event -> {
if ( event . getGestureSource () != this &&
event . getDragboard (). hasString ()) {
event . acceptTransferModes ( TransferMode . COPY );
}
event . consume ();
});
// Visual feedback on drag enter
setOnDragEntered (event -> {
if ( event . getGestureSource () != this &&
event . getDragboard (). hasString ()) {
setStyle ( "-fx-border-color: -color-primary; " +
"-fx-background-color: rgba(59, 130, 246, 0.05);" );
}
event . consume ();
});
// Remove feedback on drag exit
setOnDragExited (event -> {
setStyle ( "" );
event . consume ();
});
// Handle drop
setOnDragDropped (event -> {
Dragboard db = event . getDragboard ();
boolean success = false ;
if ( db . hasString ()) {
String data = db . getString ();
handleDrop (data);
success = true ;
}
event . setDropCompleted (success);
event . consume ();
});
}
private void handleDrop ( String data ) {
// Update UI based on dropped data
JLabel label = new JLabel ( data . toUpperCase ());
getChildren (). clear ();
getChildren (). add (label);
}
}
Component Hierarchies
Build complex components by extending base classes:
import com.jjarroyo.components.JSidebarItem;
// Extend JSidebarItem for consistent styling
public class EnhancedDraggableField extends JSidebarItem {
private final String fieldId ;
private boolean selected = false ;
public EnhancedDraggableField ( String fieldId , String name , JIcon icon ) {
super (name, icon != null ? icon . view () : null );
this . fieldId = fieldId;
getStyleClass (). add ( "enhanced-field" );
// Add selection behavior
setOnMouseClicked (e -> toggleSelection ());
// Enable drag only when not selected
setOnDragDetected (e -> {
if ( ! selected) {
startDrag (e);
}
});
}
private void toggleSelection () {
selected = ! selected;
if (selected) {
getStyleClass (). add ( "selected" );
} else {
getStyleClass (). remove ( "selected" );
}
}
private void startDrag ( javafx . scene . input . MouseEvent event ) {
Dragboard db = startDragAndDrop ( TransferMode . COPY );
ClipboardContent content = new ClipboardContent ();
content . putString (fieldId);
db . setContent (content);
db . setDragView ( this . snapshot ( null , null ));
event . consume ();
}
public String getFieldId () { return fieldId; }
public boolean isSelected () { return selected; }
}
Complete Example: Custom Designer System
Custom Field Type
Custom Canvas
Application
import com.jjarroyo.components.JIcon;
import javafx.scene.input. * ;
import javafx.scene.layout.HBox;
public class DesignerField extends HBox {
public enum FieldType {
TEXT , NUMBER , DATE , BARCODE , IMAGE
}
private final String fieldId ;
private final String displayName ;
private final FieldType fieldType ;
public DesignerField ( String id , String name , FieldType type ) {
this . fieldId = id;
this . displayName = name;
this . fieldType = type;
setupUI ();
setupDragDrop ();
}
private void setupUI () {
setSpacing ( 10 );
setPadding ( new javafx. geometry . Insets ( 10 ));
getStyleClass (). add ( "designer-field" );
// Icon based on type
JIcon icon = switch (fieldType) {
case TEXT -> JIcon . TEXT ;
case NUMBER -> JIcon . HASHTAG ;
case DATE -> JIcon . CALENDAR ;
case BARCODE -> JIcon . BARCODE ;
case IMAGE -> JIcon . IMAGE ;
};
JLabel iconLabel = new JLabel ();
iconLabel . setGraphic ( icon . view ());
JLabel nameLabel = new JLabel (displayName);
nameLabel . addClass ( "font-medium" );
JLabel typeLabel = new JLabel ( fieldType . name ());
typeLabel . addClass ( "text-xs" , "text-slate-400" );
VBox textBox = new VBox ( 2 , nameLabel, typeLabel);
getChildren (). addAll (iconLabel, textBox);
}
private void setupDragDrop () {
setOnDragDetected (event -> {
Dragboard db = startDragAndDrop ( TransferMode . COPY );
// Transfer field data as JSON-like string
String data = String . format (
"{id:%s,type:%s}" ,
fieldId,
fieldType . name ()
);
ClipboardContent content = new ClipboardContent ();
content . putString (data);
db . setContent (content);
db . setDragView ( this . snapshot ( null , null ));
event . consume ();
});
}
public String getFieldId () { return fieldId; }
public FieldType getFieldType () { return fieldType; }
}
Best Practices
Use appropriate transfer modes
COPY: Source remains, target gets copy (sidebar → canvas)
MOVE: Source removed, target receives (reordering within canvas)
LINK: Reference to source (rarely used)
Change opacity during drag
Highlight valid drop targets
Show hover states
Update cursor styles
Invalid drop targets
Multiple simultaneous drags
Drag cancellation (ESC key)
Source and target validation
API Reference
JDraggableField
public JDraggableField (
String fieldId,
String fieldName,
JIcon icon
)
Methods:
Method Return Type Description getFieldId()StringGet the field identifier getFieldName()StringGet the display name
JavaFX Drag Events
Event Description setOnDragDetectedFired when drag gesture starts setOnDragOverFired continuously during drag setOnDragEnteredFired when entering drop target setOnDragExitedFired when leaving drop target setOnDragDroppedFired when drop occurs setOnDragDoneFired at drag source when complete
TransferMode Enum
COPY - Copy data to target
MOVE - Move data to target
LINK - Link to data
ANY - Accept any mode
Styling
CSS classes for custom components:
.j-draggable-field {
-fx-cursor : hand ;
}
.j-draggable-field:hover {
-fx-background-color : -color-bg-hover;
}
.custom-draggable {
-fx-background-color : white ;
-fx-border-color : -color-border-default;
-fx-border-radius : 8 ;
-fx-background-radius : 8 ;
}
.drag-over {
-fx-border-color : -color-primary;
-fx-background-color : rgba ( 59 , 130 , 246 , 0.05 );
}
For more examples of drag-and-drop implementation, see the Design Canvas documentation.