Easy JavaScript Interop Without Dart Wrappers

Most of the Dart web developers I know do their best to avoid using JavaScript in their projects when they can, but it's not always possible. Sometimes you've just gotta have that one JS library in your project that does exactly what you need, and you don't have time to port it to Dart. In fact, once in a while, even using the fantastic JS interop package seems like more work than it's worth.

Fortunately, there are options. For a quick and dirty solution, you can simply include some JavaScript the usual way (toss it into index.html) and interact with it through HTML custom events! Since Dart for the Web is compiled to JavaScript, the code runs in the same context of execution, so it's not all that different from passing events around within JS itself.

Note: This article is an update/rewrite of the fabulous Faisal Abid's prior version. New developments in Dart (notably strong mode) altered the necessary code for this technique.

What are custom events?

HTML custom events are derived from the standard Event object, but they can be of any type and are able to carry a custom payload along with them. They are currently supported in all major browsers (even Safari!), so it is generally safe to use them.

How do they work?

Communicating between Dart and JavaScript via events is easy. You might think the two worlds would fight like cats and dogs, but in these modern times, diversity training has led to harmony between the species.

Send a "dog" event from Dart to JavaScript

To try it out, let's imagine your Dart code needs to send out some data into the cold, dark world of JavaScript. Dart is going to "put out the dog," as it were.

JavaScript

Here is how your JS code might listen for such an event:

index.html

<script type="application/javascript">
  document.addEventListener("dog", function(event) {
    console.log(JSON.stringify(event.detail));
  });
</script>

Pretty typical. Using the standard addEventListener() method on the document object, JS starts listening for "dog" events. When it gets one, it will log the contents of the detail field, which will contain the custom payload.

Dart

Somewhere in your Dart code, you need to construct a "dog" event and dispatch it, like so:

main.dart (or wherever)

import 'dart:html';

void dispatchDogEvent() {
  CustomEvent event = new CustomEvent("dog", detail: {
    "name": "Bowser",
    "hazcheeseburger": false
  });

  document.dispatchEvent(event);
}

Obviously, a button or something needs to execute that function, but that's your problem. When it runs, it creates a new CustomEvent, attaches a Map with some details, and then uses the document object to dispatch the event. Note that a Dart program gets access to document by importing dart:html. It is important to recognize that dogs, being inferior to cats, do not "haz cheeseburger."

If you've set this up correctly, you should see the detail content show up in your browser console when the "dog" event is dispatched, placed there by the JavaScript code.

Send a "cat" event from JavaScript to Dart

Now let's see how we can send messages the opposite direction.

Dart

In order to "let in the cat," your Dart code will need to listen for "cat" events. This is the only tricky part, since Dart's document object does not have an onCat stream to listen to (for some reason).

main.dart (or wherever)

document.on["cat"].listen((Event event) {
  print((event as CustomEvent).detail);
});

The document.on property lets you use the [] operator to specify a custom event type to listen for, "cat" in this case. The required signature of the handler is EventListener(Event event), meaning a standard Event object is expected. Since we know JavaScript is going to send a CustomEvent, we go ahead and typecast event as a CustomEvent in order to keep the Dart analyzer happy about trying to access the detail property. Plain old Events do not have a detail property.

When the event comes in, we print the contents of detail to the browser console.

JavaScript

Now that Dart is listening for the plaintive meow of a "cat" event, you need to dispatch said "cat" event from the JS wilderness:

index.html

<script type="application/javascript">
  function dispatchCatEvent() {
    var event = new CustomEvent("cat", {
      detail: {
        name: "Hazel",
        hazcheeseburger: true
      }
    });

    document.dispatchEvent(event);
  }
</script>

Once again, you will need to hook up a button or something to execute this function, but when it executes, it will do essentially the same thing the Dart code did to whip off the "dog" event. The event is created, the detail field is filled out, and the event is dispatched at the document level.

Conclusion

With these basic mechanics, you can pass whatever you need between the worlds of Dart and JavaScript, and you can save yourself the hassle of writing Dart wrappers with the js package. That said, you should definitely write the wrapper for anything too complicated, or to couch a JS library with that "Darty" feel, which will make your Dart code much nicer in the end. Using events is great if you're in a hurry, or if you just need to use a JS library temporarily for a demo.

Monty Rasmussen

A web application developer with 20+ years programming experience, Dart evangelist, and certified Web Technologies GDE (Google Developer Expert).

Utah, USA

Subscribe to Dart Academy

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!