Book Image

DART Essentials

Book Image

DART Essentials

Overview of this book

Table of Contents (16 chapters)
Dart Essentials
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Installing the Dart SDK


Let's start with obtaining the Dart SDK, which already contains all we need. This includes Dart Editor, the standalone Dart VM, and Dartium browser.

We can download everything in a single package right from Dart's home page at https://www.dartlang.org/, which detects your platform automatically for you and gives you the correct package for download.

Dart Editor

Dart Editor is built on the Eclipse platform, so you might find its look and feel already familiar.

We're going to spend a lot of time in this editor, so feel free to take a look at it and try the example projects. This editor also contains a debugger that works out of the box with Dartium, so we don't need to configure anything.

Note

Although the browser that comes with the Dart SDK package is actually called Chromium, we call it Dartium because it runs Dart VM inside. Even in Dart Editor, they call it Dartium, so don't be confused when you hit Run in Dartium and it opens the Chromium browser.

There's also a Dart plugin for IDEs developed by JetBrains, specifically, WebStorm, PHPStorm, and IntelliJ IDEA.

We're going to use Dart Editor in this book. However, the Dart team announced that they're planning to move to JetBrains IDEs and abandon Dart Editor probably in late 2015.

Writing a greeting for all Dartisans

Our first Dart app will randomly generate five colors in the <ul> element; let's enter a name into the <input> field and greet you with a selected color inside <h1>.

The final working app with some CSS will look like this:

We'll set off by creating a new project by clicking on Create an application in the Welcome window or by going to File | New Project. There are a few templates for the most common use cases. We'll go with Uber Simple Web Application because we need just the most basic app structure right now.

Our project should look like this:

For us, the most important files are pubspec.yaml, index.html, and main.dart. We can take a look at them one by one.

pubspec.yaml

This is a file that defines our project and its dependencies. By default, it contains only very basic information and one dependency called browser, which we'll use in index.html. If you're using Dart Editor, you can add more dependencies right in the editor's GUI, and you don't need to modify the file as text. Later in this chapter, we'll add more statements that control, for example, the dart2js compiler. For now, we can leave it as it is:

name: 'my_first_dart_app'
version: 0.0.1
description: An absolute bare-bones web app.
environment:
  sdk: '>=1.0.0 <2.0.0'
dependencies:
  browser: any

Note that dependencies in Dart projects don't necessarily need to contain any Dart code. For example, browser contains only two JavaScript files.

Tip

Downloading the example code

You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

When you modify pubspec.yaml, Dart Editor downloads new dependencies automatically for you.

We used any to specify the version for the browser package, which means that the newest available version will be used. There are more ways to define allowed versions; for a more detailed description, refer to https://www.dartlang.org/tools/pub/dependencies.html. We'll use this option to set specific versions when working with polymer.dart and AngularDart in Chapter 5, Web Components and polymer.dart, and Chapter 6, AngularDart.

index.html

This is going to be just a simple HTML page:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>my_first_dart_app</title>
  <link rel="stylesheet" href="styles/main.css">
</head>

<body>
  <ul id="color-select"></ul>
  <input type="text" id="name">
  <h1 id="output"></h1>

  <script type="application/dart" src="main.dart"></script>
  <script data-pub-inline src="packages/browser/dart.js"></script>
</body>
</html>

Look at the last two <script> tags. The first one links the main.dart file, which is an entry point for our app. No matter how many files your Dart project has, you always link just the one that contains the main() function, as we'll see in a moment.

The browser package contains a script called dart.js that you'll probably use in all the Dart web projects you'll make. When you compile the Dart code to JavaScript, it creates a new file called main.dart.js with all your Dart code compiled to JavaScript. The dart.js script automatically checks whether your browser supports Dart natively and if it doesn't, it replaces main.dart with dart.main.js. Therefore, you can develop, test, and deploy projects in both Dart and JavaScript without even touching the HTML code. The data-pub-inline attribute tells the compiler to handle this element in a special way. We'll talk about this later in this chapter.

In this file, we created three elements (<ul>, <h1>, and <input>) that will be controlled from Dart.

Note

We're omitting the CSS file here and in most of the book as well, unless there's something particularly interesting and related to the topic. You can download all the source code for this book from the Packt Publishing website.

The main() function

The real fun starts here. The entry point to the app is the top-level main() function and as Dart is a class-based language, we'll create a class called GreetingsManager that will update the text and its color.

We can jump right into the code to get a quick glimpse of what Dart code looks like. Try to read the code and guess what you think it does. I believe that even without any knowledge of Dart, you'll be able to tell how it works.

// web/main.dart
import 'dart:html';
import 'dart:math';

class GreetingsManager {
  Random _rnd; // Random number generator.
  HtmlElement h1 = querySelector('#output');
  
  GreetingsManager() {
    _rnd = new Random();
  }
  
