Polymer Dart Code Lab: Checkout Form with Autofill

When it's doing its job, Polymer makes your life easier. Ever had to build a form to take payment information? If you haven't, you will, and Polymer comes equipped with custom HTML elements that do all the dirty work for you, even automatically hooking into the user's saved credit card details to autofill the form fields. Complete this code lab to get some experience creating forms the Polymer way.

Note: This code lab's full code is available on Github for those who would like to play with it or use it as a reference.

The code lab was tested with Dart SDK v1.14.1.

Credit

This Polymer Dart code lab was adapted from a similar lab for JavaScript, which was presented at Polymer Summit 2015.

Dart, Polymer, and Setting Up

If you haven't already done the intro code lab, Polymer Dart Code Lab: Your First Elements, I recommend you do so before continuing with this one. At the very least, you should review the first few sections for instructions on how to set up your Polymer Dart development environment. Also included there are links to familiarize yourself with Dart and Polymer in general.

This code lab assumes some knowledge of HTML, CSS, and Dart syntax.

Creating Your Project

To create your project directory for this code lab, follow the instructions in Polymer Dart Code Lab: Your First Elements under the section Step 1: Gotta Start Somewhere.

Code Lab: Checkout Form with Autofill

Step 1: Layout and Backgrounds

First things first! In this step, you'll get your basic layout up and running, in preparation for adding the more serious elements.

Step 1.1: Iron Flex Layout

With Polymer's iron-flex-layout convenience classes, it's easy to make your app fill the browser's display area. These classes are handy wrappers around the flexbox rules.

Import It

You're going to be doing this a lot with Polymer Dart, so pay attention. Your browser doesn't recognize custom elements without help. To teach it the new tricks, you will typically import the definition file for the code you want to use.

Import the layout tools into your Dart app by adding this line just below the other import statements in your main Dart code file, which you'll find in the project's web directory:

web/index.dart

import 'package:polymer_elements/iron_flex_layout/classes/iron_flex_layout.dart';

Now everything iron-flex-layout has to offer will be available.

Use It

Apply the fullbleed CSS class to your app's <body>:

web/index.html

<body unresolved class="fullbleed">

The fullbleed class causes your app to fill all available space in the browser's client area.

Next, add the fit class to the <main-app> custom element:

web/index.html

<main-app class="fit"></main-app>

With this in place, <main-app> will expand to fill its container.

Step 1.2: A Material Background

Import It

Add this import statement to the others at the top of your main app's Dart file:

lib/main_app.dart

import 'package:polymer_elements/paper_material.dart';

If you've downloaded your dependencies correctly, this line will pull in the definition of the <paper-material> element from the polymer_elements package. It's part of Polymer's Paper Elements collection.

Use It

Replace the contents of your main app's <template> tag with the following:

lib/main_app.html

<paper-material class="card" elevation="1">
  Hello, World!
</paper-material>

Using a custom element is a lot like using regular HTML elements, isn't it? There's a tag, and you set some attributes, and maybe give it a few children. This one will be styled to look like a card (CSS class definition pending), and it uses the Google Material Design concept of elevation to set up a shadow.

Style It

Replace your main app element's <style> contents with the following:

lib/main_app.html

:host {
  display: block;
  background-color: #FAFAFA;
  padding: 24px;
  font-family: 'Roboto', 'Noto', sans-serif;
  font-size: 14px;
}

.card {
  background: white;
  max-width: 400px;
  padding: 24px;
  margin: 0 auto;
}

The :host selector allows an element to apply CSS rules to itself, in this case <main-app>. To learn more about styling custom elements, take a look at the styling docs. Note that custom elements default to display: inline, so you'll often see the :host selector used to change that to block when needed.

In case you were wondering why you didn't use a <paper-card> element here if you were just going to style <paper-material> to look like a card, it's because <paper-material> is a lighter-weight solution that doesn't include unneeded features like headers and control bars.

Run It

Run it now to see your app greet the entire world as a friend. If I could get paid to write "Hello, World" apps, I'd be very rich.

hello

Step 2: Create a Form Element

