scribeGriff Studios

Books • Algorithms • Dart • HTML5 • CSS3

To content | To menu | To search

Using the HTML5 <input> range Attribute with the Timer Class in Dart

Update 20 February 2013: With the M3 release of Dart, futures and the timer class have been moved to the dart:async library. We no longer need to import the dart:isolate library for this example. Also, event listeners have now been "streamified": on.click.add is now onClick.listen, for example. The timer class now expects a constant of type Duration rather than int as before. In this example, we parse an int from the user interface to set the timer's delay. To convert that parsed int to a Duration, we can make use of the multiplication operator of the Duration class:

// Define some default unit constant Duration value.
const ms = const Duration(milliseconds: 1);
// Parse the int value from the user.
querySelector('#mySlider').onMouseUp.listen((e) {
    var consumptionRate = int.parse(mySlider.value);
});
// Multiply your parsed value by the const defined earlier.
Duration duration = ms * consumptionRate;
// Update your timer with the new value.
timer = new Timer(duration, consume);

Update 16 October 2012: A lot has happened with the Dart programming language since I originally wrote this post last year so for Dart's first anniversary I thought it was a good time for an update. Perhaps the biggest change as it relates to this article is that we now have a Timer class available on both the client and the server side through the dart:isolate library. So we'll start the update by saying goodbye to both setInterval and clearInterval in our original example. But while we are at it, we'll also incorporate some of the other really nice language features Dart has implemented over the past year, including [1]:

  • Method cascades
  • Futures for asynchronous programming
  • No + for string concatenation
  • Elements now have factory constructors:
//This still works:
DivElement myDiv1 = new Element.tag("div");
//But you can now do:
var myDiv1 = new DivElement();

to name just a few. The post itself has been updated although the intent remains simply to illustrate how one could do a variety of assorted tasks in Dart and not to suggest a specific way of doing things. We don't, for example, cover Dart's excellent methods for programmatically manipulating classes attached to (or removed from) elements:

myElement
    ..previousElementSibling.classes.remove('show')
    ..previousElementSibling.classes.add('hide')
    ..classes.remove('hide')
    ..classes.add('show');

and others including toggle(), contains(), etc [2]. Adding and removing classes to control styles is arguably the more appropriate technique than what we have outlined here, but the aim of this article was always just to exercise a number of Dart's DOM manipulation features. In any event, the updated source code for this post can be found on github.

One thing has surprisingly not changed in the past year, however. Neither IE nor Firefox yet (ever?) support the range attribute for the <input> tag. So even though Dart compiles to JavaScript for cross-browser support, the slider element will not work in either of these two browsers.

Introduction


HTML5 adds a number of useful type attributes for the <input> tag [3], including fields for validating email addresses, entering dates, and providing a color picker. Although browser adoption has so far been somewhat inconsistent as of this writing, one attribute that has been of particular interest is the range attribute, more commonly referred to as the slider. Not currently supported by either Firefox (surprisingly) or IE (less so), the remaining popular browsers (Chrome, Safari and Opera) do offer support for this attribute of the <input> tag.

The HTML for this type of tag looks like the following:

<input id="mySlider" type="range" min="1000" max="10000" step="20" value="5000" />

In this article, however, we will be implementing our example almost entirely in code. Therefore, in Dart [4], we can create the <input> tag above as follows:

var mySlider = new InputElement();
mySlider.attributes = ({
  "id": "mySlider",
  "type": "range",
  "min": "500",
  "max": "8000",
  "step": "20",
  "value": "2000"
});
container.nodes.add(mySlider);

where container is the name of the enclosing <div> tag, which we will create in just a moment.

For this example, we will use the slider to control new Timer(delay, (Timer timer) => myFun())'s fixed time delay when calling function myFun(). First, however, we need to create a simple web page to display our example.

Working with HTML in Dart


In the Dart Editor [5], we've created a new project that contains a fairly sparse HTML file comprised of only a few basic tags:

<html>
  <head>
    <title>Using the HTML5 input range Attribute with the Timer class in Dart</title>
  </head>
  <body id="home">
    <script type="text/javascript" src="html_in_dart.dart.js"></script>
  </body>
</html>

By default, the Dart Editor comes bundled with a version of Dartium that allows you to test your code directly without having to compile to JavaScript first. A bootstrap file, dart.js, first checks to see if there is native support for Dart in the browser. If there is, loads the Dart file. If not, it loads the compiled JavaScript file. Using this technique, your HTML file would look like the following:

<html>
  <head>
    <title>Using the HTML5 input range Attribute with the Timer class in Dart</title>
  </head>
  <body id="home">
    <script type="application/dart" src="html_in_dart.dart"></script>
    <script src="packages/browser/dart.js"></script>
  </body>
</html>

To make use of this approach, you need to use pub, the package manager for Dart. To use pub with Dart, you create a pubspec.yaml file and add the browser dependency as follows:

name: dart_html5_example1
version: 0.0.1
author: Richard Griffith
description: Using the HTML5 <input> range Attribute with the Timer Class in Dart
dependencies:
  browser: any

and then run pub install. This will create a directory called packages in your project's directory structure with a symlink to the browser directory that contains the bootstrap file dart.js.

However, in this example, we are going to generate the JavaScript file, and hence we use the script tag of that type. Before we start adding some page elements to help us organize our example, let's just discuss for a few minutes several different ways that we have to add styles to these elements in Dart.

Adding Styles

There are three different ways to add styling to an HTML page - external style sheet(s), internal style sheet(s) or by applying an inline style to a particular tag [6]. Depending on how you set up your project in the Dart Editor, it might create a sample style sheet and attach it to a sample HTML page for you, or you can simply add a link to the css file in the <head> tag of your project's HTML file:

<link type="text/css" rel="stylesheet" href="myStyleSheet.css">

Adding a link to the external style sheet programmatically can be accomplished by defining a LinkElement:

var myStyleSheet = new LinkElement();
myStyleSheet
    ..type = "text/css"
    ..rel = "stylesheet"
    ..href = "myStyleSheet.css";
document.head.nodes.add(myStyleSheet);

You can also use Dart to create an internal style sheet. If you look at our simple HTML file above, you'll notice we gave the body tag an ID selector of id="home". To style this selector using an internal style sheet, you can add the following in your application:

var myStyle = new StyleElement();
myStyle
    ..type = "text/css"
    ..appendHtml = "#home {background-color: #E1CF75}";
document.head.nodes.add(myStyle);

Finally, if you want to apply your styles to tags inline, there are two ways to do this. In this example, we will be creating a div with an ID selector of id="page". We can then style it as follows:

var page = new DivElement();
page
    ..id = "page"
    ..style.width = "1000 px"
    ..style.margin = "20 auto"
    ..style.backgroundColor = "rgba(255,255,255,0.75)"
    ..style.borderRadius = "15px";
document.body.nodes.add(page);

Note that if you use this approach, the CssStyleDeclaration method name will not necessarily match its corresponding CSS style name (ie, backgroundColor for background-color). Refer to the CssStyleDeclaration section of Dart's HTML library for a complete list of style methods [7].

A more concise way of writing an inline style is to use the cssText() method. We can then rewrite the above as follows:

var page = new DivElement();
page
    ..id = "page"
    ..style.cssText = "width:1000px; margin:20px auto; background-color:rgba(255,255,255,0.75); border-radius:15px;";
document.body.nodes.add(page);

In most practical applications, styling rules would be kept in an easily modified external style sheet which you can manipulate by making use of Dart's robust element.classes methods. For illustration purposes, however, we will be using either the internal style sheet or the inline approach in this example. So let's set up our document.

Building a Better DOM

One of the nice improvements put forth by the Dart's creators is the use of factory methods to create new instances of DOM types [8]. Other improvements include superior event handling, better querying (think jQuery without the library), using real collections and simpler naming. Below is an example of adding an event listener in Dart.

querySelector('#myButton').onClick.listen(
    (e) => _foo.fun(new bar()));

The above code snippet queries the DOM for an element with an ID selector of id="myButton" and, if that button is clicked, executes the fun() method of private class foo, passing a call to the constructor method for bar().

Let's put what we have discussed so far into the beginnings of a comprehensive example. The code below is just meant to illustrate collectively how one might go about accomplishing a variety of tasks in Dart. Although not really necessary for this specific example, we make use of Dart's Future<T> object [9] [10] to allow a linear progression of adding an internal style sheet, building an HTML page of elements and finally adding event listeners to several of those elements. All Dart applications contain a function main() so let's start there:

void main() {
  var htmlExample = new HtmlInDart().createStyles();
  htmlExample
      ..then((htmlExample) => htmlExample.buildPage())
      ..then((htmlExample) => htmlExample.addListeners());
}

Here we are chaining together a sequence of Future methods from an instance of the HtmlInDart() class. This class begins by importing some libraries and defining some top level variables:

import 'dart:html';
import 'dart:math';
import 'dart:async';

