A simple way to bring drag’n’drop to flutter web

Related tags

UI dropzone
Overview

drop_zone

pub package License: MIT

A simple way to bring drag’n’drop to flutter web.

drop_zone is commonly used for file choosing by dragging and dropping a file(s) onto a designated widget. The user can then use the dropped html file(s).

Example

An example can be found in the example directory of this repository.

How to use

Add drop_zone to pubspec.yaml of your project:

dependencies:
  drop_zone: ^1.0.2

Add necessary imports and wrap any widget with DropZone() to use it as a dropzone:

import 'package:drop_zone/drop_zone.dart';
import 'dart:html' as html;

DropZone(
    onDragEnter: () {
        print('drag enter');
    },
    onDragExit: () {
        print('drag exit');
    },
    onDrop: (List<html.File> files) {
        print('files dropped');
        print(files);
    },
    child: Container(
        width: 300,
        height: 300,
    )
)

License

MIT License.

Comments
  • Potential improvements to the library

    Potential improvements to the library

    Hi @Derrick56007 thanks for putting this library together. I tried a few for Flutter web and this one seems to work the best for us on various browsers. We ran into a few issues with bugs around multiple drop targets (need a single instance of the drag target), the mouse leaving the window, etc. So I copied your code and refactored it some.

    Happy to issue a PR at some point but a bit swamped at the moment.

    There are some pieces that are coupled to our codebase in here but thought it'd be better to share the code here to start a discussion.

    A few high level points

    • We have a single instance that handles the drag/drop events, stops the propagation, and relays them to widgets that need it. This is because if you do run e.stopPropagation() for every widget it won't work for multiple widgets (event gets swallowed).
    • We handle dragEnter/dragLeave because dragOver can't detect if your mouse leaves the window
    • Centralized the logic to handle _dragInBounds updates to better deal with edge cases
    • Reduced the number of null operators necessary with early returns

    Let me know what you think

    //@dart=2.12
    
    // adapted from https://github.com/Derrick56007/dropzone
    import 'dart:html';
    
    import 'package:flutter/widgets.dart';
    import 'package:turtle_app/file_manager/file_picker/html.dart';
    
    import '../file_drop_target.dart';
    import '../local_file.dart';
    import 'package:turtle_app/util/event_handler.dart';
    
    FileDropTarget createFileDropTarget({
      Key? key,
      required Widget child,
      Function()? onEnter,
      Function()? onExit,
      Function(Iterable<LocalFile>)? onDrop,
    }) =>
        HtmlFileDropTarget(
          key: key,
          child: child,
          onEnter: onEnter,
          onExit: onExit,
          onDrop: onDrop,
        );
    
    class HtmlFileDropTarget extends StatefulWidget implements FileDropTarget {
      const HtmlFileDropTarget({
        Key? key,
        required this.child,
        this.onEnter,
        this.onExit,
        this.onDrop,
      }) : super(key: key);
    
      final Widget child;
    
      final Function()? onEnter;
      final Function()? onExit;
      final Function(Iterable<LocalFile>)? onDrop;
    
      @override
      State<StatefulWidget> createState() => _HtmlFileDropTargetState();
    }
    
    class _HtmlFileDropTargetState extends State<HtmlFileDropTarget> {
      bool _dragInBounds = false;
    
      final _dispose = <void Function()>[];
    
      @override
      void dispose() {
        super.dispose();
        for (final cb in _dispose) cb();
      }
    
      @override
      void initState() {
        super.initState();
    
        _dispose.add(_controller.onDragEnter.addListener(_onDragEnter));
        _dispose.add(_controller.onDragOver.addListener(_onDragOver));
        _dispose.add(_controller.onDragLeave.addListener(_onDragLeave));
        _dispose.add(_controller.onDragDrop.addListener(_onDragDrop));
      }
    
      @override
      Widget build(BuildContext c) => widget.child;
    
      void _onDragEnter(MouseEvent e) {
        // can treat this like drag-over
        _onDragOver(e);
      }
    
      void _onDragLeave(MouseEvent e) {
        // mouse has left the window or drag region so consider this as onExit
        _setDragInBounds(false);
      }
    
      void _onDragOver(MouseEvent e) {
        final renderObject = context.findRenderObject();
        if (renderObject == null) {
          _setDragInBounds(false);
          return;
        }
    
        final bounds = _getGlobalPaintBounds(renderObject);
        _setDragInBounds(_isCursorWithinBounds(e, bounds));
      }
    
      void _onDragDrop(MouseEvent e) {
        _setDragInBounds(false);
    
        final htmlFiles = e.dataTransfer.files ?? [];
        final localFiles = htmlFiles.map(castToFile);
        widget.onDrop?.call(localFiles);
      }
    
      void _setDragInBounds(bool value) {
        if (_dragInBounds == value) return;
        _dragInBounds = value;
    
        if (_dragInBounds) {
          widget.onEnter?.call();
        } else {
          widget.onExit?.call();
        }
      }
    }
    
    Rect _getGlobalPaintBounds(RenderObject renderObject) {
      final translation = renderObject.getTransformTo(null).getTranslation();
      return renderObject.paintBounds.shift(Offset(translation.x, translation.y));
    }
    
    bool _isCursorWithinBounds(MouseEvent e, Rect bounds) {
      return e.layer.x >= bounds.left &&
          e.layer.x <= bounds.right &&
          e.layer.y >= bounds.top &&
          e.layer.y <= bounds.bottom;
    }
    
    class _HtmlFileDropTargetController {
      _HtmlFileDropTargetController() {
        final body = document.body!;
    
        body.onDragEnter.listen(_onDragEnter);
        body.onDragOver.listen(_onDragOver);
        body.onDragLeave.listen(_onDragLeave);
        body.onDrop.listen(_onDrop);
      }
    
      final onDragEnter = Event<MouseEvent>();
      final onDragOver = Event<MouseEvent>();
      final onDragLeave = Event<MouseEvent>();
      final onDragDrop = Event<MouseEvent>();
    
      void _onDragEnter(MouseEvent e) {
        _stopEvent(e);
        onDragEnter.notifyListeners(e);
      }
    
      void _onDragOver(MouseEvent e) {
        _stopEvent(e);
        onDragOver.notifyListeners(e);
      }
    
      void _onDragLeave(MouseEvent e) {
        _stopEvent(e);
        onDragLeave.notifyListeners(e);
      }
    
      void _onDrop(MouseEvent e) {
        _stopEvent(e);
        onDragDrop.notifyListeners(e);
      }
    }
    
    final _controller = _HtmlFileDropTargetController();
    
    void _stopEvent(MouseEvent e) {
      e.stopPropagation();
      e.stopImmediatePropagation();
      e.preventDefault();
    }
    
    typedef EventHandler<T> = void Function(T event);
    
    class Event<T> {
      final _listeners = <EventHandler<T>>{};
    
      void Function() addListener(EventHandler<T> listener) {
        _listeners.add(listener);
        return () => removeListener(listener);
      }
    
      void removeListener(EventHandler<T> listener) => _listeners.remove(listener);
    
      void notifyListeners(T event) {
        for (final listener in _listeners) listener(event);
      }
    
      void dispose() {
        _listeners.clear();
      }
    }
    

    Let me know if you have any questions on the changes in the code. Cheers!

    enhancement 
    opened by venkatd 3
  • Use universal_html and add cursor position

    Use universal_html and add cursor position

    This improves the plugin in two major ways:

    1. Uses universal_html instead of dart:html, which allows running tests and compiling for other platforms than web.
    2. Integrates #5, just with better syntax.

    → also generally improves the structure / syntax of the plugin to align with Dart/Flutter idioms.

    opened by creativecreatorormaybenot 2
  • Multiple DropZone issue

    Multiple DropZone issue

    I have a form with multiple video and image fields (in this case two video and one image). All have the DropZone is a Stack.

    I get several errors:

    1> The image does not display, just the videos. If I remove one of the video widget, the image is rendered.

    2> When I push a camera widget and pop back I get errors like this: ══╡ EXCEPTION CAUGHT BY SCHEDULER LIBRARY ╞═════════════════════════════════════════════════════════ The following IndexError was thrown during a scheduler callback: RangeError (index): Index out of range: index must not be negative: -1

    Can you help identify the issue?

    opened by sunilguptasg 1
  • DropZone not triggered over iFrame

    DropZone not triggered over iFrame

    I have an iFrame component and I wish to use DropZone - the onDrag and onDrop are not triggered.

    Don't know if this is an enhancement, bug or an issue on how I created the iFrame:

    html.IFrameElement _iframeElement;
    _iframeElement = html.IFrameElement();
    _iframeElement.id = iFrameId;
    _iframeElement.allow = 'accelerometer; gyroscope;';
    _iframeElement.allowFullscreen = true;
    _iframeElement.src = initialVideo;
    _iframeElement.style.border = 'none';
    _iframeElement.style.cursor = 'auto';
    _iframeElement.style.width = '100%';
    _iframeElement.style.height = '100%';
    
    // ignore: undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(iFrameId, (int viewId) => _iframeElement);
    
    videoWidget = HtmlElementView(
      key: key,
      viewType: iFrameId,
    );
    
    opened by sunilguptasg 0
  • Enhancement: createObjectUrlFromBlob

    Enhancement: createObjectUrlFromBlob

    The last method is not available, and has to be added explicitly.

    final name = file.name; final mime = file.type; final length = file.size;

    final url = Url.createObjectUrlFromBlob(file);

    I believe that a file.url will be a useful addition.

    opened by sunilguptasg 0
  • onDrop() not called after files were selected using the pickFiles() method.

    onDrop() not called after files were selected using the pickFiles() method.

    Hi, I would like to display a list with all files that will be uploaded. If I drop them on the drop zone this can be easily done using the onDrop method. I add the filenames to a list and call setState -> my view with a listview is updated. It is handy that this library also provides a method to open a file picker. Usually both is needed. But if files were picked with that file picker, the on drop is not called. So my view is not updated. Ho to solve that? Would it be an option to change this library to also call onDop if the filepickes is used?

    opened by Wissperwind 0
  • DropZone does not work After Hot Restart

    DropZone does not work After Hot Restart

    When using a DropZone widget in a Flutter web app and using hot restart, the drop zone will stop working.

    I investigated this and found out that the old listener is called instead of the new one after hot restart, i.e. the listener attached to the JS drop events survives the hot restart and no new listener is attached.

    @Derrick56007 do you have an idea how to fix this?

    opened by creativecreatorormaybenot 2
  • Add support for cursor position when dropped

    Add support for cursor position when dropped

    This exposes the cursor position when the files are dropped. This can be useful for any use-case where the dropped file needs to be shown at the dropped position.

    opened by BirjuVachhani 1
