Handling the DOM in Dart

Learning Dart

January 2014


Dart is the programming language developed by Google that offers a new level of simple versatility. Learn all the essentials of Dart web development in this brilliant tutorial that takes you from beginner to pro.

(For more resources related to this topic, see here.)

A Dart web application runs inside the browser (HTML) page that hosts the app; a single-page web app is more and more common. This page may already contain some HTML elements or nodes, such as <div> and <input>, and your Dart code will manipulate and change them, but it can also create new elements. The user interface may even be entirely built up through code. Besides that, Dart is responsible for implementing interactivity with the user (the handling of events, such as button-clicks) and the dynamic behavior of the program, for example, fetching data from a server and showing it on the screen. We explored some simple examples of these techniques. Compared to JavaScript, Dart has simplified the way in which code interacts with the collection of elements on a web page (called the DOM tree). This article teaches you this new method using a number of simple examples, culminating with a Ping Pong game. The following are the topics:

  • Finding elements and changing their attributes
  • Creating and removing elements
  • Handling events
  • Manipulating the style of page elements
  • Animating a game
  • Ping Pong using style(s)
  • How to draw on a canvas – Ping Pong revisited

Finding elements and changing their attributes

All web apps import the Dart library dart:html; this is a huge collection of functions and classes needed to program the DOM (look it up at api.dartlang.org). Let's discuss the base classes, which are as follows:

  • The Navigator class contains info about the browser running the app, such as the product (the name of the browser), its vendor, the MIME types supported by the installed plugins, and also the geolocation object.
  • Every browser window corresponds to an object of the Window class, which contains, amongst many others, a navigator object, the close, print, scroll and moveTo methods, and a whole bunch of event handlers, such as onLoad, onClick, onKeyUp, onMouseOver, onTouchStart, and onSubmit. Use an alert to get a pop-up message in the web page, such as in todo_v2.dart:

    window.onLoad.listen( (e) =>
    window.alert("I am at your disposal") );

  • If your browser has tabs, each tab opens in a separate window. From the Window class, you can access local storage or IndexedDB to store app data on the client
  • The Window object also contains an object document of the Document class, which corresponds to the HTML document. It is used to query for, create, and manipulate elements within the document. The document also has a list of stylesheets (objects of the StyleSheet class)—we will use this in our first version of the Ping Pong game.
  • Everything that appears on a web page can be represented by an object of the Node class; so, not only are tags and their attributes nodes, but also text, comments, and so on. The Document object in a Window class contains a List<Node> element of the nodes in the document tree (DOM) called childNodes.
  • The Element class, being a subclass of Node, represents web page elements (tags, such as <p>, <div>, and so on); it has subclasses, such as ButtonElement, InputElement, TableElement, and so on, each corresponding to a specific HTML tag, such as <button>, <input>, <table>, and so on. Every element can have embedded tags, so it contains a List<Element> element called children.

Let us make this more concrete by looking at todo_v2.dart, solely for didactic purposes—the HTML file contains an <input> tag with the id value task, and a <ul> tag with the id value list:

<div><input id="task" type="text" placeholder="What do you want to do?"/> <p id="para">Initial paragraph text</p> </div> <div id="btns"> <button class="backgr">Toggle background color of header</button> <button class="backgr">Change text of paragraph</button> <button class="backgr">Change text of placeholder in input
field and the background color of the buttons</button> </div> <div><ul id="list"/> </div>

In our Dart code, we declare the following objects representing them:

InputElement task; UListElement list;

The following list object contains objects of the LIElement class, which are made in addItem():

var newTask = new LIElement();

You can see the different elements and their layout in the following screenshot:

The screen of todo_v2

Finding elements

Now we must bind these objects to the corresponding HTML elements. For that, we use the top-level functions querySelector and querySelectorAll; for example, the InputElement task is bound to the <input> tag with the id value task using: task = querySelector('#task'); .

