Forms, HTTP servers, and Web Components with Dart

(UPDATED VERSION HERE. This post is out of date, you probably want the new version.)

Dart can be used on the client and the server. This post shows how to:
  • build a form as a custom element
  • bind input fields to a Dart object
  • build a Dart HTTP server
  • handle and parse a form submit


For lots more examples, and more context and details, be sure to check out the Dart Tutorials. You can find the code for this post at my Github account.

Step 1: Install the Web UI package

Open up pubspec.yaml (used by the pub package manager) and add the dependencies for Web UI.

dependencies:
  browser: any
  web_ui: any

Step 2: Create the model class

This class is for our "business object". It is bound to the form, so we make it observable.

library person;

import 'package:web_ui/web_ui.dart';

@observable
class Person {
  String firstName;
  String lastName;
  double age = 0.0// not an int because web ui loves doubles
}

Step 3: Create the custom element HTML

This custom element, polyfilled by Web UI, wraps the form and makes it easy to reuse. The custom element is bound to an instance of Person, our model class. The <template> tag contains the structure of this custom element. Notice the submit event runs the submit() method of this custom component (see in the next step). The $event object, representing the submit event, is passed to submit().


    <element name="person-form" constructor="PersonForm" extends="div">
      <template>
        <div id="message"></div>
        <form method="post" on-submit="submit($event)">
          <input type="text" bind-value="person.firstName" name="firstName">
          <input type="text" bind-value="person.lastName" name="lastName">
          <input type="number" bind-value-as-number="person.age" name="age">
          <input type="submit">
        </form>
      </template>
      <script type="application/dart" src="personform.dart"></script>
    </element>

Step 4: Create the custom element Dart code

If a custom element needs custom behavior, you can implement it with Dart. Each custom element tag you use on the page as an instance of its corresponding class. The submit() method is called when the form is submitted (see the on-submit binding from Step 3). The person and action fields of the PersonForm class get initialized by attribute and data bindings from the usage of the custom element tag.

In this case, we disable the default behavior of form submission so we don't incur a page reload. We use HttpRequest to submit the form data, and update a div inside our custom element with the response text.

import 'package:web_ui/web_ui.dart';
import 'person.dart';
import 'dart:html';

class PersonForm extends WebComponent {
  Person person;
  String action;
  
  submit(Event e) {
    e.preventDefault();
    
    FormElement form = e.target as FormElement;
    form.action = action;
    
    HttpRequest.request(form.action,
          method:form.method,
          sendData:new FormData(form))
        .then((HttpRequest req) {
          host.query('#message').text = req.responseText;
        })
        .catchError((e) => print(e));
  }
}

Step 5: Import and use the custom element

Use a <link> tag to import the custom element. Use the custom element with is="person-form" attribute on a <div>. An instance of person (created in the next step) is bound to the custom element. The action attribute on div is used to set the action via static data binding.

<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="parse_form_submit.css">
    <link rel="import" href="personform.html">
  </head>
  <body>
    <h1>Forms</h1>
    
    <div action="http://localhost:8888/submit" is="person-form" person="{{person}}"></div>

    <script type="application/dart" src="parse_form_submit.dart"></script>
    <script src="packages/browser/dart.js"></script>
  </body>
</html>

Step 6: Create the app's main code

All this app needs is one instance of Person. The instance defined here is visible to the page, and is the same as used in the custom element from Step 5 (person="{{person}}"). The main() method is empty, because Web UI handles all the binding, initialization, etc.

import 'person.dart';

Person person = new Person();

main() { }

Step 7: Write the server

Dart's dart:io library help you write command-line and server-side application. We use the builtin HTTP server functionality to listen for POST requests, parse the form data into a Map, enable CORS headers (so pages from any origin can submit to this server), and send back a string version of the original form data.

import 'dart:io';

main() {
  HttpServer.bind('0.0.0.0', 8888).then((HttpServer server) {
    server.listen((HttpRequest req) {
      if (req.uri.path == '/submit' && req.method == 'POST') {
        print('received submit');
        HttpBodyHandler.processRequest(req).then((HttpBody body) {
          print(body.body.runtimeType); // Map
          req.response.headers.add('Access-Control-Allow-Origin', '*');
          req.response.headers.add('Content-Type', 'text/plain');
          req.response.statusCode = HttpStatus.CREATED;
          req.response.write(body.body.toString());
          req.response.close();
        });
      }
    });
  });
}

Summary

Ta da! You've just used Dart to create a live, data-bound form and wrapped it up in a web component. Then, you wrote a Dart server to parse the form data and return a simple response. Be sure to checkout the Github repo for this post for all the code (as I left out a few things). Is this the best we can do? Probably not, you probably want a more full-featured server-side framework. There are a few in pub.dartlang.org for you to explore.

Learning more

To learn more about Dart, try these resources:

Photo Credit: jasleen_kaur

Popular posts from this blog

Lists and arrays in Dart

Converting Array to List in Scala

Null-aware operators in Dart