In this step, you'll create a custom element to house your checkout form. Why? Because that's the Polymer way! Breaking your app into small, manageable, encapsulated chunks is the whole point.

Step 2.1: Create the HTML File

Create a new HTML file in your project's lib directory: checkout_form.html. Remove any boilerplate code your development environment may have included, and use this starter code instead:

lib/checkout_form.html

<dom-module id="checkout-form">
  <template>
    <style>
      :host {
        display: block;
      }

      .form-title {
        margin-bottom: 20px;
      }

      .avatar {
        display: inline-block;
        width: 40px;
        height: 40px;
        border-radius: 50%;
        overflow: hidden;
        background: #3367d6;
        margin-right: 20px;
      }

      .company {
        color: #3367d6;
        font-size: 20px;
        font-weight: 200;
      }
    </style>

    <div class="layout horizontal center form-title">
      <div class="avatar" item-icon></div>
      <div class="flex company">ACME Goods Co.</div>
    </div>
  </template>
</dom-module>

This is a standard basic Polymer element definition, and it includes just a little bit of local DOM (inside <template>) and associated styling.

Note you are again using CSS classes from Polymer's iron-flex-layout. On the first <div>, layout, horizontal, and center translate to: "Using flexbox, layout my children horizontally, and vertically align their center points." And flex on the last child <div> means: "Flex to fill as much of my parent container as possible." You will import iron-flex-layout in the next substep.

Step 2.2: Create the Dart File

Create a new Dart file in your project's lib directory: checkout_form.dart. Here's the starting code:

lib/checkout_form.dart

@HtmlImport('checkout_form.html')
library pdcl_form.lib.checkout_form;

import 'dart:html';
import 'package:polymer_elements/iron_flex_layout/classes/iron_flex_layout.dart';
import 'package:polymer/polymer.dart';
import 'package:web_components/web_components.dart';

@PolymerRegister('checkout-form')
class CheckoutForm extends PolymerElement {

  CheckoutForm.created() : super.created();
}

Since Dart is a class-based, object-oriented programming language, every custom element has a Dart class associated with it. Here, you've named it CheckoutForm, though its name really doesn't matter. What does matter is the @PolymerRegister annotation that ties the class to a <dom-module> with an id that matches the argument in parentheses. That <dom-module> should be defined in the file passed to the @HtmlImport annotation.

Step 2.3: Use Your New Component

Using your new <checkout-form> component from within <main-app> is easy.

Import It

Before you can use your new component in main_app.html, you must import it in main_app.dart.

lib/main_app.dart

import 'checkout_form.dart';
Use It

Replace the "Hello, World!" text node with your new <checkout-form> element, like so:

lib/main_app.html

<paper-material class="card" elevation="1">
  <checkout-form></checkout-form>
</paper-material>

With the import done, the browser will know what to do with <checkout-form> when it's parsed. Without it, the browser would pretend it wasn't even there.

Run It

So long, World. We're here to make money, not friends! Behold the beginning of your checkout form:

ACME Goods

Step 3: Add Form Inputs

To build the actual checkout form, you'll make heavy use of Polymer's pre-built custom elements, especially those from the Gold collection, which specialize in gathering data to facilitate commercial transactions.

Step 3.1: Import Polymer Elements

Before you can use them, you must import them. Right? Make sure you are importing each of these elements at the top of your checkout form's Dart file:

lib/checkout_form.dart

import 'package:polymer_elements/iron_form.dart';
import 'package:polymer_elements/paper_input.dart';
import 'package:polymer_elements/paper_button.dart';
import 'package:polymer_elements/gold_cc_input.dart';
import 'package:polymer_elements/gold_email_input.dart';
import 'package:polymer_elements/gold_cc_cvc_input.dart';
import 'package:polymer_elements/gold_cc_expiration_input.dart';

Step 3.2: Start the Form

Finally, you get to see a <form> tag. I know you've been waiting for this. Well, here it comes.

Insert the Form

Place the <form> within your custom element's <template>, after the <style> tag:

lib/checkout_form.html

