NAME Droppable - Global drag-and-drop file interception system SYNOPSIS Add class="rsx-droppable" to any element or component to receive dropped files via the file-drop event. DESCRIPTION Droppable is the RSX framework's global file drop system. It intercepts all file drag-and-drop operations at the document level and routes dropped files to designated drop targets using CSS class-based registration. Unlike traditional HTML5 drag-and-drop which requires explicit event handlers on each element, Droppable provides: - Automatic visual feedback during file drags - Smart target selection (single vs multiple targets) - Cursor feedback indicating valid/invalid drop zones - Component event integration Droppable initializes automatically during framework bootstrap. No manual initialization is required. CSS CLASSES rsx-droppable Marks an element as a valid file drop target. Add this class to any element or component that should receive dropped files. Example:
Drop files here
rsx-drop-active Automatically added to ALL visible rsx-droppable elements when files are being dragged anywhere on the page. Use this for visual feedback like highlighting or borders. Example CSS: .My_Upload_Widget.rsx-drop-active { border: 2px dashed #007bff; background: rgba(0, 123, 255, 0.1); } rsx-drop-target Automatically added to the specific element that will receive the drop. This is the "hot" target that files will go to if the user releases. Example CSS: .My_Upload_Widget.rsx-drop-target { border: 2px solid #007bff; background: rgba(0, 123, 255, 0.2); } TARGET SELECTION BEHAVIOR Single Target: When only ONE visible rsx-droppable element exists on the page, it automatically becomes the drop target as soon as files enter the window. No hover required. Multiple Targets: When MULTIPLE visible rsx-droppable elements exist, the user must hover over a specific element to select it as the target. The cursor shows "no-drop" when not over a valid target. Visibility: Only visible elements participate in drop handling. Elements that are display:none, visibility:hidden, or outside the viewport are ignored. This allows inactive widgets to exist in the DOM without interfering. FILE-DROP EVENT When files are dropped on a valid target, a file-drop event is triggered on the component (if the element is a component) or the element itself. Component Event Handler: class My_Upload_Widget extends Jqhtml_Component { on_render() { this.on('file-drop', (component, data) => { this._handle_files(data.files); }); } _handle_files(files) { for (let file of files) { console.log('Received:', file.name, file.type, file.size); // Upload file, validate type, etc. } } } jQuery Event Handler (non-component elements): $('.my-drop-zone').on('file-drop', function(e, data) { for (let file of data.files) { console.log('Received:', file.name); } }); Event Data: { files: FileList, // The dropped files dataTransfer: DataTransfer, // Full dataTransfer object originalEvent: DragEvent // Original browser event } FILE VALIDATION Each widget is responsible for validating dropped files. Droppable provides files without filtering. Common validation patterns: By MIME Type: _handle_files(files) { for (let file of files) { if (!file.type.startsWith('image/')) { Flash.error(`${file.name} is not an image`); continue; } this._upload(file); } } By Extension: _handle_files(files) { const allowed = ['.pdf', '.doc', '.docx']; for (let file of files) { const ext = '.' + file.name.split('.').pop().toLowerCase(); if (!allowed.includes(ext)) { Flash.error(`${file.name}: only PDF and Word documents allowed`); continue; } this._upload(file); } } By Size: _handle_files(files) { const max_size = 10 * 1024 * 1024; // 10 MB for (let file of files) { if (file.size > max_size) { Flash.error(`${file.name} exceeds 10 MB limit`); continue; } this._upload(file); } } Single File Only: _handle_files(files) { if (files.length > 1) { Flash.error('Please drop only one file'); return; } this._upload(files[0]); } CURSOR FEEDBACK Droppable automatically sets dropEffect to control cursor appearance: - "copy" cursor: Shown when hovering over a valid drop target - "none" cursor: Shown when no valid target exists or when hovering outside all targets (in multi-target mode) This provides immediate visual feedback about whether a drop will succeed. IMPLEMENTATION NOTES Drag Counter: Droppable uses a drag counter to track when files enter and leave the window. This handles the common issue where dragenter and dragleave fire for child elements. Event Prevention: Droppable prevents default browser behavior for all file drags, ensuring files are never accidentally downloaded or opened. Cleanup: Drag state is automatically cleared when: - Files are dropped (successfully or not) - Drag operation is cancelled (e.g., Escape key) - Files leave the window entirely EXAMPLES Basic Image Uploader:
Drop image here
class Image_Uploader extends Jqhtml_Component { on_render() { this.on('file-drop', (_, data) => { const file = data.files[0]; if (!file || !file.type.startsWith('image/')) { Flash.error('Please drop an image file'); return; } // Show preview const reader = new FileReader(); reader.onload = (e) => { this.$sid('preview').attr('src', e.target.result).show(); }; reader.readAsDataURL(file); // Upload this._upload(file); }); } } Multi-File Document Upload:
Drop documents here
    class Document_Dropzone extends Jqhtml_Component { on_create() { this.state.files = []; } on_render() { this.on('file-drop', (_, data) => { for (let file of data.files) { if (!this._validate(file)) continue; this.state.files.push(file); this._add_to_list(file); } }); } _validate(file) { const allowed = ['application/pdf', 'application/msword']; if (!allowed.includes(file.type)) { Flash.error(`${file.name}: only PDF and Word files allowed`); return false; } if (file.size > 25 * 1024 * 1024) { Flash.error(`${file.name}: maximum 25 MB`); return false; } return true; } } STYLING RECOMMENDATIONS Provide clear visual states for drag operations: .My_Dropzone { border: 2px dashed #ccc; padding: 20px; text-align: center; transition: all 0.2s ease; } /* Files are being dragged - highlight potential targets */ .My_Dropzone.rsx-drop-active { border-color: #007bff; background: rgba(0, 123, 255, 0.05); } /* This specific element will receive the drop */ .My_Dropzone.rsx-drop-target { border-style: solid; background: rgba(0, 123, 255, 0.15); } DROPPABLE VS HTML5 DRAG-DROP Standard HTML5: - Must add dragenter, dragover, drop handlers to each element - Must manually prevent default behavior - Must track drag state per element - No automatic multi-target coordination Droppable: - Add rsx-droppable class, handle file-drop event - Droppable handles all drag events - Automatic state management and cleanup - Smart single vs multi-target behavior TROUBLESHOOTING Files not being received: - Verify element has rsx-droppable class - Check element is visible (not display:none) - Ensure event handler is registered before drop Visual feedback not appearing: - Define CSS for .rsx-drop-active and .rsx-drop-target - Check CSS specificity isn't being overridden Wrong target receiving files (multiple targets): - Both targets are visible; ensure unused widget is hidden - Check z-index if elements overlap Cursor shows "no-drop" unexpectedly: - No visible rsx-droppable elements on page - Not hovering over any target in multi-target mode SEE ALSO file_upload.txt - Server-side file upload system jqhtml.txt - JQHTML component system VERSION RSpade Framework 1.0 Last Updated: 2026-01-15