Friday, September 13, 2013

I Don't Understand Drop Events (in any language)


Regardless of whether or not I can test it in Dart, I still need to be able to drop project files into ICE Code Editor. So as much as it pains me to do this… tonight I will add that feature to ICE without a test to guide me.

After last night's vain attempt to TDD the behavior into existence, I have the outline of how this will look:
    Element.
      dropEvent.
      forTarget(document).
      listen((event) {
        event
          ..preventDefault()
          ..stopPropagation();

        var file = event.dataTransfer.files.first;
      });
Nothing too fancy there—in Dart or JavaScript. It establishes an event stream for drop events. When such an event occurs, the listener callback is invoked with the drop event. I prevent the default action (which would be to open the file in the browser) and then get started with reading the contents of the dropped file.

As of last night, I had only gotten as far as getting a reference to the uploaded file. I was unable to populate the dataTransfer property in a test, but it ought to work just fine in real life. The API for file readers in Dart seems to follow the JavaScript exactly. So my next step is to create a FileReader object that reads the dropped file as text:

      // ...
      listen((event) {
        event
          ..preventDefault()
          ..stopPropagation();

        var file = event.dataTransfer.files.first;
        new FileReader()
          ..onLoad((read_event) {
              print(read_event.target.result);
            })
          ..readAsText(file);
      });
If everything is working correctly, when I drop a file into ICE:



I should now see the contents of the file printed to the console.

Instead, I see the contents of the file that I just uploaded:



Arrgh! Is drop just completely broken in Dart?!

Er, no. It's just me.

In the end, I have to boil this down into the simplest JavaScript version that is possible before I realize that it still does not work. It seems that it is not sufficient to prevent the drop event—I also have to prevent the “dragover” event:
<body>
<h1>Hello</h1>
</body>
<script>
document.addEventListener('dragover', function (e) {
  e.preventDefault();
});

document.addEventListener('drop', function(e) {
  e.preventDefault();
  var file = e.dataTransfer.files[0];

  console.log('file.name: ' + file.name);
  console.log('file.type: ' + file.type);

  var reader = new FileReader();
  reader.onload = function (e) {
    console.log(e.target.result);
  };
  reader.readAsText(file);
});
</script>
And, in fact, I need to prevent both the dragover and drop default event behavior so that the dropped file does not open in the browser:



DOM coding: catch the fever!

Comforted in the knowledge that my trouble is with DOM coding, not Dart, I switch back to Dart. There, I also prevent the default dragover event behavior:
    document
      ..onDragOver.
          listen((event) {
            event.preventDefault();
          })
      ..onDrop.
          listen((event) {
            event.preventDefault();

            var file = event.dataTransfer.files.first;
            print('file.name: ${file.name}');
            print('file.type: ${file.type}');

            new FileReader()
              ..onLoad.
                  listen((read_event) {
                    print(read_event.target.result);
                  })
              ..readAsText(file);
          });
With that, I can successfully read the pertinent information from the dropped file:



No doubt there is a very good reason for needing to prevent two different events. I do not really want to know what it is. Really. Mostly I would like Dart or one of its libraries to make this go away. Regardless, I believe that I have a sufficient understanding of how this works to enable me to hook it into ICE—even if I cannot test it. That seems a fine stopping point for tonight.


Day #873

1 comment: