javascript - Drag/drop files on the page -- lack of a consistent solution -


here's i'm trying achieve:

there multiple dropzones on page. users should able drag files os , drop them dropzones.

dropzones highlighted during dragging. there 2 visually different types of highlighting: "target" (for example, element gets outlined dashed border) , "hover" (for example, element gets bright background).

the target highlighting applied/removed on/from dropzones @ once:

  • when user drags file on page, dropzones should highlighted target highlighting.
  • when user drags file outside page, or cancels drag/drop operation, or performs drop, target highlighting should removed dropzones.

the hover highlighting should applied 1 dropzone:

  • when user drags file on dropzone, dropzone should highlighted hover highlighting.
  • when user drags file outside dropzone, or cancels drag/drop operation, or performs drop, target highlighting should removed dropzone.

when user drops file on dropzone, file's name should appear inside dropzone.

when user drops file on page outside dropzones, dropzones highlighting should removed , nothing else should happen. specifically, dropped file should not opened browser.

the solution should graceful possible: dirty hacks using timeouts, counting dragenter/dragleave events , reapplying highlighting on every dragover not welcome.

the solution should work in latest versions of major browsers.

here's i've managed achieve far: attempt 1, attempt 2.

problems have solved

  1. dropping file outside dropzone resulted in browser opening file.

    solution:

    $(document).on('dragover drop', function (e) {     e.preventdefault(); }); 
  2. dropping file dropzone generates drop event target equal child of dropzone rather dropzone itself.

    solution:

    $dropzones.on( 'drop', function (event) {    /* ... */    // find dropzone responsible event   $targetdropzone = $(event.target).closest($dropzones);    /* ... */ }); 
  3. hovering file on dropzone's children generates multiple dragleave events, making hover highlighting disappear immediately (the hover highlighting should removed dropzone when mouse cursor leaves dropzone, it's bound dragleave event).

    solution: use dragout event instead of dragleave. dragout custom event provided jquery.event.dragout plugin. event won't fire element's children.

unsolved problems

  1. unable detect moment when dragged file leaves document or window, "remove target highlighting" command executed.

    the custom dragout plugin designed work children of <body>. works neither document nor window.

    the official dragstart , dragend events won't work @ dragging files. expected behavior. :(

    the dragenter , dragleave events bound document triggered not when mouse pointer enters/leaves document when pointer enters/leaves document's children. worse of all, event.target of first $(document).on('dragenter') occurrence may appear 1 element (it can document or child), , last occurrence of $(document).on('dragleave') can appear different element, can't resolve issue comparing event.targets.

    due these issues, failed gracefully track moment when mouse leaves document.

    i have attempted use draghover plugin aimed resolve issue. managed make work (tested in chrome), stop working correctly after first successful drop. see failed attempt here.

  2. though it's impossible tell visually, dragenter event gets fired many times while file hovers on document. thus, highlighting applied multiple times instead of one.

  3. the mouse pointer during drag should display browser's standard "can't drop here" icon when hovering outside dropzones , "can drop here" icon when hovering on dropzones.


upd 2014-03-16 15:30, reply ian bytchek's answer

hi comrade! thank detailed reply. unfortunately, there's number of issues solution.

1.

  1. unable detect moment when dragged file leaves document or window, "remove target highlighting" command executed.

$(document).on('dragleave', … must trick, see fiddle below.

nope, bad.

let's listen dragenter , dragleave events on <body>. whenever dragging mouse pointer hovers across edge of element, 2 events trigger:

  • dragenter on <body> event.target set hovered element;
  • dragleave on <body> event.target set parent of hovered element.

i supposed target highlighting applied .dropzone.target-higlighing selector. did witty trick applying target highlighting .target-highlighting .dropzone selector.

have @ code:

$('body')     .on('dragenter', function (event) {         $(event.target).addclass('target-highlighting');         event.preventdefault();     })     .on('dragleave drop', function (event) {         $(event.target).removeclass('target-highlighting-class');         event.preventdefault();     }) 

if dropzone resides in number of nested containers, dragging file across containers result in target highlighting class migrate form outermost container innermost. due fact used .target-highlighting .dropzone selector in css, looks target highlighting stands...

...until drag file on element not 1 of dropzone's parents. sidebar or dropzone itself. when happens, .target-highlighting .dropzone selector stops being applied , target highlighting disappears.

this not acceptable. target highlighting should appear when file dragged onto page , removed when file dragged out of page or when dragging has been finished (by drop or cancellation).

2.

  1. though it's impossible tell visually, dragenter event gets fired many times while file hovers on document. thus, highlighting applied multiple times instead of one.

the event fired every time when mouse enters element. so, drag on page enters many elements , fires many times. avoid need "disable" underneath each droparea, there 2 ways this.

first, use css pointer events, elegant, least browser friendly solution. works recent ones, , love it.

second, create transparent overlay on top of droparea – mouse hit , not elements underneath, prevent multiple drag enter events.

these solutions acceptable triggering hover highlighting applied while mouse pointer inside dropzone. (btw, i've found more graceful solution this: dragout event plugin, see #3 in solved problems section above.)

but totally inappropriate target highlighting should applied while mouse pointer both inside , outside of dropzone. you'd have disable mouse events (with either pointer-events: none; or overlay) whole page, , dropzones no longer accept drops.

3.

  1. the mouse pointer during drag should display browser's standard "can't drop here" icon when hovering outside dropzones , "can drop here" icon when hovering on dropzones.

i'm not 100% on this, on mac can't seem change icon while dragging, uses special default one. assume can't done, love learn otherwise.

i've noticed ask works in chrome! see link below.

firefox not change mouse pointer though. :(

a better boilerplate test solutions on

there pretty looking libraries, http://www.dropzonejs.com/, don't have experience with, yet source of "inspiration".

i've seen plugin. not resolve issues described above. target highlighting not applied @ all, , hover highlighting flickers drag file on dropzone.

also, can't use because have own dropzone implementation. example, dropzone allows users sort files added dropzone. require solution handle drag events.

my personal advice use per-droparea plugin approach, not per-page approach in examples. components tend grow pretty big once add uploading logic, validation, etc.

you're absolutely right. in project, use wonderful jquery ui widget factory. it's method of defining jquery plugins behave separately each other.

here created better boilerplate test further solutions on: http://jsbin.com/rupaloba/4/edit?html,css,js,output

i hope it's not complicated.

privet andrey! i've faced of those, try share knowledge.

1. unable detect moment when dragged file leaves document or window, "remove target highlighting" command executed.

$(document).on('dragleave', … must trick, see fiddle below.

2. though it's impossible tell visually, dragenter event gets fired many times while file hovers on document. thus, highlighting applied multiple times instead of one.

the event fired every time when mouse enters element. so, drag on page enters many elements , fires many times. avoid need "disable" underneath each droparea, there 2 ways this.

first, use css pointer events, elegant, least browser friendly solution. works recent ones, , love it.

second, create transparent overlay on top of droparea – mouse hit , not elements underneath, prevent multiple drag enter events.

3. mouse pointer during drag should display browser's standard "can't drop here" icon when hovering outside dropzones , "can drop here" icon when hovering on dropzones.

i'm not 100% on this, on mac can't seem change icon while dragging, uses special default one. assume can't done, love learn otherwise. can use different design, background color change or maybe add cursor-like div follow mouse. example shows trick background.

fiddle examples: http://jsfiddle.net/ianbytchek/q6uep/8/


that concerns questions. personal advice use per-droparea plugin approach, not per-page approach in examples. components tend grow pretty big once add uploading logic, validation, etc. in nutshell:

  1. a base jquery plugin extended logic required in 2 (more) components.
  2. it handles drag , drop business + share base css / html keep dry.
  3. somewhere in index.js $(document).on('dragenter dragover drop', function… prevents opening files in browser , navigating away.

there pretty looking libraries, http://www.dropzonejs.com/, don't have experience with, yet source of "inspiration".

i've used following in code cover pointer-events in older browsers (but never tested really) – checks if mouse outside element's boundaries.

// jquery event configuration. jquery.event.props.push('datatransfer', 'pagex', 'pagey');  element.on('dragleave', function ( event) {                                                                                                                                             var elementposition = element.offset();                                                                                                                                                      var elementwidth = element.width();                                                                                                                                                          var elementheight = element.height();                                                                                                                                                         if (event.pagex < elementposition.left || event.pagex > elementposition.left + elementwidth || event.pagey < elementposition.top || event.pagey > elementposition.top + elementheight) {         element.removeclass(states.highlighted);                                                                                                                                                 }            // …         // …         // … 

update 1 (2014-03-16 19:00)

@andrey'lolmaus'mikhaylov, you're right on points – it's mess once start nesting things. played further , turned out real bitch, got intrigued. had little luck solving dragenter , dragleave events, i'm sure solution exists. did comeup less appealing though: http://jsfiddle.net/ianbytchek/q6uep/14/

it's neat solution , think it's going cleaner can other apporaches. @ same time feels little hacky coordinate checks. i'm tired looking @ it, if gets polished better / neater version great know.


Comments

Popular posts from this blog

php - SPIP: From Tag directly to an article -

jquery - isAjaxRequest always return false -

ruby on rails - In a controller spec, how to find a specific tag in the generated view? -