Dart: a New Web Programming Experience
JavaScript has had a long-standing monopoly on client-side Web programming. It has a tremendously large user base, and countless libraries have been written in it. Surely it is the perfect language with no flaws at all! Unfortunately, that is simply not the case. JavaScript is not without its problems, and there exists a large number of libraries and "trans-pilers" that attempt to work around JavaScript's more quirky behaviors. JQuery, Coffeescript, Objective-J and RubyJS are examples of how people have been trying to make JavaScript better. However, a new contender is throwing its hat into the ring in a big way.
In comes Google Dart, a new JavaScript replacement language. Dart is a ground-up re-imagining of what JavaScript should be. It requires its own runtime environment (which Google makes available for free under the three-clause BSD license) and has its own syntax and libraries. Dart is an object-orientated language with a heavy Java-like feel, but it maintains many of the loved JavaScript paradigms like first-class functions.
So, why have I chosen to use Dart? Good question! I have chosen Dart because it is a clean break from JavaScript, and it has the object-orientated programming style I have come to know and love. Because I have a Java background, the learning curve didn't seem as steep with Dart as it does with JavaScript. Also, because Dart is so new, it gives me a chance to become an early adopter of the language and watch its evolution.
Installing Dart
Before you can program with Dart, you need to grab a copy from https://dartlang.org. I chose to install only the SDK; however, there is an option to grab the full Dart integrated development environment with the SDK included. Eclipse users will feel right at home with the IDE, because it is based on Eclipse components.
To install the SDK, I just unzipped the files and copied the whole directory to $HOME/bin. Then I modified my path variable to look in the folder I created:
PATH=$PATH:$HOME/bin/dart-sdk/bin
Now I can run dart, dart2js and dartdoc from anywhere.
Language Features
The core Dart language is pretty straightforward. The basic data types available are var (stores any object), num (stores any number type), int, double, String, bool, List (arrays) and Map (associative array). All of these data types are declared in the dart:core library. dart:core is always available and does not need to be imported. Functions also can be considered a data type, because Dart treats them as first-class objects. You can assign functions to variables and pass them as parameters to other functions or write anonymous functions in-line.
For flow control, you have the "usual" if, else if, else, for, while and do-while loops, break, continue, switch and assert. Exceptions are handled through try-catch blocks.
Dart has a lot of support for object-oriented programming. Classes are defined with the class keyword. Every object is an instance of some class, and all classes descend from the Object type. Dart allows only for single inheritance. The extends keyword is used to inherit from a parent class other than Object. Abstract classes can be used to define an interface with some default implementation. They cannot be instantiated directly, but can make use of factory constructors to create the appearance of direct instantiation. Abstract classes are defined with the abstract modifier in front of the class declaration.
Standard Library
Dart ships with an impressive standard library. I use a few of the libraries and classes in my examples here, so it will be helpful to have an idea of what they can do ahead of time. I can't cover all of them, but I cover the ones I personally find most useful.
As I said earlier, dart:core defines all of the core data types that are available. That's not all though! dart:core also contains the regular expression classes that are an invaluable addition to any standard library. Dart uses the same regular expression syntax as JavaScript.
dart:io provides classes that let your program reach out to the world. The File and Directory classes can be used to interact with the local filesystem. The File class also will allow you to open input and output streams on the specific file. If you want to write cross-platform code and allow users to specify the path of a file native to their operating system, the Path class provides a really nice Path.fromNative(String path) constructor that will convert Windows and UNIX paths to their Dart counterparts. Also included in this library are the Socket and ServerSocket classes that can be used for traditional network communications. The HttpServer class allows you to program a Web server quickly. This is great if you want to add a custom rest API to your application. dart:io can be imported and used only on server-side applications, so don't try to use it in your browser apps!
dart:html contains all of the classes necessary to interact with a client browser's document object model. This library is required to write any client-side code that runs in a browser. The library defines two static methods: Element query(String selector) and List<Element> queryAll(String selector). These methods allow you to grab HTML5 elements from the browser's DOM using cascading-stylesheet selectors. (I show an example of this later.)
dart:math, dart:json and dart:crypto provide helpers that are hard to live without. dart:math provides all of the static math methods that programmers have come to expect. dart:json provides the JSON helper class. It has only three static methods: parse(String json), which returns a Map containing the parsed document; String stringify(Object object) and void printOn(Object object, StringBuffer output) can be used to serialize an object into JSON. Any object can be made serializable by implementing a toJson() method. dart:crypto has helpers for performing md5, sha1 and sha256 hashing. There also is a CryptoUtils class with methods for converting bytes to hex and bytes to base64.
Server-Side Programming
Let's jump into Dart by looking at some server-side programming:
import 'dart:io';
void main(){
String fileName = './test.txt';
File file = new File(fileName);
var out = file.openOutputStream();
out.writeString("Hello, Dart!\n");
out.close();
}
Does it look pretty familiar? It's not much different from a Java program at this point. You start by importing the dart:io library. This gives you access to the File and OutputStream classes. Next, you declare a main method. Just like Java and C, main acts as the entry point for all programs. The File object is used to hold a reference to a single file on the filesystem. In order to write to this file, you open the file's output stream. This method will create the file if it does not exist or clears the contents of the file if it does. It returns an OutputStream object that then can be used to send data to the file. You write a single string and close the OutputStream.
To run this program, save it to a file called first.dart. Then use the Dart runtime environment provided with the SDK:
$ dart first.dart
When your program is done, you should see a file called test.txt in the same directory. Open it, and you will see your text.
What's interesting about Dart is that all I/O is event-based. This is much in the same style as Node.js. Every time you call a method that performs I/O, it is added to the event queue and the method returns immediately. Almost every single I/O method takes in a callback function or returns a Future object. The reason for this design choice is for scalability. Because Dart runs your code in a single thread, non-blocking asynchronous I/O calls are the only way to allow the software to scale to hundreds or even thousands of users.
Listing 1. wunder.dart
import 'dart:io';
import 'dart:uri';
import 'dart:json';
void main(){
List jsonData = [];
String apiKey = "";
String zipcode = "";
//Read the user supplied data form the options
//object
try {
apiKey = new Options().arguments[0];
zipcode = new Options().arguments[1];
} on RangeError {
print("Please supply an API key and zipcode!");
print("dart wunder.dart <apiKey> <zipCode>");
exit(1);
}
//Build the URI we are going to request data from
Uri uri = new Uri("https://api.wunderground.com/"
"api/${apiKey}/conditions/q/${zipcode}.json");
HttpClient client = new HttpClient();
HttpClientConnection connection =
client.getUrl(uri);
connection.onResponse =
(HttpClientResponse response) {
//Our client has a response, open an input
//stream to read it
InputStream stream = response.inputStream;
stream.onData = () {
//The input stream has data to read,
//read it and add it to our list
jsonData.addAll(stream.read());
};
stream.onClosed = () {
//response and print the location and temp.
try {
Map jsonDocument =
JSON.parse(new String.fromCharCodes(jsonData));
if (jsonDocument["response"].containsKey("error")){
throw jsonDocument["response"]["error"]["description"];
}
String temp =
↪jsonDocument["current_observation"]["temperature_string"];
String location = jsonDocument["current_observation"]
["display_location"]["full"];
print('The temperature for $location is $temp');
} catch(e) {
print("Error: $e");
exit(2);
}
};
//Register the error handler for the InputStream
stream.onError = () {
print("Stream Failure!");
exit(3);
};
};
//Register the error handler for the HttpClientConnection
connection.onError = (e){
print("Http error (check api key)");
print("$e");
exit(4);
};
}
In Listing 1, you can see this evented I/O style put to work with the
HttpClientConnection
object returned by the
HttpClient.getUrl(Uri url)
method. This object is working in the background waiting for a response
from the HTTP server. In order to know when the response has been received,
you must register an onResponse(HttpClientResponse
response)
callback method.
I created an anonymous method to handle this. Also notice that toward the
bottom of the program, I register an onError()
callback
as well. Don't worry;
all of the callbacks are registered before the HTTP transaction begins.
Once the onResponse()
callback is executed, I pull the InputStream object
out of the response to begin reading data. I register the
onData()
,
onClosed()
and onError()
callbacks to handle the different states the
InputStream can be in. onData()
simply reads bytes off the stream and
appends them to the jsonData list object. onData()
is guaranteed to be called as
long as there is data to read. Once the stream has hit "end of
file",
onClosed()
is executed. At this point, I know all of the data from the
HttpRequest has been transferred and read, so I can use the JSON helper class
to parse the response into a Map object and print the final result to the user.
This is where the program actually exits from if everything was successful. If
there was an error in the InputStream, then the
onError()
callback would have
been called, and the program would have exited from there.
To run this program, call it with the Dart runtime environment. You will need to register for an API key from Weather Underground (https://www.wunderground.com/weather/api). Don't worry; it's completely free. Once you have your API key, you can check the current temperature for any US zip code:
$ dart wunder.dart ec7....93b 10001
The temperature for New York, NY is 57.2 F (14.0 C)
Client-Side Dart
Now that you've seen what Dart can do on the server side, let's take a look at what it really was designed for, the client side. Dart excels at programming large-scale browser applications. Unfortunately, space constraints prevent me from showing a truly large application. Instead, I cover a not-so-large but very cool application using the HTML5 Canvas object. Let's use Dart for finger painting.
Listing 2. fingerpaint.dart
library fingerpaint;
import 'dart:html';
class DrawSurface {
String _color = "black";
int _lineThickness = 1;
CanvasElement _canvas;
bool _drawing = false;
var _context;
DrawSurface(CanvasElement canvas) {
_canvas = canvas;
_context = _canvas.context2d;
_canvas.on.mouseDown.add((Event e) => _onMouseDown(e));
_canvas.on.mouseUp.add((Event e) => _onMouseUp(e));
_canvas.on.mouseMove.add((Event e) => _onMouseMove(e));
_canvas.on.mouseOut.add((Event e) => _onMouseUp(e));
}
set lineThickness(int lineThickness) {
_lineThickness = lineThickness;
_context.lineWidth = _lineThickness;
}
set color(String color) {
_color = color;
_context.fillStyle = _color;
_context.strokeStyle = _color;
}
int get lineThickness => _lineThickness;
int get color => _color;
void incrementLineThickness(int amount){
_lineThickness += amount;
_context.lineWidth = _lineThickness;
}
String getPNGImageUrl() {
return _canvas.toDataUrl('image/png');
}
_onMouseDown(Event e){
_context.beginPath();
_context.moveTo(e.offsetX, e.offsetY);
_drawing = true;
}
_onMouseUp(Event e){
_context.closePath();
_drawing = false;
}
_onMouseMove(Event e){
if (_drawing == true){
_drawOnCanvas(e.offsetX, e.offsetY);
}
}
_drawOnCanvas(int x, int y){
_context.lineTo(x, y);
_context.stroke();
}
}
void main() {
CanvasElement canvas = query("#draw-surface");
DrawSurface ds = new DrawSurface(canvas);
List<Element> buttons = queryAll("#colors input");
for (Element e in buttons){
e.on.click.add((Event eve) {
ds.color = e.id;
});
}
var sizeDisplay = query("#currentsize");
sizeDisplay.text = ds.lineThickness.toString();
query("#sizeup").on.click.add((Event e) {
ds.incrementLineThickness(1);
sizeDisplay.text = ds.lineThickness.toString();
});
query("#sizedown").on.click.add((Event e) {
ds.incrementLineThickness(-1);
sizeDisplay.text = ds.lineThickness.toString();
});
query("#save").on.click.add((Event e) {
String url = ds.getPNGImageUrl();
window.open(url, "save");
});
}
Listing 3. fingerpaint.html
<!DOCTYPE html>
<html>
<head>
<h3>Finger Paint</h3>
<link rel="stylesheet" href="fingerpaint.css" />
</head>
<body>
<h1>Finger Paint</h1>
<div>
<canvas id="draw-surface" width="800px" height="600px">
</canvas>
</div>
<div id="colors">
<input id="white" type="button"></input>
<input id="red" type="button"></input>
<input id="black" type="button"></input>
<input id="blue" type="button"></input>
<input id="green" type="button"></input>
<input id="purple" type="button"></input>
<input id="yellow" type="button"></input>
<input id="orange" type="button"></input>
<input id="brown" type="button"></input>
</div>
<div>
<input id="sizeup" type="button" value="Increase width">
</input>
<input id="sizedown" type="button" value="Decrease width">
</input>
<span id="currentsize"></span>
</div>
<div>
<input id="save" type="button" value="Save"></input>
</div>
<script type="application/dart" src="fingerpaint.dart">
</script>
<script type="application/javascript"
src="https://dart.googlecode.com/svn/trunk/dart/client/dart.js">
</script>
</body>
</html>
Listing 4. fingerpaint.css
#draw-surface {
border-style: solid;
border-width: 2px;
}
#white {
background-color: white;
width: 30px;
}
#red {
background-color: red;
width: 30px;
}
#black {
background-color: black;
width: 30px;
}
#blue {
background-color: blue;
width: 30px;
}
#green {
background-color: green;
width: 30px;
}
#purple {
background-color: purple;
width: 30px;
}
#yellow {
background-color: yellow;
width: 30px;
}
#orange {
background-color: orange;
width: 30px;
}
#brown {
background-color: brown;
width: 30px;
}
In our simple finger-painting application, there will be buttons for each color that is available to users, as well as buttons to increment and decrement the thickness of their strokes. What good is painting a masterpiece if you can't save it and share it with the world? So, let's make a save button that will convert the canvas to a PNG image.
First, let's take a look at the markup for this project. In Listing 3, you
can see there is an HTML5 Web page that contains a canvas element called
draw-surface
. This is where the work of art will be made. Below the
canvas are the control buttons that allow users to select colors and
stroke width, and the save button. The last part of the document is
the most interesting part. There are two script elements. The first is
a script tag with the type attribute set to "application/dart". This
script type is currently recognized only by a fork of Chromium called
Dartium (https://www.dartlang.org/dartium). The second is a JavaScript
bootstrap file that is required to start the Dart VM in Dartium. It also
has a special second function that I talk about later.
Now let's take a look at the application itself. At the top of Listing 2, I start the program by importing dart:html. All client-side applications must import this library to have access to the DOM. Next, I create a class called DrawSurface that will act as a container class for the canvas object. The constructor takes in a CanvasElement and grabs its 2-D-rendering context. It also registers all of the callbacks to handle mouse movements on the draw surface. When the user presses down on the mouse, somewhere on the draw surface canvas I begin a draw path.
As the user moves the mouse around with the button pressed down, I add line segments to the drawing. When the user releases the mouse or moves out of the canvas element, I close the drawing path.
I implemented getters and setters for the color and lineThickness
attributes. In the setter methods, I make sure to update the rendering
context on any change. I also add two methods incrementLineThickness(int
amount)
that will allow the user to adjust the lineThickness by some
amount, instead of just setting it, and
getPNGImageUrl()
to expose the
canvas element's toDataUrl()
method. This method will allow the save
button to function.
In main, I use the static query(String selector)
function to get the canvas
element by its ID. The query function takes any CSS selector and returns
an Element object that matches it. If there is more than one element that
you want to access on a page, you can use the queryAll(String
selector)
function
that will return a List<Element> object. I use this function to gather up all
of the color buttons at once and register onClick() events to set the current
color to its respective ID value.
Finally, I register the callbacks for the size-up and size-down buttons that change the thickness of the line by 1. I also register the callback for the save button to grab the PNG data URL from the canvas and open it in a new window. The user then can save the image by right-clicking on it and choosing "save image as".
Running the Application
If you have chosen to download and install the full Dart editor package, you already have Dartium installed. If, like me, you chose to install only the SDK, you need to grab a copy of Dartium from https://www.dartlang.org/dartium. To install it, I just unzipped the file and created a symlink in my $HOME/bin directory to the chrome program that I extracted:
$ ln -s /path/to/unzipped/folder/chrome dartium
Once it is installed, you can run this application from the command line with the command:
$ dartium fingerpaint.html
Note:
Dartium is an experimental browser, so it should be used only for developing Dart applications locally. Don't use it as your normal browser! There might be security exploits or stability issues that have not been discovered yet.
Figure 1. The finger-paint application running on Dartium, a special fork of Chromium.
Running on Other Browsers
It's okay if you don't have Dartium. Remember that bootstrap script line in
fingerpain.html? Aside from starting the Dart VM in Dartium, it also
will fall back to a JavaScript application if Dart is not supported. The JavaScript
application must have the same name as the Dart application with the extension
.dart.js. The Dart SDK comes with a nifty program called dart2js that will convert
a Dart browser application into a JavaScript application for use in any
browser.
To convert this application, you can run dart2js
on
fingerpaint.dart:
$ dart2js -ofingerpaint.dart.js fingerpaint.dart
When this is done, you will see several new files, including fingerpaint.dart.js.
Now the application will work in any browser that can handle JavaScript. I personally recommend using Dartium for application development and then converting the application to JavaScript for release.
Figure 2. After being converted to JavaScript, the finger-paint application can now run on any browser. Here it is running on Firefox.
State of Dart
I would love to tell you that the community has welcomed Dart with open arms, but that's simply not the case. The people in charge are afraid of Dart becoming the next VBScript and hurting the open Web. So far, Microsoft, Mozilla and Apple have rejected the idea of embedding a Dart runtime into their browsers. As Dart matures and gains popularity, I hope to see this stance reverse, but for now, dart2js is the only way to get Dart projects on-line for all to use.
Conclusion
Dart is a fantastic language that presents an entirely new approach to writing large-scale, client-side, object-oriented applications. I have enjoyed working with it, and I hope you will too. The potential of this language is limitless, and I hope to see wide-spread adoption of it in the future.
Resources
Dart Home Page: https://www.dartlang.org
Dart API Reference: https://api.dartlang.org/docs/bleeding_edge/index.html
Dartium: https://www.dartlang.org/dartium
Dart Source Code and Bug Tracking: https://code.google.com/p/dart
HTML5 for Publishers: https://shop.oreilly.com/product/0636920022473.do