Going POSTal

Going postal!

So it seems, that people like their client-side applications to be able to talk to their servers. Strange what people like to do these days.

As it so happens, there are a few ways to accomplish this in Dart. Each has some pros and cons associated with it, and which one you want to use will really depend on how you’re displaying and collecting information.

HttpRequest.request()

To start off with, we’ll look at a use-case where your app is primarily based off of the dart:html libraries. In cases like this, where we do not have any data-binding to each of fields in the Form, it’s sometimes easier to just send the entire Form itself. First here’s a quick look at the code for our extremely simple form:

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <title>Postal</title>
    <link rel="stylesheet" href="postal.css">
  </head>
  <body>
    <h1>Postal</h1>
    
    <form id="myForm" action="send/">
      <label>First Name:<input type="text" value="" name="firstName" id="firstName"></label><br>
      <label>Last Name:<input type="text" value="" name="lastName" id="lastName"></label><br>
      <label>Email: <input type="email" value="" name="email" id="email"></label><br>
      <input type="Submit" value="Submit">
    </form>

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

The easiest way send all of that information is to bundle it into a FormData class. This will automatically associate the values with the name attribute in the FormElement and pass that mapping to the server in our request.

Lets take a look at what our Dart code looks like and see what exactly is going on here:

import 'dart:html';

void main() {
  var myForm = querySelector('form#myForm') as FormElement;
  myForm.onSubmit.listen((e) {
    e.preventDefault(); // Don't change page when the form is submitted.
    
    var data = new FormData(myForm);
    HttpRequest.request(myForm.action, method: 'POST', sendData: data)
      .then((resp) {
        // Do something with the response from the server.
      });
  });
}

First we need to get our FormElement and assign it to a varable, myForm. Next we need to listen for an on-submit event from the form. When that event occurs the first thing we want to do is prevent the default action, which is to redirect to whatever page is in the action attribute of the form (or reload the current page). This is a Single Page Application though! We don’t need no stinkin’ redirects!

Now we need to get our data that we want to send with our request. As previously mentioned, since we want to send the entire form and don’t want to have to query each and every input field it contains, it’s just so much easier to send the entire form. Thus we use the FormData constructor, passing our FormElement, myForm as the constructor (due to Dart’s awesome scoping rules, we can use our variable name inside a closure on the variable itself!)

Now that we have the data, we just need to send it. There is a static method on the HttpRequest, named request. This method will return a Future with an HttpRequest which will represent our response from the server once the request has completed. You’ll note the above sample is for demonstration only and does not include any error checking or any of that good stuff we should have. To our request method we pass myForm.action as the URL of where the request should be sent to. I find it is often best to use the action attribute of the form to store the URL of the request as its often much easier to find that way should it need to be updated later. Additionally we pass the named parameter method: 'POST'. By default the request method will issue a GET method query. Since we’re trying to send data, not get data, we want to make sure we properly use POST (plus, if the request is a GET request, the next argument is ignored per HTTP/1.1 protocol.) Finally we use the parameter to send the data sendData: data.

Using the asynchronous tools that Dart provides, we then() wait for the response to complete. Once we have that we can do whatever we need, be it verifying the data was submitted, error codes from the request, etc. As you notice this sample is pretty short and concise. However it does have its drawbacks. Depending on what you’re using for a backend server, it may not recognize the default content-type generated by the dart request. This means we may sometimes have to supply a different argument to specify the requestHeaders.

But all-in-all the FormData is a quick and easy way to send our form information to the sever. Of particular importance is to remember to apply the name attribute to your input elements or you may have difficulties accessing your data! 😉

HttpRequest Instance

Since it’s along the same lines, we’ll next look at using a full instance of the HttpRequest. In the background, HttpRequest.request() is actually just a static function which creates the full instance of HttpRequest automagically for you and only passes back the response to you in a Future.

The HTML source will stay the same as above. Find below the new dart source:

import 'dart:html';

void main() {
  var myForm = querySelector('form#myForm') as FormElement;
  myForm.onSubmit.listen((e) {
    e.preventDefault(); // Don't change page when the form is submitted.
    
    var data = new FormData(myForm);
    var req = new HttpRequest();
    
    req.open('POST', myForm.action);
    req.onLoad.listen((event) {
      var resp = event.target as HttpRequest;
      // Do something with the response from the server.
    });
    req.send(data);
  });
}

You’ll notice that generally its pretty similar to using the static request method. First we attach a listener for the on-submit event for the form. In which we also prevent the default action. We also wrap our data in a FormData instance as well. As opposed to sending each of our configuration values as parameters, we now need to apply them each individually to the request.

