Dart
  • Get Started
    • Get Dart
    • Dart Tutorials
    • Technical Overview
  • Docs
    • Programmer's Guide
    • API Reference
    • Dart Cookbook (Beta)
    • Dart: Up and Running
    • More Books
    • Articles
    • FAQ
  • Tools
    • Dart Editor
    • Pub Package Manager
    • More Tools
  • Resources
    • Code Samples
    • Translations from Dart
    • Try Dart!
    • Presentations
    • Dartisans Videos and Podcast
    • Dart Tips Videos
    • Bugs and Feature Requests
  • Community
    • Contact Us
    • Contributor's Guide
    • More Resources
  • Tweet
On Air

Using JavaScript from Dart: The js Library

Written by Vijay Menon
September 2012

This article describes how to use the js Dart library, which allows Dart web apps to use JavaScript libraries. You can use the js library both in native Dart code (in Dartium) and in code that’s compiled to JavaScript (using dart2js) and executed in a modern browser.

The library is implemented in the GitHub project dart-lang/js-interop.

Contents

  1. Installing the library’s package
  2. Importing the library
  3. Getting the top-level context
  4. Creating JavaScript objects from Dart
    1. Instantiating classes
    2. Creating maps and arrays
  5. Calling Dart from JavaScript
  6. Managing proxy lifetimes
  7. Managing callback lifetimes
  8. Working with CSP restricted pages
  9. Examples
    1. Google Maps
    2. Google Charts
    3. Twitter
  10. For more information

Installing the library’s package

To install the library’s package using pub, put the following lines in your app’s pubspec.yaml file:

dependencies:
  js: any

Then use pub install:

$ pub install
Dependencies installed!

For details about using pub, see the pub documentation.

Importing the library

A Dart program can import the js library if it is running on the UI thread of the browser—that is, if the program imports dart:html.

To import the js library, use the following code:

import 'package:js/js.dart' as js;

Getting the top-level context

To use the js library, you must get a proxy to the top-level JavaScript context of your app’s page:

var context = js.context;

References to the context are automatically forwarded to JavaScript. For example, you can invoke the top-level alert function in JavaScript as follows:

js.context.alert('Hello from Dart via JavaScript.');

Note: Parameters and return values are intuitively passed by value for primitives and by reference for non-primitives. In the latter case, the references are automatically wrapped and unwrapped as proxies by the library.

Creating JavaScript objects from Dart

Your Dart code can instantiate JavaScript classes and create JavaScript maps and arrays.

Instantiating classes

Given a proxy to a JavaScript constructor, your Dart code can create JavaScript objects. For example, if the JavaScript Google Maps API is available, you can create a Map object like this:

var canvas = query('#map_canvas');
var googlemaps = js.context.google.maps;
var googlemap = new js.Proxy(googlemaps.Map, canvas);

This code instantiates a JavaScript google.maps.Map object onto the given canvas and returns a proxy to Dart. Note that googlemaps.Map is a proxy to the JavaScript constructor. The resulting googlemap is a proxy to the actual Google Map object in JavaScript.

Creating maps and arrays

Use the top-level map() and array() functions to create JavaScript maps and arrays from their Dart equivalents. For example, the following code creates a JavaScript map and returns a Dart proxy to it.

var options = js.map({ 'zoom': 9,
                       'mapTypeId': googlemaps.MapTypeId.ROADMAP,
                       'center': new js.Proxy(googlemaps.LatLng, 47.6097, -122.3331) });

Elements of a list or map are converted recursively. For example, a Dart list of Dart lists is converted to a JavaScript list of JavaScript lists. If a (top-level or nested) list or map contains a Dart object, that object is converted to a proxy on the JavaScript side. If a list or map contains a proxy to a JavaScript object, it is unwrapped to the underlying JavaScript object.

In the example above, options is a proxy to a JavaScript map with three keys—“zoom”, “mapTypeId”, and “center”. The “mapTypeId” key is mapped to the JavaScript constant googlemaps.MapTypeId.ROADMAP, and the “center” key is mapped to a JavaScript googlemaps.LatLng object.

Converting lists and maps is especially useful for configuring JavaScript objects from Dart. For example:

googlemap.setOptions(options);

Calling Dart from JavaScript

An important aspect of interoperating with existing JavaScript libraries is enabling Dart functions to be called from JavaScript. You can expose a Dart function to JavaScript by converting the function to a Callback object. For example:

js.context.handler = new js.Callback.once(display);

