Friday, March 30, 2012

Building a client / server Dart App – Part 2 – client (browser) side.


Update: 05/May/2012: This post^ provides a simple, up-to-date (at the time of writing) example
on how to do simple dart client and server. What you may read below is still valid, but some api’s may have moved on a little. Please let me know if you find any obvious bugs.
Yesterday, I blogged about the server side of a dart + mongoDb web app, written in Dart in both the client and the server.  The server code consists of an http server and mongodb drivers which saves blog posts sent from the client as JSON into the mongodb, and retrieves them by querying them from mongodb, and sending them back to the browser as JSON again.
The blog code running in the browser
All the code to try this out is available on github^. And thanks to some work from +Adam Smiththis post on Google Plus will help you get running even quicker.
Our interface to the data looks like:
GET: http://localhost:8083/post/all
POST: http://localhost:8083/post
with a JSON representation of a post looking like:
{"posted": "2012/03/29 00:00:00.000", "text":"some post text"}
Our client will be made up from an html file and a .dart file (which will eventually become a .dart.js file).  These will end up in the dartwatch-blog-server\client folder (see the previous post for details).  These files are served up by the StaticFile() class of the crimson http server.  There is no processing of the .dart or .html file on the server – it is simply served to the browser.
But first, we need to build a UI.  Although this is a very simple app, we’ll build the UI in code.  (It would be fairly straightforward to build this directly in html, but for example’s sake, we’ll build the UI using the dart:htmllibrary).

Building a Dart UI with dart:html

We’ll start with the Dart Editor‘s new web application wizard.  We’ll start with the html file, which is very straightforward – it simply contains a couple of script tags and a tiny bit of css.  The first script tag brings in the blog.dart file, and the second script tag automatically converts the script tag request to ask for the blog.dart.js file if the browser doesn’t support dart natively (every browser apart from Dartium at the moment).
<!DOCTYPE html>
<html>
  <head>
    <title>blog</title>
  </head>
  <body>
    <style>
      #textbox { width:150px; height:100px; float:right;  }
      #add-post-button { float:right; }
      #list-of-posts { width: 350px; float:left; }
    </style>
    <script type="application/dart" src="blog.dart"></script>
    <script src="http://dart.googlecode.com/svn/branches/bleeding_edge/dart/client/dart.js"></script>
  </body>
</html>
Next, our we’ll take a look at the blog.dart file.  Every dart app starts with the main() function which is called automatically when the dart app is ready to run.  Our app imports the two required libraries dart:html and dart:json.
The dart:html library provides a neat way to manipulate the browser dom without needing to worry about all the inconsistencies that the dom has by default.
The dart:json library is straightforward – it converts JSON Strings back and forth from a List / Map. This is the same library that we used on the server side part (a good example of where we can use the same code on the client and server).
The start of our app looks like this:
#import('dart:html');
#import("dart:json");

void main() {
  buildUi();
  loadExistingPosts();

}

buildUi() {
  //todo - build the user interface
}

loadExistingPosts() {
  //todo - load the posts from the server to show in the UI
}
To build the UI, we are going to create an html textarea, a button and a div to contain the list of posts. Each of these elements will have an id (which is referenced in the css), and our button adds a click listener called _onAddPostClick. We add each of the elements to the body by using the body Elements “nodes” property as in document.body.nodes.add( … ).
void buildUi() {
  Element input = new Element.tag("textarea");
  input.id = "textbox";
  document.body.nodes.add(input);

  var button = new Element.tag("button");
  button.text="Add Post";
  button.id="add-post-button";
  button.on.click.add(_onAddPostClick);
  document.body.nodes.add(button);

  var postDiv = new Element.tag("div");
  postDiv.id = "list-of-posts";
  document.body.nodes.add(postDiv);

}
The _onAddPostClick(event) function is called whenever the user clicks the button. This uses the dart:html query function to grab the “textbox” element, and get the text out of it. It populates a map with the text and the current dateTime. Finally, it calls two functions: addPostToServer() and addPostsToUi().
void _onAddPostClick(event) {
  var textbox = document.body.query("#textbox");
  Map blogPost = new Map();
  blogPost["text"] = textbox.value;
  blogPost["posted"] = new Date.now().toString();

  addPostToServer(blogPost);
  addPostToUi(blogPost);
}

Sending data to the server