Note that the majority of settings on an HttpRequest instance need to be applied after we have called open, but before we call send. In the above example, we open our request passing the method (which is now a required positional argument and not optional named argument), and our URL. open Also accepts a few additional named parameters, one of interest is a boolean async which is true by default. If we pass false to this then the entire request will be completed synchronously and will block on the request. Please, please, please use this sparingly!

Next we need to add a listener to our onLoad property. Again we want to do this before we send the request to ensure that there’s no opportunity for the callback to be missed between the sending and the assignment of the listener. And finally we send our request including the attached data.

Using an HttpRequest instance is much more verbose way of creating a request however it is also much more powerful, allowing for more customization and control over your request.

HttpRequest.postFormData

Finally we reach my preference when it comes to sending data. The static method postFormData. This method is particular useful when you’re using binding to automatically link your variables with the values in the input fields. Some great examples of this are with Polymer or Angular.

First lets look at the code. In this case I’m using the Polymer library to easily create my own custom element which contains its own form. I won’t get into too much detail about the Polymer stuff as that’s not the goal of this post:

Main/index file:

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sample app</title>
    <link rel="stylesheet" href="bindingpostal.css">
    
    <!-- import the click-counter -->
    <link rel="import" href="myform.html">
    <script type="application/dart">export 'package:polymer/init.dart';</script>
    <script src="packages/browser/dart.js"></script>
  </head>
  <body>
    <h1>BindingPostal</h1>
    
    <div id="sample_container_id">
      <my-form></my-form>
    </div>

  </body>
</html>

My Polymer element HTML file:

<!DOCTYPE html>
<polymer-element name="my-form" attributes="count">
  <template>
    <style>
      div {
        font-size: 24pt;
        text-align: center;
        margin-top: 140px;
      }
      button {
        font-size: 24pt;
        margin-bottom: 20px;
      }
    </style>
    <form id="myForm" action="send/" on-submit="{{ save }}">
      <label>First Name:<input type="text" value="{{ firstName }}" id="firstName"></label><br>
      <label>Last Name:<input type="text" value="{{ lastName }}" id="lastName"></label><br>
      <label>Email: <input type="email" value="{{ email }}" id="email"></label><br>
      <label>Cool: <input type="checkbox" checked="{{ isCool }}" id="email"></label><br>
      <input type="Submit" value="Submit">
    </form>
  </template>
  <script type="application/dart" src="myform.dart"></script>
</polymer-element>

And the Dart code for the Polymer element:

import 'dart:html';
import 'package:polymer/polymer.dart';

@CustomTag('my-form')
class ClickCounter extends PolymerElement {
  @observable String firstName = '';
  @observable String lastName = '';
  @observable String email = '';
  @observable bool isCool = false;

  ClickCounter.created() : super.created() {
  }

  void save(Event e, var detail, Node node) {
    e.preventDefault();
    
    var form = e.target as FormElement;
    var data = this.toJson();
    
    HttpRequest.postFormData(form.action, data).then((resp) {
      // do something with the response.
    }); 
  }
  
  Map<String,String> toJson() {
    return {
      'firstName' : firstName,
      'lastName' : lastName,
      'email' : email,
      'isCool' : '$isCool',
    };
  }
}

So a few things to note first regarding the class for my element. I added a method toJson which returns a Map of Strings and Strings. This is important as the postFormData method does not allow non-String values. As a demonstration I added a boolean to our form isCool. This value is String interpolated in the toJson method.

Our save method is automatically bound to the onSubmit event on our Polymer Element so no worries about attaching listeners here! Since the binding is on the form itself, that will be the target of the event that is passed to the method. The other arguments we don’t really need in this case.

As usual, we prevent default. We also get the FormElement so we can easily pass the action property to the postFormData static method. We also initialize data to be the Map of String which represent our form values. You’ll notice the HTML markup for the input elements do not require the name attribute.

Similar to HttpRequest.request we pass our URL and our data. In this case we already know it’s a POST request so there’s no need to specify that. postFormData also returns a Future as similar to request which provides an HttpRequest instance of the response from the server.

Some additional nice features about this method is that it automatically applies the Content-Type: application/x-www-form-urlencoded; charset=UTF-8 header, which helps servers on the backed to properly recognize the request and handle it (I’m looking at you PHP!).

So as you can see there are a number of ways to send the data to the server using the HTTP/1.1 POST method. Which one you want will depend on your code-base and usage. When in doubt, post (haha get it?) questions to the Dart tag on Stack Overflow.

This entry was posted in Dart and tagged , . Bookmark the permalink.

Have Something To Add?

Loading Facebook Comments ...
Loading Disqus Comments ...