Files
rspade_system/app/RSpade/man/file_drop.txt
root d523f0f600 Fix code quality violations and exclude Manifest from checks
Document application modes (development/debug/production)
Add global file drop handler, order column normalization, SPA hash fix
Serve CDN assets via /_vendor/ URLs instead of merging into bundles
Add production minification with license preservation
Improve JSON formatting for debugging and production optimization
Add CDN asset caching with CSS URL inlining for production builds
Add three-mode system (development, debug, production)
Update Manifest CLAUDE.md to reflect helper class architecture
Refactor Manifest.php into helper classes for better organization
Pre-manifest-refactor checkpoint: Add app_mode documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 10:38:22 +00:00

319 lines
10 KiB
Plaintext
Executable File

NAME
File Drop Handler - 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
The RSpade framework provides a global file drop handler that intercepts
all file drag-and-drop operations at the document level. This system
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, this system provides:
- Automatic visual feedback during file drags
- Smart target selection (single vs multiple targets)
- Cursor feedback indicating valid/invalid drop zones
- Component event integration
The framework initializes this handler automatically during 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:
<div class="rsx-droppable">
Drop files here
</div>
<My_Upload_Widget class="rsx-droppable" />
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. The framework
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
The framework 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:
The framework 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:
The handler 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:
<Define:Image_Uploader tag="div" class="rsx-droppable">
<div class="drop-hint">Drop image here</div>
<img $sid="preview" style="display: none" />
</Define:Image_Uploader>
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:
<Define:Document_Dropzone tag="div" class="rsx-droppable Document_Dropzone">
<div class="drop-area">
<span class="icon">Drop documents here</span>
<ul $sid="file_list"></ul>
</div>
</Define:Document_Dropzone>
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);
}
RSX 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
RSX:
- Add rsx-droppable class, handle file-drop event
- Framework 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: 2025-01-14