<form id="form" is="iron-form" method="post" action="/checkout">
  <paper-input name="name" label="Name on card" required
               autocomplete="cc-name">
  </paper-input>
</form>

Whoa! That's a weird form. Why the is attribute? What's an iron-form?

It turns out that if you place custom elements within a standard <form>, it doesn't work quite right. The HTML <form> tag strictly supports only a subset of child elements, and none of yours or Polymer's custom elements are on that list. With a bit of magic, you can upgrade that lousy old <form> into the new hotness that is an iron-form, which can handle any element that implements Polymer.IronFormElementBehavior. (More about behaviors.)

The iron-form element extends the native <form> tag, which is why you use is instead of <iron-form>. The extension allows the element to piggyback on the behavior of collecting values for native elements, while adding additional behavior to work with custom input elements. As usual, the <form> takes method and action attributes to specify how your requests should be sent to a server. For this code lab, we won’t be building a back end, but now you know how to use them in your future apps.

The <papar-input> element has a bunch of interesting attributes, as well. Let's look at them one at a time:

  • name: This is the name of the field that will be submitted as part of the form content.
  • label: This acts like a standard input placeholder until the user starts typing, at which point it transforms impressively into a topside label.
  • required: Its presence indicates that the form should not submit without a value in this field.
  • autocomplete: Use this attribute to specify which part of the autocomplete API the field should hook into. Polymer will handle the dirty details for you. The value cc-name prompts the user for the name on their credit card, if they have it saved in their browser already.
Run It

You won't see any of the autocompletion behavior just yet, as you need to add a few more fields first, but if you run the app now, you can play around with the name field and admire its material design styling.

Name on card

Step 3.3: Using Gold Elements

The rest of the fields in this form could also be <paper-input> elements, but it turns out that Polymer's Gold Collection has several specialized elements that were built specifically to handle credit card info. These Gold elements have built-in support for validation, default labels, and already contain the proper autocomplete attributes, so you'll use those instead.

Credit Card Number

Add this control to your <form> element, after the <paper-input>:

lib/checkout_form.html

<gold-cc-input name="cc-number" required auto-validate
               card-type="{{typeOfCard}}">
</gold-cc-input>

The <gold-cc-input> knows all about credit card numbers. The auto-validate attribute tells the element to check that the entered number is valid. Once that's determined, it will update its card-type attribute with the type of credit card the number represents (Visa, MasterCard, etc.), and the binding there will sync the value with a property on your <checkout-form> element called typeOfCard, which will be used to tell the CVC code input what type of card to validate against. This input will also display a cool graphic of the identified card type.

Bug!

As of this writing, there was still a bug in Dart's polymer_elements package that prevents the tiny credit card images from showing up properly in Dartium. It's possible to hack around it by copying the needed images from your packages folder into the expected paths (as displayed in the debug console), but the issue shouldn't get in the way of successfully completing the tutorial.

Expiration and CVC

The last two pieces of data you’ll need are the expiration date and the Card Verification Value Code (often referred to as the CVC or CVV). Naturally, there are more Gold elements for those fields.

Add the <div> and its contents to your <form> element, just after <gold-cc-input>:

lib/checkout_form.html

<div class="horizontal layout">
  <gold-cc-expiration-input name="cc-expiration" required auto-validate
                            label="Expiration">
  </gold-cc-expiration-input>

  <gold-cc-cvc-input name="cc-cvc" required auto-validate
                     card-type="[[typeOfCard]]">
  </gold-cc-cvc-input>
</div>

These fields are short, so they'll work well displayed on one line with a horizontal layout (thus the <div>). Everything else here should be familiar or self-explanatory by now, except for card-type="[[typeOfCard]]", which is notable for using square brackets to denote a one-way binding. The <gold-cc-cvc-input> doesn't set a card type, like <gold-cc-input> does, but through this binding, it can receive the type. This will help it properly validate the CVC/CVV code, the format of which can vary based on credit card type.

A Little Style

To make things look nice, you need to add a little bit of layout CSS for expiration input. Place these rules in your element's <style> area:

lib/checkout_form.html