class HtmlInDart {
  InputElement numBottles;
  InputElement mySlider;
  int consumptionRate = 2000;
  int bottleNumber;
  Timer timer;
  // Timer now requires the use of a Duration to set the timer interval.
  static const ms = const Duration(milliseconds: 1);

  HtmlInDart();

Next, let's look at the first method, createStyles(), where we create our internal style sheet:

Future createStyles() {
  final c = new Completer();
  // Adds the opening and closing <style> tags, sets the type and adds it to the head section.
  var myStyle = new StyleElement();
  document.head.nodes.add(myStyle);
  // Defines most of the styles used in the example.  A few styles have been defined inline.
  myStyle
    ..type = "text/css"
    ..innerHtml = "#home {background-color: #E1CF75;"
        "font-family: Verdana, Geneva, sans-serif;}"
    ..appendHtml("#page {width:1000px; margin:20px auto;"
        "background-color:rgba(255,255,255,0.7); border-radius:15px; overflow:hidden;}")
    ..appendHtml("#output {width:50%; height:600px; margin:10px; padding:20px;"
        "background-color:white; float:right; border-top-right-radius:20px;"
        "border-bottom-right-radius:20px; overflow:auto;}")
    ..appendHtml("#titleHeading {margin:30px; text-align:center; border-bottom: 2px inset Khaki;"
       "overflow:hidden; padding-bottom:10px;}")
    ..appendHtml("#input1 {width:30%; margin:30px 50px; padding:0 20px 20px 20px;"
        " border-bottom: 2px inset Khaki;}")
    ..appendHtml("#input2 {width:30%; margin:30px 50px; padding:0 20px;}")
    ..appendHtml("h4 {text-align:center;}");

  c.complete(this);
  return c.future;
}

Note that we begin by declaring a new Completer object. Once the method finished, we return our HtmlInDart object and move on to building our document:

Future buildPage() {
  final c = new Completer();
  // Create a page div that will enclose our example and add it to the body section.
  var page = new DivElement();
  page.id = "page";
  document.body.nodes.add(page);

  // Create another div for the output text to be written to.
  var outputCont = new DivElement();
  outputCont.id = "output";
  page.nodes.add(outputCont);

  // Our page gets a heading.
  var titleHeading = new HeadingElement.h2();
  titleHeading
      ..id = "titleHeading"
      ..text = "Welcome to 99 Bottles or Less of Dart Beer!";
  page.nodes.add(titleHeading);

  // Now define two divs to hold the user input and provide a title for each div.
  // Add both to the page div.
  var inputCont1 = new DivElement();
  inputCont1.id = "input1";
  page.nodes.add(inputCont1);

  var inputHeading1 = new HeadingElement.h4();
  inputHeading1
      ..id = "inputHeading1"
      ..text = "Enter the number of Dart beers on the wall (1-99):";
  inputCont1.nodes.add(inputHeading1);

  var inputCont2 = new DivElement();
  inputCont2.id = "input2";
  page.nodes.add(inputCont2);

  var inputHeading2 = new HeadingElement.h4();
  inputHeading2
      ..id = "inputHeading2"
      ..text = "Rate of Consumption:";
  inputCont2.nodes.add(inputHeading2);

  // Now create the user interface elements.
  // The first element is a text field with a submit button.
  numBottles = new InputElement();
  numBottles.attributes = ({
    "id": "numBottles",
    "type": "text"
  });
  inputCont1.nodes.add(numBottles);

  var commenceButton = new ButtonElement();
  commenceButton.attributes = ({
    "id": "commenceButton",
    "type": "button",
  });
  commenceButton.innerHtml = "commence";
  inputCont1.nodes.add(commenceButton);

  // The second interface element is a slider (or range) element.
  mySlider = new InputElement();
  mySlider.attributes = ({
    "id": "mySlider",
    "type": "range",
    "min": "500",
    "max": "8000",
    "step": "20",
    "value": "2000"
  });
  inputCont2.nodes.add(mySlider);

  // Now add labels to the slider and style the labels inline.
  var labelFast = new LabelElement();
  labelFast
      ..text = "fast"
      ..style.float = "left"
      ..style.paddingRight = "50px";
  inputCont2.nodes.add(labelFast);

  var labelSlow = new LabelElement();
  labelSlow
      ..text = "slow"
      ..style.float = "right";
  inputCont2.nodes.add(labelSlow);

  c.complete(this);
  return c.future;
}

Finally, we add our event listeners. Note that for the slider we are using a mouseUp event rather than a change event. This is to prevent registering new values for our variable consumptionRate until the user has actually released the mouse.

Future addListeners() {
  final c = new Completer();

  querySelector('#commenceButton').onClick.listen(
      (e) => startConsuming());

  querySelector('#mySlider').onMouseUp.listen((e) {
    consumptionRate = int.parse(mySlider.value);
  });

  c.complete(this);
  return c.future;
}

If you were to execute this code in the Dart Editor with the HTML file that we showed earlier, you would see something like the figure below (depending on your browser's support for HTML5 elements):

using-the-slider-with-dart1.jpg

Now that we have constructed our page and styled it, let's add some logic to the application to allow a user to vary the length of time between subsequent calls to a method.

The <input> range Attribute with the Timer Class


With Dart we no longer need to rely on the setInterval() function to call a method after a fixed amount of time has passed. The Timer class is available to both the client and the server and also provides methods for repeatedly triggering a method at a set interval [11]. In the functions below, we start counting down from a number of bottles until all the bottles have been consumed. The rate of consumption is controlled by the slider element. Note that we have declared the variable consumeIntervalID at the top level of the class HtmlInDart.

void startConsuming() {
  querySelector('#commenceButton').attributes['disabled'] = 'disabled';
  querySelector('#output').innerHtml = "";
  numBottles.value == "" ? numBottles.value = "24" : numBottles.value;
  bottleNumber = int.parse(numBottles.value) ;
  bottleNumber > 99 || bottleNumber < 1 ? bottleNumber = 24 : bottleNumber;
  consume();
}

void consume() {
  String s, s1, s2, s3, s4;
  // Timer needs a value of type Duration.  But we are
  // parsing a value of type int.  We will use the fact that
  // int * Duration = Duration to satisfy the Timer requirement.
  var duration;
  if (bottleNumber <= 0) {
    s1 = "Sorry, there are no bottles of Dart beer left. <br>";
    s2 = "Time for more coding with Dart!";
    s = "$s1 $s2";
  } else {
    s1 = "$bottleNumber bottles of Dart beer on the wall, <br>";
    s2 = "$bottleNumber bottles of Dart beer, <br>";
    s3 = "Take one down and pass it around, <br>";
    s4 = "${bottleNumber-1} bottles of Dart beer on the wall. <br><br>";
    s = "$s1 $s2 $s3 $s4";
    bottleNumber -= 1;
    // Duration = Duration * int.
    duration = ms * consumptionRate;
    timer = new Timer(duration, consume);
  }
  write(s);
}

void write(String message) {
  querySelector('#output').addHtml("$message<br>");
  querySelector('#output').scrollTop = 100000;
}

You can try out this example here and find the source code here. We did note that it would be better to set the slider to have a logarithmic scale to make it more visually intuitive. The Dart source code has been compiled to JavaScript for the demonstration. However, as we mentioned earlier, the range attribute for the input tag, which we used to implement the slider, is not currently supported by either IE or FF.


Works Cited

[1] Dart M1 Language Changes
[2] Dart's CssClassSet API Reference
[3] HTML5 Input Types at w3schools.com
[4] Dart's Home Page
[5] The Dart Editor
[6] CSS How To at w3schools.com
[7] Dart's CSSStyleDeclaration Reference
[8] Improving the DOM with Dart
[9] Asynchronous Programming in Dart
[10] Common Dart Scenarios for the 'Future' API
[11] Dart's Timer Class API Reference

With inspiration from 99 Bottles of Dart Beer by Riccardo Brambilla

submit to reddit
Google+
Richard Griffith

Author: Richard Griffith

Keep in touch with the latest updates. Subscribe to the RSS Feed for this category.

Comments (0)

Be the first to comment on this article

Add a comment This post's comments feed

no attachment



You might also like

angular-and-dart-iconj.jpg

Today's @Directive: Get Up to Speed Using Angular with Dart

Although I spend most of my time working with Dart using Polymer, it's hard not to find the announcement of AngularDart a compelling reason to take it out for a spin. And I confess, the more I use Angular with Dart, the more I'm convinced it is going to be a major component of my Dart toolbox for the foreseeable future. I had come across Jesus Rodriguez's introduction to the model driven framework with his appropriately named blog post Why Does Angular.js Rock?. In this article, we look at the Dart equivalents of the examples Jesus presented.

Continue reading

future-word-cloud-dart.png

Iterables, Futures, and Future.wait() in Dart

Several days ago while working with Dart, I was coding up a problem that required that I iterate over a function that returns its value represented as a Future. The variables that I needed to pass to this function were read from an external file and stored in a List using a Stream. To evaluate the function for each element in the List, I was tempted to use a simple forEach() to pass the elements to the function returning a Future. But I was soon going to discover a much better way for dealing with a scenario such as this.

Continue reading