Skip to main content

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

fieldId
String
required
Unique identifier for the field. This value is transferred during drag operations.
fieldName
String
required
Display name shown in the UI. Human-readable label for the field.
icon
JIcon
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:
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

1

Drag Detected

User clicks and drags the JDraggableField
setOnDragDetected(event -> {
    Dragboard db = startDragAndDrop(TransferMode.COPY);
    // ...
});
2

Data Transfer Setup

Field ID is added to clipboard content
ClipboardContent content = new ClipboardContent();
content.putString(fieldId);  // "product_name"
db.setContent(content);
3

Visual Feedback

Snapshot of component becomes drag image
db.setDragView(this.snapshot(null, null));
4

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

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

  • 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
  • Use simple data formats when possible
  • Cache snapshots if reusing
  • Avoid heavy computation in drag handlers
  • Debounce drag over events if needed

API Reference

JDraggableField

public JDraggableField(
    String fieldId,
    String fieldName,
    JIcon icon
)
Methods:
MethodReturn TypeDescription
getFieldId()StringGet the field identifier
getFieldName()StringGet the display name

JavaFX Drag Events

EventDescription
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.

Build docs developers (and LLMs) love