Flutter Complete E-Commerce app (UI by - 'The Flutter Way')

NOT COMPLETED YET! e_commerce A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to ge

null 1 Mar 8, 2022
A better way for new feature introduction and step-by-step users guide for your Flutter project.

A better way for new feature introduction and step-by-step users guide for your Flutter project.

好未来技术 139 Oct 26, 2022
A simple Flutter component capable of using JSON Schema to declaratively build and customize web forms.

A simple Flutter component capable of using JSON Schema to declaratively build and customize web forms.

null 3 Oct 2, 2022
Foody - Flutter project to display foods to clients and display charts and has a lot of features , two flutter apps : Android and Web

Foody Flutter project to display foods to the clients and use charts and use a lot of features to complete the process (HUGE APP) There two apps: Andr

ABDULKARIMALBAIK 1 Feb 7, 2022
Kraken - a high-performance, web standards-compliant rendering engine based on Flutter.

Kraken - a high-performance, web standards-compliant rendering engine based on Flutter.

OpenKraken 4.8k Jan 7, 2023
Flutter is Google's UI toolkit for building beautiful, natively compiled applications for mobile, web, desktop, and embedded devices from a single codebase.

flutter_quize A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if

IQBAL HASAN 3 Aug 24, 2022
Flutter YouTube UI - Web & Mobile: Android | IOS