  // Generate a random color.
  String getRandomColor() {
    // Randomly generate numbers as hex strings with padding.
    return _rnd.nextInt(256).toRadixString(16).padLeft(2, '0') +
           _rnd.nextInt(256).toRadixString(16).padLeft(2, '0') +
           _rnd.nextInt(256).toRadixString(16).padLeft(2, '0');
  }
  
  // Generate a list of strings where each item represents one
  // color. [List<T>.generate()] is a named constructor that
  // calls the callback function n-times.
  List<String> generateColors(int total) {
    return new List<String>.generate(total, (int i) => 
        getRandomColor());
  }
  
  void setTextColor(String color) {
    SpanElement span = h1.querySelector('span');
    if (span != null) {
      span.style.color = color;
    }
  }
  
  void setText(String name) {
    // Override its inner HTML.
    h1.innerHtml = name.trim().isEmpty
        ? ""
        : "Hello, <span>$name</span>!";
  }
}

void main() {
  var gm = new GreetingsManager();
  
  // Target container for our colors.
  UListElement ul = querySelector('#color-select');
  
  // Iterate all colors and create <li> element for each of them.
  gm.generateColors(5).forEach((String color) {
    LIElement elm = new LIElement();
    // Set the background color.
    elm.style.backgroundColor = "#${color}";
    
    // Bind a listener to the onClick event.
    elm.onClick.listen((e) {
      gm.setTextColor(elm.style.backgroundColor);
      ul.querySelectorAll('li').classes.remove('selected');
      elm.classes.add('selected');
    });
    // Add HTML element to the <ul>.
    ul.append(elm);
  });
  
  InputElement nameInput = querySelector('#name');
  // Bind a listener to the onKeyUp event.
  nameInput.onKeyUp.listen((Event e) {
    String name = (e.target as InputElement).value;
    // print() outputs a variable as a string to
    // the environment's standard output.
    print(name);
    
    gm.setText(name);
    LIElement selected = ul.querySelector('li.selected');

    if (selected != null) {
      gm.setTextColor(selected.style.backgroundColor);
    }
  });
}

There are a couple of important things to pay attention to in more detail.

