Monday, August 30, 2010

File Promises and dojo drag and drop

When Adobe pushed AIR 2.0 out via auto update, one of the first new API features that I wanted to try to take advantage of is the File Promises API. Armed with Christian Cantrell's article, I went about integrating File Promises into my application.

Drag and drop isn't a new interaction paradigm in the application I work on. Users have always been able to drag and drop items in the application thanks to dojo's drag and drop APIs. With AIR 2.0 and File Promises though, I wanted to give the user the ability to drag files internally in the application and also out of the application and onto their desktop. To allow both internal and external drag and drop operations, I needed to override the default dojo.dnd.Avatar. Overriding the default Avatar for dojo dnd operations is really easy - the constructor for your dnd source should include the following line (basically, you need some way of telling the dnd manager to use a different function when creating the avatar):
dojo.dnd.manager().makeAvatar = dojo.hitch(this, "makeAvatar");

You'll need a makeAvatar function which returns your custom Avatar:
makeAvatar: function() {
return new my.widget.FileListAvatar(dojo.dnd.manager());
},

Once you've told the dojo dnd manager to use your custom dnd avatar, you'll need to figure out when the avatar in question is being dragged close to the edge of the application. See the _handleMouseMove function below:

dojo.declare(
"my.widget.FileListAvatar",
[dojo.dnd.Avatar],
{
_mouseOverConnects: [],

construct: function() {
// override and cancel the autoScroll function to prevent whitespace from appearing
// in the application when the user is dragging files near the border.
dojo.dnd.autoScroll = function(){};
this._mouseOverConnects.push(dojo.connect(this.manager, "onMouseMove", this, "_handleMouseMove"));
}

_handleMouseMove: function() {
var pos, viewport = dojo.window.getBox();
if(this.node) {
pos = dojo.position(this.node);
if(viewport.w <= pos.x + pos.w || pos.x <= 0
|| viewport.h <= pos.y + pos.h || pos.y + pos.h <= 0) {
// at this stage we know that the user is dragging the avatar close to the application's boundaries.
// time to make a switch from an internal dnd operation to an external dnd.
this. _handleExternalDnd();
}
}

The _handleExternalDnd is the point in the application where we start using the AIR 2.0 file promises API. In the following code, all the items in my dnd source have a store and item property which contain information on the url and file name. The url property from each dnd item is passed to a new URLFilePromise object.
_handleExternalDnd: function() {
var f, url, cb = new air.Clipboard(), promises = [], vp = dojo.window.getBox(), isDraggedOut = false;
for(var j = 0, k = this.manager.nodes.length; j < k; j++) {
f = this.manager.source.getItem(this.manager.nodes[j].id).data;
url = f.store.getValue(f.item, "Url");
if(url !== "") {
var fp = new air.URLFilePromise();
fp.request = new air.URLRequest(url);
fp.relativePath = f.store.getValue(f.item, "FileName");
promises.push(fp);
}
}
cb.setData(air.ClipboardFormats.FILE_PROMISE_LIST_FORMAT, promises);
// the next line causes the switch from internal dnd to external dnd
air.NativeDragManager.doDrag(window.htmlLoader, cb);
air.NativeApplication.nativeApplication.activeWindow.stage.addEventListener(window.runtime.flash.events.NativeDragEvent.NATIVE_DRAG_UPDATE, function(evt) {
if(!isDraggedOut && evt.localX > vp.w) {
isDraggedOut = true;
} else if (isDraggedOut && evt.localX < vp.w) {
// TODO - this is the case where the file has been dragged out of the application and the user is dragging the file back in.
// would be nice at this stage to revert to revert back to an internal dnd operation.
}
});
dojo.style(this.node, "display", "none");
},

I've left some commented code in there which might give you hints of what I'd like to do when the user drags files back into the application after initially dragging them out. I haven't figured out how to cancel a native drag and drop operation just yet - if I do, I'll be sure to update this blog post.

That's pretty much it. Once the user drags files out of the application and into their native file system browser, the urls included in the URLFilePromise object will be downloaded. Check out some of the events you can connect to if you want to give your users a progress indicator of where their files are in terms of being downloaded. One point to note when using File Promises - users are only allowed to drag files from an AIR application into the default file browser. For example, the user cannot drag files directly from an Adobe AIR application into iPhoto or Lightroom. They need to drag the files into Finder first, wait for the files to be downloaded and then drag them into the file management application of their choice.

Looking to the future, it looks like this type of functionality will start to become available natively in the browser.

Some useful links which might help with further reading:

Getting started with dojo dnd
dojocampus' dnd articles
Advanced dojo dnd tutorial
CSS Ninja article on drag and drop
CSS Ninja article how gmail's dnd works

No comments:

Post a Comment