Skip to content

Commit

Permalink
Improved code for drag and drop, disabled drag and drop in safari (wh…
Browse files Browse the repository at this point in the history
…ich worked incorrectly with multiple files)
  • Loading branch information
Andrew Valums committed Aug 25, 2010
1 parent b73dad6 commit c3fdfc3
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 66 deletions.
188 changes: 123 additions & 65 deletions client/fileuploader.js
Expand Up @@ -172,76 +172,46 @@ qq.FileUploader.prototype = {
return false;
},
_setupDragDrop: function(){
function isValidDrag(e){
var dt = e.dataTransfer,
// do not check dt.types.contains in webkit, because it crashes safari 4
isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;

// dt.effectAllowed is none in Safari 5
// dt.types.contains check is for firefox
return dt && dt.effectAllowed != 'none' &&
(dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));
}

var self = this,
dropArea = this._getElement('drop');

dropArea.style.display = 'none';

var hideTimeout;
qq.attach(document, 'dragenter', function(e){
e.preventDefault();
});

qq.attach(document, 'dragover', function(e){
if (isValidDrag(e)){

if (hideTimeout){
clearTimeout(hideTimeout);
}
var dz = new qq.FileDropZone({
element: dropArea,
onEnter: function(e){
qq.addClass(dropArea, self._classes.dropActive);
e.stopPropagation();
},
onLeave: function(e){
e.stopPropagation();
},
onLeaveNotDescendants: function(e){
qq.removeClass(dropArea, self._classes.dropActive);
},
onDrop: function(e){
dropArea.style.display = 'none';
qq.removeClass(dropArea, self._classes.dropActive);
self._uploadFileList(e.dataTransfer.files);
}
});

if (dropArea == e.target || qq.contains(dropArea,e.target)){
var effect = e.dataTransfer.effectAllowed;
if (effect == 'move' || effect == 'linkMove'){
e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
} else {
e.dataTransfer.dropEffect = 'copy'; // for Chrome
}
qq.addClass(dropArea, self._classes.dropActive);
e.stopPropagation();
} else {
dropArea.style.display = 'block';
e.dataTransfer.dropEffect = 'none';
}

e.preventDefault();
}
dropArea.style.display = 'none';

qq.attach(document, 'dragenter', function(e){
if (!dz._isValidFileDrag(e)) return;

dropArea.style.display = 'block';
});

qq.attach(document, 'dragleave', function(e){
if (isValidDrag(e)){

if (dropArea == e.target || qq.contains(dropArea,e.target)){
qq.removeClass(dropArea, self._classes.dropActive);
e.stopPropagation();
} else {

if (hideTimeout){
clearTimeout(hideTimeout);
}

hideTimeout = setTimeout(function(){
dropArea.style.display = 'none';
}, 77);
}
}
});

qq.attach(dropArea, 'drop', function(e){
dropArea.style.display = 'none';
self._uploadFileList(e.dataTransfer.files);
e.preventDefault();
});
qq.attach(document, 'dragleave', function(e){
if (!dz._isValidFileDrag(e)) return;

var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);

// only fire when leaving document out
if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){
dropArea.style.display = 'none';
}
});
},
_createUploadHandler: function(){
var self = this,
Expand Down Expand Up @@ -421,6 +391,91 @@ qq.FileUploader.prototype = {
}
};

qq.FileDropZone = function(o){
this._options = {
element: null,
onEnter: function(e){},
onLeave: function(e){},
// is not fired when leaving element by hovering descendants
onLeaveNotDescendants: function(e){},
onDrop: function(e){}
};
qq.extend(this._options, o);

this._element = this._options.element;

this._disableDropOutside();
this._attachEvents();
};

qq.FileDropZone.prototype = {
_disableDropOutside: function(e){
// run only once for all instances
if (!qq.FileDropZone.dropOutsideDisabled ){

qq.attach(document, 'dragover', function(e){
e.dataTransfer.dropEffect = 'none';
e.preventDefault();
});

qq.FileDropZone.dropOutsideDisabled = true;
}
},
_attachEvents: function(){
var self = this;

qq.attach(self._element, 'dragover', function(e){
if (!self._isValidFileDrag(e)) return;

var effect = e.dataTransfer.effectAllowed;
if (effect == 'move' || effect == 'linkMove'){
e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
} else {
e.dataTransfer.dropEffect = 'copy'; // for Chrome
}

e.stopPropagation();
e.preventDefault();
});

qq.attach(self._element, 'dragenter', function(e){
if (!self._isValidFileDrag(e)) return;

self._options.onEnter(e);
});

qq.attach(self._element, 'dragleave', function(e){
if (!self._isValidFileDrag(e)) return;

self._options.onLeave(e);

var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
// do not fire when moving a mouse over a descendant
if (qq.contains(this, relatedTarget)) return;

self._options.onLeaveNotDescendants(e);
});

qq.attach(self._element, 'drop', function(e){
if (!self._isValidFileDrag(e)) return;

e.preventDefault();
self._options.onDrop(e);
});
},
_isValidFileDrag: function(e){
var dt = e.dataTransfer,
// do not check dt.types.contains in webkit, because it crashes safari 4
isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;

// dt.effectAllowed is none in Safari 5
// dt.types.contains check is for firefox
return dt && dt.effectAllowed != 'none' &&
(dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));

}
};

qq.UploadButton = function(o){
this._options = {
element: null,
Expand Down Expand Up @@ -871,7 +926,10 @@ qq.remove = function(element){
element.parentNode.removeChild(element);
};

qq.contains = function(parent, descendant){
qq.contains = function(parent, descendant){
// compareposition returns false in this case
if (parent == descendant) return true;

if (parent.contains){
return parent.contains(descendant);
} else {
Expand Down
48 changes: 48 additions & 0 deletions tests/test-drop-zone.htm
@@ -0,0 +1,48 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
.drop-zone {height:100px; width:256px; background:gray; margin:20px;}
</style>
<script src="jquery-1.4.2.min.js" type="text/javascript"></script>
<script src="../client/fileuploader.js" type="text/javascript" ></script>
<script>

function createDropZone(selector){
var element = $(selector)[0];

new qq.FileDropZone({
element: element,
onEnter: function(){
console.log('enter')
$(element).css('background', 'green');
},
onLeave: function(){
console.log('leave')
},
onLeaveNotDescendants: function(){
console.log('onLeaveNotDescendants')
$(element).css('background', 'gray');
},
onDrop: function(e){
$(element).css('background', 'gray');
console.log('drop');
console.log(e.dataTransfer.files);
}
});
}

jQuery(function(){
createDropZone('#drop-zone1');
createDropZone('#drop-zone2');
});

</script>
</head>
<body>
<div id="drop-zone1" class="drop-zone"><p>drop-zone1</p></div>
<div id="drop-zone2" class="drop-zone"><p>drop-zone2</p></div>
</body>
</html>


3 changes: 2 additions & 1 deletion tests/test-upload-handlers.htm
Expand Up @@ -21,7 +21,8 @@
el2.appendChild(el3);

var el4 = document.createElement('div');


ok(qq.contains(el1,el1));
ok(qq.contains(el1,el2));
ok(qq.contains(el1,el3));
ok(!qq.contains(el1,el4));
Expand Down

0 comments on commit c3fdfc3

Please sign in to comment.