The preceding code defines a top-level handler function in JavaScript that forwards to the corresponding Dart callback. The Callback.once() constructor indicates that the Callback is executed only once. Its argument is forwarded to the display() function.

For example, the following Dart code makes a JSONP request to Twitter. The result is forwarded via the JavaScript handler to the Dart display() function.

var script = new ScriptElement();
script.src
 = 'http://search.twitter.com/search.json?q=dartlang&rpp=10&callback=handler';
document.body.nodes.add(script);

Managing proxy lifetimes

To avoid memory leaks, you must manage the lifetime of Proxy and Callback objects. To ease this task we use the concept of scopes.

By default, all proxies are locally scoped. Code must enter a scope to create or use proxies. When the code exits that scope, any proxies created within that scope are automatically released (and their underlying JavaScript or Dart objects may be later garbage collected). To execute Dart code within a scope, use scoped():

js.scoped(() {
  js.context.alert('Hello from Dart via JavaScript.');
});

This example creates a proxy to the alert() function and then invokes it. That proxy is released once the function passed to scoped() is complete.

If a proxy must live beyond its local scope, you can explicitly retain it using js.retain().

var googlemap;
js.scoped(() {
  var canvas = query('#map_canvas');
  var googlemaps = js.context.google.maps;
  googlemap = new js.Proxy(googlemaps.Map, canvas);
  js.retain(googlemap);
});

In the preceding example, only the googlemap proxy is valid once the scope is exited. All other proxies are released. The underlying JavaScript map cannot be garbage collected until its proxy is explicitly released, using js.release().

js.release(googlemap);  // Allow googlemap to be garbage collected.

Starting from version 0.0.19 scoped() can be avoid. All proxies created will be scoped and automatically released at the end of the current event loop. retain and release are still required for proxies that span multiple events.

Managing callback lifetimes

Similarly, Callback objects to Dart functions must be released so that the Dart functions can be garbage collected.

In general, most callbacks are executed only once as in our Twitter-JSONP example from earlier:

js.context.handler = new js.Callback.once(display);

The Callback.once() constructor conveniently handles this common case, in which callbacks are automatically disposed of after a single invocation.

If your callback is executed multiple times, use the Callback.many() constructor instead.

var callback = new js.Callback.many(display);
js.context.handler = callback;

In this case, you can explicitly dispose of the callback:

callback.dispose();  // Allow the callback function to be garbage collected.

If a callback is invoked from JavaScript after it’s been disposed of, an exception is thrown because the backing closure may have been garbage collected.

Working with CSP restricted pages

To work with CSP pages (for example, when writing a Chrome App), you need to explicitly add the script packages/js/dart-interop.js to your html file. The JS-interop code normally adds it for you at runtime, but CSP does not allow runtime script injection.

Examples

The examples at http://dart-lang.github.com/js-interop/example/ use publicly available JavaScript APIs from Dart via the js library. The Dart version is usually a port of an existing JavaScript sample. All examples linked below run natively in Dartium or in Chrome via dart2js. Support for other browsers is still underway.

In general, the Dart ports of these JavaScript samples look remarkably like the original JavaScript. Lifetime management of proxies and callbacks is the primary added complexity during porting, but the additional code is relatively small.

The js library currently prints the number of live proxies (along with the total number ever allocated) each time the code exits a scope.

Proxy status :
 Dart objects Live : 1 (out of 4 ever allocated).
 JS objects Live : 4 (out of 32 ever allocated).

To avoid memory leaks, a well designed application should have a small, relatively constant number of live proxies.

Google Maps

The Google Maps sample uses both a directions API to communicate with the server and a maps API to render a map.

  • Original JavaScript sample
  • Dart version
  • Dart source

Google Charts

This demonstrates simple usage of the Google Charts API BubbleCharts.

  • Original JavaScript sample
  • Dart version
  • Dart code

Twitter

The Twitter example demonstrates simple usage of the Twitter search API via JSONP and the js library:

  • Dart version
  • Dart code

For more information

You can find out more from the GitHub project and the generated API documentation:

  • dart-lang/js-interop project
  • js API documentation

Popular on this site

  • Web UI
  • Performance
  • Language tour & library tour
  • Code samples
  • Tutorials & Code Lab
  • Cookbook

More resources

  • Try Dart!
  • Translations from Dart
  • Dart bugs and feature requests
  • Pub packages

Community

  • Mailing lists
  • G+ community
  • G+ announcement group
  • Stack Overflow

Dart is an open-source project with contributors from Google and elsewhere.

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the BSD License.

Terms of Service — Privacy Policy