The addPostToServer(Map blogPost) function uses XMLHttpRequest^ (which allows sending data between the client and the server). Although it’s called XMLHttpRequest, you can use any data format. We’re going to use JSON to convert the blogpost Map to a string, and then us an http POST to send the data to our /post url.
void addPostToServer(blogPost) {
  String jsonData = JSON.stringify(blogPost);
  XMLHttpRequest req = new XMLHttpRequest();
  req.open("POST", "http://localhost:8083/post");
  req.send(jsonData);
}

Adding data to the UI

The next function is addPostToUi(Map blogPost) which adds the blog post data to the postDiv (which contains the list of all the posts in the UI).
We create new elements using the dart:html Element class, which has two named constructors: .tag() which takes a tag name, or .html() which takes a block of html with a single root element.
By using the dart:html query() function to get a reference to the document postDiv (called list-of-posts), we can add our postText div (which contains the text from the post, and the timestamp) to the postDiv.nodes collection, which adds it into the DOM, and makes it appear on screen.
void addPostToUi(blogPost) {
  var postDiv = document.body.query("#list-of-posts");

  var postedDateElement = new Element.tag("div");
  postedDateElement.innerHTML = "Posted on ${blogPost['posted']}";

  var postText = new Element.tag("div");
  postText.nodes.add(postedDateElement);
  postText.nodes.add(new Element.html("<span>${blogPost['text']}</span>"));
  postText.nodes.add(new Element.html("</pre>

<hr />

<pre>
"));

  postDiv.nodes.add(postText);
}
The final function in our todo list is loadExistingPosts().

Loading data from the server

Once we have built our UI, and added the required functionality (hooking the button up with the ability to save a post and add it to the UI), we need to load any existing posts from the server. We do this using another XMLHttpRequest, which performs a GET against the http://localhost:8083/post/all url. You can reference this directly in your browser to see the JSON that is returned from the server.
The asnyc call to load the posts takes the URL and a callback function which contains the results. When we get the result back, we use the JSON library to convert it back to a List of Map – and then call the same addPostToUi function that we used in the button click listener.
loadExistingPosts() {
  XMLHttpRequest req =
        new XMLHttpRequest.getTEMPNAME("http://localhost:8083/post/all",
               (result) {    //this is the callback function
                             //which is called when the XMLHttpRequest receives a response
                             //from the server
                 List posts = JSON.parse(result.responseText);
                 for (Map blogPost in posts) {
                   addPostToUi(blogPost);
                 }
               });   

}
And that is it – your app can send data to the server, load data from the server, and build a UI directly in Dart.
I’ll probably expand on this in the near future, by adding more UI features, and the CrimsonHttp server is still very much a toy (for example, it doesn’t recognise route parameters, such as /post/{postId}, so at the moment you’d have to put a postId on the query string if you wanted to load specific data.

7 comments:

  1. Thanks for these great tutorials. I am reading them alongside the book, MEAP 'Dart in Action', I recently bought.

    Can you update the links to the images etc. Please?

    ReplyDelete
    Replies
    1. Images updated! Thanks for the reminder. Please let me know if you find any I've missed.

      Delete
  2. You need to update the internal links. They're broken.

    ReplyDelete
  3. Very good tutorial!
    I just have the problem, that my Dart Editor M3 doesnt accept the JSON and the XmlHttpRequest. Errors:
    No such type JSON.
    Cannot resolve XmlHttpRequest.
    Can anyone help me with that?
    Thanks in advance!

    ReplyDelete
    Replies
    1. Thanks - there's a re-writing of this tutorial in progress.

      For the moment, the JSON library now has "top level functions", so either use:
      import 'dart:json' as JSON; // add a library prefix JSON
      // elsewhere
      JSON.parse()... // uses the library prefix JSON

      or
      import 'dart:json'; // no library prefix
      // elsewhere
      parse(); // no need to use a library prefix

      Secondly, XmlHttpRequest is renamed HttpRequest. You might want to check out the api docs at http://api.dartlang.org as there are a couple of other smallish changes to the HttpRequest API.

      Delete
    2. Thank you for your quick answer!
      Of course I now have another problem :-)
      When I try to get the data from the server with...
      HttpRequest req = new HttpRequest.getTEMPNAME("http://localhost:8083/post/all",
      ...then Dart throws the error "New expression does not resolve to a constructor".
      I would be very happy if you could help me again.
      Thanks!

      Delete
    3. No problem - that part of the API has changed too.
      A more recent example is shown on my article here:
      http://www.dartlang.org/articles/json-web-service/#getting-data

      Delete