Here's an idea that I haven't thought all the way through yet, but
wanted to write down before I forget.
The current model, with the Gears Drag and Drop API, is that you pick
which DOM elements on the page are interested in (file) drops,
register JS callbacks that are similar to the browser's inbuilt drag
and drop callbacks, except that they have extra information (i.e. file
blobs) in the callback arguments. Gears will intercept the browser's
inbuilt DnD event flow, and inject this extra information
automatically when invoking those callbacks.
Typically, a web-app developer that wants file drops will also want to
"protect" the rest of the page, so that missing the designated DOM
element drop target by 1 pixel won't jarringly navigate away from the
web-app. For someting like a YouTube uploader page, or a GMail "add an
attachment to this e-mail that I'm composing" page, I think it would
be a better user experience for the whole of the "client area" (i.e.
the big, middle chunk of the browser window that isn't the chrome) to
accept drops, rather than a fraction of the client area. Even if the
developer didn't want the whole page to be a drop target, they still
might want to call registerDropTarget(window.document.document,
options) with options containing empty callback functions (e.g.
'ondragenter': function() {return false;}), since on some browsers, if
DOM element A has a position:absolute style and looks like it is over
DOM element B, then a drop on A may not trigger B's event handlers (my
hypothesis is that this is because an absolutely positioned A does not
have B in its ancestor chain), so you need to catch the event at the
root element or else users will be confused when they think they're
dropping over your accepting DIV but instead they end up navigating
away from your web-app.
The current implementation also hasn't been stress tested with
multiple drop targets, especially if they're overlapping. There may be
a misunderstanding in which JS handler or C++ (i.e. Gears) event
handler is responsible for calling things like event.preventDefault
and event.stopPropagation. And, the way Gears currently intercepts
events at the C++ level (on IE and Firefox, but not Safari) before
forwarding them up to JavaScript may be problematic if we're
intercepting multiple times.
Yet another point is that, by designating a given DOM element, Gears
currently creates objects (that Ref() the element) that should be
explicitly released for long lived web-apps (e.g. there needs to be
explicit clean-up every time you're done with GMail's compose window)
to avoid increasing memory usage over the life of the page. This is
the reason why desktop.registerDropTarget returns an object, which has
only one member function: unregister().
The current API design was a combination of trying to follow the
spirit of HTML5's proposal, yet constrained to what was practically
achievable with browsers as shipped today.
As I think I mentioned in my previous (big) e-mail about Drag and Drop
API design, the ideal API might look something like Gears simply and
invisibly adding the ability to query
event.dataTransfer.getData('files') in the case of file drops.
However, we can't consistently (across all platforms) manipulate or
impersonate the dataTransfer object, so instead, we've provide this
information via a context.files argument (and what used to be called
event.files).
Here is a different API proposal. Rather than Gears invoking
designated callbacks with file information when events happen, what if
instead elements just participate in a browser's natural event flow,
and call Gears to get file information. Gears is the callee rather
than the caller, and provides a clipboard API like so:
Option X (the proposed API)
-------------------------------
<div ondrop="myOnDrop()">
...
var desktop = google.gears.factory.create('beta.desktop');
desktop.enableClipboard();
function myOnDrop() {
var shiftKey = window.event.shiftKey;
var clipboard = desktop.getClipboard();
var files = clipboard.getFiles();
if (files && files.length > 0) {
// ... do some stuff with files
}
}
-------------------------------
instead of
Option Y (the current API)
-------------------------------
<div id="foo">
...
var desktop = google.gears.factory.create('beta.desktop');
var fooDiv = document.getElementById('foo');
var dropTarget = desktop.registerDropTarget(fooDiv);
dropTarget.ondrop = function(context) {
var shiftKey = context.event.shiftKey;
var files = context.files
if (files && files.length > 0) {
// ... do some stuff with files
}
};
...
// somewhere else
dropTarget.unregister();
-------------------------------
The desktop.enableClipboard() call sets up the client area to
intercept drag and drop events exactly once (and to prevent file drops
from causing navigation), and clipboard.getFiles() call only returns
the blobs during an actual drop, otherwise it returns empty. Gears
doesn't implicitly inject itself into event handling, but is instead
explicitly called by JavaScript via desktop.getClipboard(), and
desktop.getClipboard().getFiles() is the equivalent of the ideal
event.dataTransfer.getData('files').
This (option X) is more or less exactly how Drag and Drop is
implemented in Safari, under the hood, with Gears C++ (and
Objective-C) doing some slight contortions to present the option X
implementation via an option Y API. The Safari implementation would be
simpler if we just presented an option X style API in the first place,
and I think that the IE and Firefox C++ implementations and the
JavaScript API will likewise be simpler and shorter.
Comments?