YouTube Clone UI - Flutter Mobile: IOS | Android Mobile Version: Android | IOS @luanbatistadev Open Source Copyright © 2021-present, Luan Batista. Fac

Luan Batista 5 Sep 22, 2022
I am trying to teach Responsive Ui design. This video for Web and Mobile. This design is suitable for Desktop, Tab, and Mobile platforms.

before clone the GitHub repository please give a star on the repository. I am trying to teach Responsive Ui design. This video for Web and Mobile. Thi

Blackshadow Software Ltd 22 Sep 1, 2022
Fluid layouts allows you to create responsive layout for mobile, web and desktop from a single codebase.

Fluid layout aims to help building a responsive experience through all the different screen sizes. Based in the boostrap approach, FluidLayout calculates a padding content (fluid_padding) that changes depending on the parent size. The Fluid widget uses that padding to set its size

Jaime Blasco 3 Jan 10, 2022
Movies App UI in Flutter using Simple Widgets without Using API's in Flutter.

Movies App UI in Flutter using Simple Widgets without Using API's in Flutter.

Habib ullah 3 May 15, 2022
A flutter plugin for Easily make Flutter apps responsive. Automatically adapt UI to different screen sizes. Responsiveness made simple.

A flutter plugin for Easily make Flutter apps responsive. Automatically adapt UI to different screen sizes. Responsiveness made simple.

Urmish Patel 191 Dec 29, 2022
A simple animated radial menu widget for Flutter.

flutter_radial_menu A radial menu widget for Flutter. . Installation Install the latest version from pub. Quick Start Import the package, create a Rad

Victor Choueiri 434 Jan 7, 2023
A simple number ticker for flutter app.

Number Ticker Number_ticker is a dart package that provides a Robinhood-like number ticker widget for displaying changing number. Usage import 'packag

Jiaqi Feng 13 Jun 18, 2022
This is a simple movies app build it with flutter

Movies App ?? This is a simple app build it with flutter. Features: ?? Get movies from themoviedb.org Search movies See details movies and casting Too

Tony Gonzalez 2 Feb 1, 2021
A simple UI inclined tasks-app in Flutter.

A simple UI inclined tasks-app in Flutter.

Sambhav Saxena 0 Jun 22, 2022
ID-Card - A Simple ID Card Project Using Flutter

ID-CARD A new Flutter project.A simple demonstration of my time in the Dart lang

edwromero 0 Jan 26, 2022
A simple flutter UI project showing viewer a beautiful UI about coffee

coffee This is a simple flutter UI project where i am showing viewer a beautiful UI about coffee. And also i am using modular way to create this proje

Sarwar Murshed Shatil 5 Dec 13, 2022
A simple widget for animating a set of images with full custom controls as an alternative to using a GIF file.

image_sequence_animator A simple widget for animating a set of images with full custom controls as an alternative to using a GIF file. If you have a G

AliYigitBireroglu 134 Dec 22, 2022
This is simple Ninja ID Card App Code ;)

ninjacard A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if thi

Saini Madhu 4 Oct 17, 2022