gold-cc-expiration-input {
  width: 50%;
  margin-right: 20px;
}
Run It

You've got a nice-looking form now! Note that if you try to fill it out, you may see a warning saying credit card autofilling has been disabled because you’re not on a secure connection (the form is not being served over HTTPS). If you don't see the warning, it's probably because you don't have any credit cards saved to your Chrome profile. If you'd like to add one now, follow these instructions.

CC inputs

To conduct a proper test of the autocomplete functions, you need to test from a real server, which you'll have the opportunity to do later on.

Step 3.4: Make Them Pay

A checkout form isn't much good if the user can't submit, so it's time to add a button.

Add It

You need one last tag in your <form>. Add this <paper-button> as the last child:

lib/checkout_form.html

<paper-button on-tap="submit">Pay</paper-button>

Another thing Polymer makes it easy to do is adding event handlers. When your button enters the DOM, Polymer will handle wiring up a listener for the tap event thanks to the on-tap attribute. Don't worry, that works for clicks too. Creating listeners this way is great, because not only is it easy, but Polymer even handles removal for you, helping to prevent memory leaks.

Soon, you'll create a submit() method that will act as the event's handler.

Style It

Add a few styles for your button inside the checkout form's <style> tag to keep your form from looking too stupid:

lib/checkout_form.html

paper-button {
  background-color: #4285f4;
  color: white;
  margin-top: 50px;
  width: 100%;
}
Wire It Up

As promised, here's the function that responds to tap events on your Pay button. Add it to the CheckoutForm Dart class:

lib/checkout_form.dart

@reflectable
void submit(Event event, Map detail) {
  $['form'].submit();
}

With Polymer Dart, any function that needs to be accessed by the Polymer system needs to be @reflectable. This ensures that the function is available from JavaScript-land.

Since your <paper-button> isn't an official submit button (<input type="submit">), you still need to call the form's submit() method to get things rolling. Within custom elements, any local DOM element with an id has a reference stored in the $ object. This is known as automatic node finding. This saves you from needing to call things like querySelector() all the time.

Listen To It

You could listen to the iron-form-submit event declaratively, like this:

<form on-iron-form-submit="submitForm" ...>

But this is a great opportunity to explore another way to listen for events, using the @Listen annotation. You don't have a back-end script waiting to hear from your form, so instead of processing the user's input, you'll just output an informative message to the browser console for now.

Add this annotated event handler to your CheckoutForm class:

lib/checkout_form.dart

@Listen("form.iron-form-submit")
void submitForm(Event event, Map detail) {
  print("Submitting form.");
}

@Listen("form.iron-form-submit") tells Polymer to handle the iron-form-submit event from a child element with an id of form by running submitForm().

Note that the iron-form-submit event will not be fired if the any of the auto-validating inputs don't have valid entries. Isn't that nice? Yes...yes it is.

Run It

Run your app now to see a local version of your form running. As before, since you're not loading the page over a secure connection, you won't actually see the autofill stuff working, but you can manually type in values and submit. Because there’s no handler for the "/checkout" URL, that part will return an error in your browser console. In a real app, you could send back a response and let the user know whether their payment was accepted or rejected.

Here's a fake credit card number you can use for testing:

fake card number

4000 0000 0000 0002

The final form

Step 4: Try It Out Live

To test out the autofill features, take a look at this version of the form, running with https on Firebase. Don’t worry, none of your information is stored or submitted anywhere, so if you happen to autofill with a real credit card, it’ll be okay. I promise. :)

If you’ve previously saved your credit card in Chrome, it should prompt you to autocomplete as soon as you start typing your name. Note that depending on how you’ve previously saved your card, the CVC number may or may not autofill.

Conclusion

Another code lab in the bag! If you got through the whole thing, you're the best; really, you are. Take an hour to relax, by whatever means is your favorite. I won't judge.

Incidentally, a recent study conducted by Google shows that when forms are properly marked up with browser autocomplete attributes, users tend to complete them about 30% faster! Who doesn't want customers to fork over their hard-earned cash as quickly as physics will allow? So take your Polymer form out there and collect those fees.