Both functions take a string (a CSS selector) that identifies the element, where the id value task will be preceded by #. CSS selectors are patterns that are used in .css files to select elements that you want to style. There are a number of them, but, generally, we only need a few basic selectors (for an overview visit http://www.w3schools.com/cssref/css_selectors.asp).

  • If the element has an id attribute with the value abc, use querySelector('#abc')
  • If the element has a class attribute with value abc, use querySelector('.abc')
  • To get a list of all elements with the tag <button>, use querySelectorAll('button')
  • To get a list of all text elements, use querySelectorAll('input[type="text"]') and all sorts of combinations of selectors; for example, querySelectorAll('#btns .backgr') will get a list of all elements with the backgr class that are inside a tag with the id value btns

These functions are defined on the document object of the web page, so in code you will also see document.querySelector() and document.querySelectorAll().

Changing the attributes of elements

All objects of the Element class have properties in common, such as classes, hidden, id, innerHtml, style, text, and title; specialized subclasses have additional properties, such as value for a ProgressElement method. Changing the value of a property in an element makes the browser re-render the page to show the changed user interface. Experiment with todo_v2.dart:

import 'dart:html'; InputElement task; UListElement list; Element header; List<ButtonElement> btns; main() { task = querySelector('#task'); list = querySelector('#list'); task.onChange.listen( (e) => addItem() ); // find the h2 header element: header = querySelector('.header'); (1) // find the buttons: btns = querySelectorAll('button'); (2) // attach event handler to 1st and 2nd buttons: btns[0].onClick.listen( (e) => changeColorHeader() ); (3) btns[1].onDoubleClick.listen( (e) => changeTextPara() ); (4) // another way to get the same buttons with class backgr: var btns2 = querySelectorAll('#btns .backgr'); (5) btns2[2].onMouseOver.listen( (e) => changePlaceHolder() );(6) btns2[2].onClick.listen((e) => changeBtnsBackColor() ); (7) addElements(); } changeColorHeader() => header.classes.toggle('header2'); (8) changeTextPara() => querySelector('#para').text =
"You changed my text!"; (9) changePlaceHolder() => task.placeholder =
'Come on, type something in!'; (10) changeBtnsBackColor() => btns.forEach( (b)
=> b.classes.add('btns_backgr')); (11) void addItem() { var newTask = new LIElement(); (12) newTask.text = task.value; (13) newTask.onClick.listen( (e) => newTask.remove()); task.value = ''; list.children.add(newTask); (14) } addElements() { var ch1 = new CheckboxInputElement(); (15) ch1.checked = true; document.body.children.add(ch1); (16) var par = new Element.tag('p'); (17) par.text = 'I am a newly created paragraph!'; document.body.children.add(par); var el = new Element.html('<div><h4><b>
A small divsection</b></h4></div>'); (18) document.body.children.add(el); var btn = new ButtonElement(); btn.text = 'Replace'; btn.onClick.listen(replacePar); document.body.children.add(btn); var btn2 = new ButtonElement(); btn2.text = 'Delete all list items'; btn2.onClick.listen( (e) => list.children.clear() ); (19) document.body.children.add(btn2); } replacePar(Event e) { var el2 = new Element.html('<div><h4><b>
I replaced this div!</b></h4></div>'); el.replaceWith(el2); (20) }

Comments for the numbered lines are as follows:

  1. We find the <h2> element via its class.
  2. We get a list of all the buttons via their tags.
  3. We attach an event handler to the Click event of the first button, which toggles the class of the <h2> element, changing its background color at each click (line (8)).
  4. We attach an event handler to the DoubleClick event of the second button, which changes the text in the <p> element (line (9)).
  5. We get the same list of buttons via a combination of CSS selectors.
  6. We attach an event handler to the MouseOver event of the third button, which changes the placeholder in the input field (line (10)).
  7. We attach a second event handler to the third button; clicking on it changes the background color of all buttons by adding a new CSS class to their classes collection (line (11)).

Every HTML element also has an attribute Map where the keys are the attribute names; you can use this Map to change an attribute, for example:

btn.attributes['disabled'] = 'true';

Please refer to the following document to see which attributes apply to which element:


Creating and removing elements

The structure of a web page is represented as a tree of nodes in the Document Object Model (DOM). A web page can start its life with an initial DOM tree, marked up in its HTML file, and then the tree can be changed using code; or, it can start off with an empty tree, which is then entirely created using code in the app, that is every element is created through a constructor and its properties are set in code. Elements are subclasses of Node; they take up a rectangular space on the web page (with a width and height). They have, at most, one parent Element in which they are enclosed and can contain a list of Elements—their children (you can check this with the function hasChildNodes() that returns a bool function). Furthermore, they can receive events. Elements must first be created before they can be added to the list of a parent element. Elements can also be removed from a node. When elements are added or removed, the DOM tree is changed and the browser has to re-render the web page.

An Element object is either bound to an existing node with the querySelector method of the document object or it can be created with its specific constructor, such as that in line (12) (where newTask belongs to the class LIElement—List Item element) or line (15). If useful, we could also specify the id in the code, such as in newTask.id = 'newTask';

If you need a DOM element in different places in your code, you can improve the performance of your app by querying it only once, binding it to a variable, and then working with that variable.

After being created, the element properties can be given a value such as that in line (13). Then, the object (let's name it elem) is added to an existing node, for example, to the body node with document.body.children.add(elem), as in line (16), or to an existing node, as list in line (14). Elements can also be created with two named constructors from the Element class:

  1. Like Element.tag('tagName') in line (17), where tagName is any valid HTML tag, such as <p>, <div>, <input>, <select>, and so on.
  2. Like Element.html('htmlSnippet') in line (18), where htmlSnippet is any valid combination of HTML tags.

Use the first constructor if you want to create everything dynamically at runtime; use the second constructor when you know what the HTML for that element will be like and you won't need to reference its child elements in your code (but by using the querySelector method, you can always find them if needed).

You can leave the type of the created object open using var, or use the type Element, or use the specific class name (such as InputElement)—use the latter if you want your IDE to give you more specific code completion and warnings/errors against the possible misuse of types.

When hovering over a list item, the item changes color and the cursor becomes a hand icon; this could be done in code (try it), but it is easier to do in the CSS file:

#list li:hover { color: aqua; font-size:20 px; font-weight: bold; cursor: pointer; }

To delete an Element elem from the DOM tree, use elem.remove(). We can delete list items by clicking on them, which is coded with only one line:

newTask.onClick.listen( (e) => newTask.remove() );

To remove all the list items, use the List function clear(), such as in line (19). Replace elem with another element elem2 using elem.replaceWith(elem2), such as in line (20).

Handling events

When the user interacts with the web form, such as when clicking on a button or filling in a text field, an event fires; any element on the page can have events. The DOM contains hooks for these events and the developer can write code (an event handler) that the browser must execute when the event fires. How do we add an event handler to an element (which is also called registering an event handler)?. The general format is:

element.onEvent.listen( event_handler )

(The spaces are not needed, but can be used to make the code more readable). Examples of events are Click, Change, Focus, Drag, MouseDown, Load, KeyUp, and so on. View this as the browser listening to events on elements and, when they occur, executing the indicated event handler. The argument that is passed to the listen() method is a callback function and has to be of the type EventListener; it has the signature: void EventListener(Event e)

The event handler gets passed an Event parameter, succinctly called e or ev, that contains more specific info on the event, such as which mouse button should be pressed in case of a mouse event, on which object the event took place using e.target, and so on. If an event is not handled on the target object itself, you can still write the event handler in its parent, or its parent's parent, and so on up the DOM tree, where it will also get executed; in such a situation, the target property can be useful in determining the original event object. In todo_v2.dart, we examine the various event-coding styles. Using the general format, the Click event on btns2[2] can be handled using the following code:

btns2[2].onClick.listen( changeBtnsBackColor );

where changeBtnsBackColor is either the event handler or callback function. This function is written as:

changeBtnsBackColor(Event e) => btns.forEach( (b) => b.classes.add('btns_backgr'));

Another, shorter way to write this (such as in line (7)) is:

btns2[2].onClick.listen( (e) => changeBtnsBackColor() ); changeBtnsBackColor() => btns.forEach( (b) =>

When a Click event occurs on btns2[2], the handler changeBtnsBackColor is called.

In case the event handler needs more code lines, use the brace syntax as follows:

changeBtnsBackColor(Event e) { btns.forEach( (b) => b.classes.add('btns_backgr')); // possibly other code }

Familiarize yourself with these different ways of writing event handlers.

Of course, when the handler needs only one line of code, there is no need for a separate method, as in the following code:

newTask.onClick.listen( (e) => newTask.remove() );

For clarity, we use the function expression syntax => whenever possible, but you can inline the event handler and use the brace syntax along with an anonymous function, thus avoiding a separate method. So instead of executing the following code:

task.onChange.listen( (e) => addItem() );

we could have executed:

task.onChange.listen( (e) { var newTask = new LIElement(); newTask.text = task.value; newTask.onClick.listen( (e) => newTask.remove()); task.value = ''; list.children.add(newTask); } );

JavaScript developers will find the preceding code very familiar, but it is also used frequently in Dart code, so make yourself acquainted with the code pattern ( (e) {...} );. The following is an example of how you can respond to key events (in this case, on the window object) using the keyCode and ctrlKey properties of the event e:

window.onKeyPress .listen( (e) { if (e.keyCode == KeyCode.ENTER) { window.alert("You pressed ENTER"); } if (e.ctrlKey && e.keyCode == CTRL_ENTER) { window.alert("You pressed CTRL + ENTER"); } });

In this code, the constant const int CTRL_ENTER = 10; is used.

(The list of keyCodes can be found at http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes).

Manipulating the style of page elements

CSS style properties can be changed in the code as well: every element elem has a classes property, which is a set of CSS classes. You can add a CSS class as follows:

elem.classes.add ('cssclass');

as we did in changeBtnsBackColor (line (11)); by adding this class, the new style is immediately applied to the element. Or, we can remove it to take away the style:

elem.classes.remove ('cssclass');

The toggle method (line (8)) elem.classes.toggle('cssclass'); is a combination of both: first the cssclass is applied (added), the next time it is removed, and, the time after that, it is applied again, and so on.

Working with CSS classes is the best way to change the style, because the CSS definition is separated from the HTML markup. If you do want to change the style of an element directly, use its style property elem.style, where the cascade style of coding is very appropriate, for example:

newTask.style ..fontWeight = 'bold' ..fontSize = '3em' ..color = 'red';

Animating a game

People like motion in games and a movie is nothing but a quick succession of image frames. So, we need to be able to redraw our screen periodically to get that effect; with Dart screen frame rates of 60 fps or higher, this becomes possible. A certain time interval is represented in Dart as an object of the type Duration. To do something periodically in Dart, we use the Timer class from the dart:async library and its periodic method. To execute a function moveBall() at every INTERVAL ms (you could call it a periodic event), use the following method:

new Timer.periodic( const Duration
(milliseconds: INTERVAL),(t) => moveBall() );

The first parameter is the time period, the second is the callback function that has to be periodically executed, and t is the Timer object. If the callback function has to be executed only once, just write a new Timer(.,.) method, omitting the periodic function. When drawing on canvas, the first thing that the periodically called function will have to do is erase the previous drawing. To stop a Timer object (usually in a game-over situation), use the cancel() method.

Another way of doing this is by using the animationFrame method from the window class. With this technique, we start gameLoop in the main() function and let it call itself recursively, as in the following code:

main() { // code left out // redraw window.animationFrame.then(gameLoop); }gameLoop(num delta) { moveBall(); window.animationFrame.then(gameLoop); }

Ping Pong using style(s)

Normally, you would write an HTML Dart game using canvas, but it is interesting to see what is possible just by manipulating the styles. Download the project from GitHub with: git clone git://github.com/dzenanr/ping_pong_dom.git.

This project was developed in spirals; if you want to see how the code was developed, explore the seven stages in the subfolder spirals (spiral s07, especially, contains a function examineCSS() that show you how to read the rules in the stylesheet of Dart code; also, the game screen contains some useful links to learn more about reading and changing CSS rules).

The following is the Dart code of the master version; we have commented on it using line numbers:

import 'dart:html'; import 'dart:async'; const int INTERVAL = 10; // time interval in ms to redraw the screen const int INCREMENT = 20; // move increment in pixels CssStyleSheet styleSheet; (1) var pingPong = { (2) 'ball': { 'speed': 3, 'x' : 195, 'y' : 100, 'dx' : 1, 'dy' : 1 }, 'key': { 'w' : 87, 's' : 83, 'up' : 38, 'down' : 40 }, 'paddleA' : { 'width' : 20, 'height' : 80, 'left' : 20, 'top' : 60, 'score' : 0 }, 'paddleB' : { 'width' : 20, 'height' : 80, 'left' : 360, 'top' : 80, 'score' : 0 }, 'table' : { 'width' : 400, 'height' : 200 } };main() { styleSheet = document.styleSheets[0]; (3) document.onKeyDown.listen(onKeyDown); (4) // Redraw every INTERVAL ms. new Timer.periodic(const Duration(milliseconds: INTERVAL),
(t) => moveBall()); (5) } String ballRule(int x, int y) { String rule = ''' #ball { background: #fbbfbb; position: absolute; width: 20px; height: 20px; left: ${x.toString()}px; top: ${y.toString()}px; border-radius: 10px; } '''; return rule; } String paddleARule(int top) { String rule = ''' #paddleA { background: #bbbbff; position: absolute; width: 20px; height: 80px; left: 20px; top: ${top.toString()}px; } '''; return rule; } String paddleBRule(int top) { String rule = ''' #paddleB { background: #bbbbff; position: absolute; width: 20px; height: 80px; left: 360px; top: ${top.toString()}px; } '''; return rule; } updateBallRule(int left, int top) { styleSheet.removeRule(1); styleSheet.insertRule(ballRule(left, top), 1); } updatePaddleARule(int top) { styleSheet.removeRule(2); styleSheet.insertRule(paddleARule(pingPong['paddleA']['top']), 2); } updatePaddleBRule(int top) { styleSheet.removeRule(3); styleSheet.insertRule(paddleBRule(pingPong['paddleB']['top']), 3); } onKeyDown(e) { var paddleA = pingPong['paddleA']; var paddleB = pingPong['paddleB']; var key = pingPong['key']; if (e.keyCode == key['w']) { (6) paddleA['top'] = paddleA['top'] - INCREMENT; updatePaddleARule(paddleA['top']); } else if (e.keyCode == key['s']) { paddleA['top'] = paddleA['top'] + INCREMENT; updatePaddleARule(paddleA['top']); } else if (e.keyCode == key['up']) { paddleB['top'] = paddleB['top'] - INCREMENT; updatePaddleBRule(paddleB['top']); } else if (e.keyCode == key['down']) { paddleB['top'] = paddleB['top'] + INCREMENT; updatePaddleBRule(paddleB['top']); } } moveBall() { var ball = pingPong['ball']; var table = pingPong['table']; var paddleA = pingPong['paddleA']; var paddleB = pingPong['paddleB']; // check the table boundary // check the bottom edge if (ball['y'] + ball['speed'] * ball['dy'] > table['height']) { ball['dy'] = -1; (7) } // check the top edge if (ball['y'] + ball['speed'] * ball['dy'] < 0) { ball['dy'] = 1; } // check the right edge if (ball['x'] + ball['speed'] * ball['dx'] > table['width']) { // player B lost (8) paddleA['score']++; document.querySelector('#scoreA').innerHtml
paddleA['score'].toString(); // reset the ball; ball['x'] = 250; ball['y'] = 100; ball['dx'] = -1; } // check the left edge if (ball['x'] + ball['speed'] * ball['dx'] < 0) { // player A lost (9) paddleB['score']++; document.querySelector('#scoreB').innerHtml =
paddleB['score'].toString(); // reset the ball; ball['x'] = 150; ball['y'] = 100; ball['dx'] = 1; } ball['x'] += ball['speed'] * ball['dx']; ball['y'] += ball['speed'] * ball['dy']; // check the moving paddles // check the left paddle if (ball['x'] + ball['speed'] * ball['dx'] < (10) paddleA['left'] + paddleA['width']) { if (ball['y'] + ball['speed'] * ball['dy'] <= paddleA['top'] + paddleA['height'] && ball['y'] + ball['speed'] * ball['dy'] >= paddleA['top']) { ball['dx'] = 1; } } // check the right paddle if (ball['x'] + ball['speed'] * ball['dx'] >= paddleB['left']) { if (ball['y'] + ball['speed'] * ball['dy'] <= paddleB['top'] + paddleB['height'] && ball['y'] + ball['speed'] * ball['dy'] >= paddleB['top']) { ball['dx'] = -1; (11) } } // update the ball rule updateBallRule(ball['x'], ball['y']); }

The screen looks like as shown in the following screenshot:

The screen of Ping Pong DOM

Basically, the mechanism is that we change the left and top property values in the style rules for the ball, paddleA and paddleB in the function ballRule, paddlARule, and so on. When this new style rule is attached to our document, the HTML element moves on the screen. In line (1), we declare a stylesheet that we append to our document in line (3). The variable pingPong in line (2) is a Map with the keys ball, key, paddleA, paddleB, and table (these correspond with HTML element IDs), and their values are, themselves, maps containing variables and their values (for example, top has the value 60). These maps are further referenced using variables, as follows:

var paddleA = pingPong['paddleA'];

In line (4), an onKeyDown event handler is defined. This tests the key that was pressed along with if (e.keyCode == key['w']) (line (6)), and so on, and, when the key is recognized, the value of the top variable in the corresponding paddle Map is incremented or decremented (the value of Top is 0 at the top of the screen and increases towards the bottom of the screen. w means that the value is going up; this means the value of top is decreasing, so we have to subtract INCREMENT from the current top value, and likewise for the other directions). An updatePaddle(A-B)Rule function is called; in it, a new style rule is inserted into the stylesheet, updating the top value for the style rule of the corresponding paddle HTML element (the style rules are multiline strings).

Let's then see what happens in the periodic function moveBall(). Basically, this method changes the x and y coordinates of the ball:

ball['x'] += ball['speed'] * ball['dx']; ball['y'] += ball['speed'] * ball['dy'];

However, we have to check a number of boundary conditions (such as the ball crossing the edges of the table); if the ball is going down toward the bottom edge of the table (line (7)), dy becomes -1, so the new ball['y'] value will be smaller and the inverse will occur for when the ball goes along the top edge. If the ball goes over the right edge (line (8)), Player A wins a point, so their score is updated on the screen and the ball is reset. In line (9), the inverse is true and Player B wins a point. In lines (10) and (11), we test for the collision of the ball and paddleA or paddleB respectively; using paddleA, we want to send the ball to the right, so we set dx = 1; with paddleB, we want to send it to the left, so dx = -1. Then, in the same way as for the paddles, we update the style rule for the ball.


You now know all the techniques for finding, manipulating, and styling web page elements using Dart code to change the user interface and you can respond to events that take place on the page. You have learned how to change the CSS properties in DOM in order to move game objects and how to build a complete game project. We found that it is advisable to develop in a spiral way, building upon the previous spirals as the project acquires more functionality. The different entities are represented by classes; in that way, our project is naturally modularized.

Resources for Article:

Further resources on this subject:

Books to Consider

comments powered by Disqus

An Introduction to 3D Printing

Explore the future of manufacturing and design  - read our guide to 3d printing for free