The code starts with import statements. These tell Dart to import (as you've probably guessed) another file or a package. Starting with dart:, it means that this is a built-in package that's shipped with the Dart SDK. Later, we'll also use package:, which is a third-party dependency, and at the end of the book, we'll meet dart-ext:, which is a native extension of the Dart VM. Of course, we'll use import to import files from our own projects as well.

All web apps will probably import the dart:html library because it makes top-level variables, document and window, and methods, such as querySelector() or querySelectorAll(), available.

Then, we declared a GreetingsManager class. If we didn't write a constructor for it, Dart would use the so-called implicit constructor by default. There's also a named constructor that we'll meet later.

All types in Dart are optional, but we're going to use them a lot in this book. It's not only easier to read; it also helps you spot possible errors and in some situations improves performance when compiled to JavaScript. If you don't care what type a variable is, you can declare it as var like in JavaScript, and the Dart static check won't bother you with it. There's also a special type dynamic, which is used underneath every time you don't specify a variable type, but in most situations, you're just fine with declaring variables with var. The dynamic keyword makes more sense when used as a generic keyword for List and Map classes, as we'll see later.

Every method in Dart has a return type, although you can use dynamic and void as well (omitting return type stands for void). Void means that this method doesn't return any value. Note that void doesn't have the same meaning as null. Null means zero or an undefined pointer, which is a valid value, while void means nothing in this context.

Collections such as lists are defined in Dart's API as List<E>. This means that the List class is generic and you can tell it what type of objects it may contain. In our example, we defined List<String>, which tells the type checker that all items in this collection will be instances of String. This notation of generics is very common in Java and C++, but as Dart types are optional, it actually doesn't restrict you from adding instances of other classes to the list, as you might expect. Using generics properly in Dart is a matter of a good programming style because it helps you and other developers understand your intentions. By the way, this is another situation where you can use the dynamic type if your list can contain any objects. As types are optional in Dart, declaring List<dynamic> is equal to not using the <E> notation at all. We'll see this in use later.

Note

Notice the way we access HTML element properties and how we can change their CSS style with elm.style.backgroundColor. Adding, removing, or toggling the classes of an element is very easy because the classes property is an instance of CssClassSet, which has many useful methods, and we can use elm.classes.add('selected'), for example. With Dart, most of the time, you don't need to access element attributes directly.

To remove element's classes, we can use querySelectorAll('li').classes.remove('selected'), where querySelectorAll() returns a collection of elements and performs .classes.remove('selected') on each of them. This approach is well known from jQuery, and it saves you a lot of writing the same code over and over again.

Then, we have the main() function, which is an entry point to our app. Dart VM parses your code before running it, so it doesn't matter where in the file you put it (it still has to be a top-level function). There, we call the GreetingsManager.generateColors()method and chain it with the forEach() method. All iterable collections implement the forEach() method, which calls a callback for each item in the collection. Creating an anonymous function has two possible formats. A short one-liner with just one expression, which we used in generateColors(), is as follows:

(int i) => getRandomColor()

This takes one parameter, calls getRandomColor(), and returns its result. This notation is equivalent to the second and is probably a more common format:

(int i) {
  return getRandomColor();
}

There is also another way we could iterate the entire collection:

for (String str in gm.generateColors()) {
  /* ... */
}

Listening to events is done via Dart streams, which is basically a way of handling asynchronous calls. For the most part, we can use them just like events in JavaScript. In our app, we're listening to the onKeyUp and onClick events. We "listen" to them by calling the listen() method that takes a callback function as an argument.

Dart lets you use type casting in a similar way to C/C++ with the variable as type notation (where as is a keyword). This is useful when the static checker doesn't know what type of object is stored in a variable but you know what you're expecting. We used it like (e.target as InputElement).value because we know that e.target is going to be of the InputElement type but e.target is a general dynamic property that doesn't have the value property itself. Of course, we could omit the typecast completely and just ignore the warning shown by type checker, but that's not a very good practice.

The last interesting thing is string interpolation. We can concatenate String objects in Dart with a plus sign +, but this tends to be confusing when used too much. Therefore, we can insert variables right into the string and leave Dart to do the work for us. In our app, we used it like this:

"Hello, <span>$name</span>!"

The $variable notations are replaced with a string representation of their variables. Interpolation can be used for expressions as well with ${expr}, for example, ${42.toString()}.

Running and debugging code in Dartium

Our code is done for now, so we can run the app in the browser. First, we'll test it in Dartium because it's able to run the native Dart code.

You can right-click on index.html and select Run in Dartium, or you can click on the white arrow in the green circle icon in the toolbar. This opens the Dartium browser and you should see the same page with five random colors just like what's shown at the beginning of this chapter. Open Developer Tools and see whether the browser really uses our main.dart file.

Debugging Dart scripts is very easy because we can use exactly the same tools used for debugging JavaScript. With Developer Tools, we can only debug web apps and not console apps.

Another way to debug both web and console apps is right in Dart Editor. Double-click on any line number to place a breakpoint and Dart VM will pause when it reaches that line.

Compiling Dart to JavaScript

From a practical point of view, Dart would be useless if we couldn't run it in today's browsers. This is why the Dart SDK comes with Dart to JavaScript compiler called dart2js. You can run it right in Dart Editor; in the top menu bar, navigate to Tools | Pub Build or right-click on index.html and select Run as JavaScript. This launches the compilation process and outputs some info:

--- 3:12:49 AM Running pub build on ./my_first_dart_app ... ---
Loading source assets...
Building my_first_dart_app...
[Info from Dart2JS]:
Compiling my_first_dart_app|web/main.dart...
[Info from Dart2JS]:
Took 0:00:07.294964 to compile my_first_dart_app|web/main.dart.
Built 224 files to "build".

As you can see, the compiler had to process 224 files in total and generated one large main.dart.js file, which we already mentioned earlier in this chapter. The compiler created a new directory named build and put there everything you need to run the app in both Dart and JavaScript.

You can run the compiler in CLI by navigating to your project's directory and running:

$ pub build

This command fetches all the dependencies, compiles your code with dart2js, and eventually processes it with transformers.

A very obvious question is how big the generated JavaScript file is. The Dart compiler removes parts of the code that your app isn't using and in Dart SDK 1.9, the final script is 290 KB. That's not bad but especially for mobile connections, it's still quite a lot. Luckily for us, we can tell dart2js to minify the final JavaScript by adding a new statement at the end of pubspec.yaml (you have to open the file as a text file or switch to the Source tab at the bottom of Dart Editor's window):

transformers:
- $dart2js:
    minify: true

When we run the compilation again, it will generate a 125 KB file. That's much better; keep in mind that this also includes all our project's code. For comparison, jQuery 2.1 that doesn't contain legacy code for older browsers and without our app code has 84 KB. With gzip compression enabled on your server, the difference is even smaller: 37 KB for dart2js versus 30 KB for jQuery. With the latest Dart Editor 1.9, you can create minimized version right from the Tools menu; however, setting it specifically in pubspec.yaml is sometimes necessary when using Dart's pub tool in CLI (more about this in the next chapter).

There's still one more thing to optimize. Our index.html includes JavaScript called dart.js, which we've already talked about. The template that we used has a special attribute, data-pub-inline:

<script data-pub-inline src="packages/browser/dart.js"></script>

By default, it does nothing. Let's add a new dependency to our project called script_inliner and then update pubspec.yaml again with:

transformers:
- $dart2js:
    minify: true
- script_inliner

Then, run Pub Build again; script_inliner processes HTML files and inlines JavaScripts marked as data-pub-inline.