Skip to content

Instantly share code, notes, and snippets.

@willbailey
Forked from jashkenas/DropManager.js
Created February 12, 2010 12:53
Show Gist options
  • Save willbailey/302532 to your computer and use it in GitHub Desktop.
Save willbailey/302532 to your computer and use it in GitHub Desktop.
//= require "util"
// This code is MIT licensed: http://creativecommons.org/licenses/MIT/
// Zenbe Inc (2009).
// Ensure our Zenbe namespaces exist.
window.zen = window.zen || {};
window.zen.util = window.zen.util || {};
/**
* The DropManager class provides a pleasant API for observing HTML5 drag-n-drop
* events, cleaning up the data that they return, and triggering the appropriate
* callbacks. With the drag and drop API so far, there are generally three types
* of data we're interested in: HTML, URLs, and plain text. On drop, your
* callback will receive a pre-processed drop object with corresponding 'url',
* 'html' and 'text' properties.
*
* To use it, instantiate a new DropManager, passing in an element to use as a
* drop target, as well as an (optional) callbacks object, with (optional)
* callbacks for drop, dragenter, dragover and dragleave:
*
* new zen.util.DropManager($('drop_target'), {
* drop : function(event, dropData) { ... process data here ... }
* });
*
*/
window.zen.util.DropManager = Class.create({
SUPPORTED_TYPES : ['text/uri-list', 'text/x-moz-url', 'text/html',
'text/plain', 'Url', 'Text', 'Files'],
IMAGE_LINK_DETECTOR : (/^\s*<a.+?<img.+?src=['"](\S+?)['"].*?>\s*<\/a>\s*$/i),
URL_DETECTOR : (/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/),
/**
* Registers all of the drop handlers that we'll need for dealing with
* drop-related actions on the target element.
*/
initialize : function(element, callbacks) {
this.enabled = true;
this.callbacks = callbacks || {};
this.el = element;
this.el.observe('dragenter', this._boundDragEnter = this._onDragEnter.bind(this));
this.el.observe('dragover', this._boundDragOver = this._onDragOver.bind(this));
this.el.observe('dragleave', this._boundDragLeave = this._onDragLeave.bind(this));
this.el.observe('drop', this._boundDrop = this._onDrop.bind(this));
},
/**
* Removes all of the drop handlers from the target element.
*/
destroy : function() {
this.el.stopObserving('dragenter', this._boundDragEnter);
this.el.stopObserving('dragover', this._boundDragOver);
this.el.stopObserving('dragleave', this._boundDragLeave);
this.el.stopObserving('drop', this._boundDrop);
},
/**
* Turn the DropManager's callbacks on and off. If disabled, none of your
* passed-in callbacks will be fired until the DropManager is re-enabled.
*/
setEnabled : function(enabled) {
this.enabled = enabled;
},
/**
* Does the DropManager support the drop event? Returns true if we're enabled,
* and we support at least one of the drop's mime types.
* @function ?
*/
supports : function(ev) {
if (ev.dataTransfer.files) return true;
if (!this.enabled) return false;
var types = $A(ev.dataTransfer.types);
if (!types.any()) return true; // IE doesn't give us any types whatsoever.
return this.SUPPORTED_TYPES.any(function(t){ return types.include(t); });
},
/**
* Internal function to post-process the drop data to pull out HTML image links
* (<a><img></a>). The desired url is probably for the image, not the link.
* In addition, some versions of webkit pass perfectly good urls as just
* text/plain -- pull 'em out.
*/
_postProcess : function(data) {
var textPart = data.html || data.text;
var match = textPart && textPart.match(this.IMAGE_LINK_DETECTOR);
if (match) data.url = match[1];
match = data.text && !data.url && data.text.match(this.URL_DETECTOR);
if (match) data.url = data.text;
return data;
},
/**
* On drop, we pull out our preferred and supported mime-types, process them
* for links, and return the resulting drop object to the provided callback.
* @function ?
*/
_onDrop : function(ev) {
ev.preventDefault();
if (!this.supports(ev)) return false;
var dt = ev.dataTransfer;
var data;
if (dt.files) {
data = {files : []};
// check after each file is loaded to see if more remain
var callback = function(file, base64FileData){
data.files.push({
file_name : file.name || file.fileName,
data : base64FileData
});
if ($A(dt.files).all(function(f){return f._completed;})) {
if (this.callbacks.drop) this.callbacks.drop(ev, data);
}
}.bind(this);
// process each file asynchronously
for (var i=0; i<dt.files.length; i++) {
this._handleFile(dt.files[i], callback);
}
} else {
try {
data = {
url : dt.getData('text/uri-list') || dt.getData('text/x-moz-url'),
html : dt.getData('text/html').stripScripts(),
text : dt.getData('text/plain')
};
} catch(e) { // IE doesn't support mime types yet.
data = {
url : dt.getData('Url'),
text : dt.getData('Text')
};
}
data = this._postProcess(data);
if (this.callbacks.drop) this.callbacks.drop(ev, data);
}
},
/**
* On drag enter, enable dropping on the drop target.
* @function ?
*/
_onDragEnter : function(ev) {
ev.preventDefault();
if (!this.supports(ev)) return false;
try { // IE Doesn't allow this property to be set ... but Webkit requires it.
ev.dataTransfer.effectAllowed = 'all';
ev.dataTransfer.dropEffect = 'copy';
} catch(e){}
if (this.callbacks.dragenter) this.callbacks.dragenter(ev);
},
/**
* On drag over, enable dropping on the drop target.
* @function ?
*/
_onDragOver : function(ev) {
ev.preventDefault();
// if (!this.supports(ev)) return false;
try { // IE Doesn't allow this property to be set ... but Webkit requires it.
ev.dataTransfer.effectAllowed = 'all';
ev.dataTransfer.dropEffect = 'copy';
} catch(e){}
if (this.callbacks.dragover) this.callbacks.dragover(ev);
},
/**
* Not much to do for drag leave.
* @function ?
*/
_onDragLeave : function(ev) {
ev.preventDefault();
if (!this.supports(ev)) return false;
if (this.callbacks.dragleave) this.callbacks.dragleave(ev);
},
/**
* read a dropped file
* @function ?
*/
_handleFile : function(file, callback) {
var binaryReader = new FileReader();
binaryReader.onloadend = function() {
file._completed=true;
callback(file, this._base64Encode(binaryReader.result));
}.bind(this);
console.log(file);
binaryReader.readAsBinaryString(file);
},
/**
* base64 encode
* @function ?
*/
_base64Encode : function(text){
if (/([^\u0000-\u00ff])/.test(text)){
throw new Error("Can't base64 encode non-ASCII characters.");
}
var digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
i = 0,
cur, prev, byteNum,
result=[];
while(i < text.length){
cur = text.charCodeAt(i);
byteNum = i % 3;
switch(byteNum){
case 0: //first byte
result.push(digits.charAt(cur >> 2));
break;
case 1: //second byte
result.push(digits.charAt((prev & 3) << 4 | (cur >> 4)));
break;
case 2: //third byte
result.push(digits.charAt((prev & 0x0f) << 2 | (cur >> 6)));
result.push(digits.charAt(cur & 0x3f));
break;
}
prev = cur;
i++;
}
if (byteNum == 0){
result.push(digits.charAt((prev & 3) << 4));
result.push("==");
} else if (byteNum == 1){
result.push(digits.charAt((prev & 0x0f) << 2));
result.push("=");
}
return result.join("");
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment