HTML & CSS
Article

Building a Credit Card Form Custom Element with Polymer

By Pankaj Parashar

In this article, we’ll be creating a custom element for a credit card payment form using the Polymer library. The final form is going to look something like this,

Credit card form

The markup that defines this payment form is as trivial as this:

<credit-card amount="$300"></credit-card>

Predictably, there is an additional layer of abstraction hidden beneath the face of this custom element that takes care of all the complexities accompanying a typical credit card payment form. Using a custom element helps in isolating any potential future problems and keeps the top layer of markup clean and semantic.

Our credit card payment form will be composed of:

  • An email address
  • A credit card number
  • An expiry date
  • A CVC number

If you have never used the Polymer library before or need to brush up on your knowledge on the basic concepts of web components, then you can first read my previous articles on SitPoint:

These articles should bring you up to speed with the minimal knowledge required to understand this article.

Basic project setup

Before we begin creating our custom element, we will quickly set up the project folder by installing Polymer and all the required dependencies via Bower.

$ bower install --save Polymer/polymer

This will install the Polymer library and the web component polyfills inside the bower_components folder.

bower_components/
├── core-component-page
├── webcomponentsjs
└── polymer

Note that since moving to v0.5.0, the platform.js polyfill has been renamed to webcomponents.js and is now being separately maintained.

Designing the markup

The basic approach for creating a custom element is to first decide on how we are going to use it in our markup, then work our way backwards to create the template of the custom element. In this case, we intend to use our custom element by specifying the amount to be paid using the amount attribute of the <credit-card> tag,

<credit-card amount="$300"></credit-card>

We’ll get this started by creating a credit-card.html file in the root of our project folder and import the polymer.html file that is required to define the custom element.

<link rel="import" href="bower_components/polymer/polymer.html">

We’ll declare our new <credit-card> element using the name attribute of <polymer-element>:

<polymer-element name="credit-card">
  <template>
    <!-- More to come -->
  </template>
</polymer-element>

All the necessary styles and markup needed to design our custom element will sit inside the <template> tag. I have used standard form controls along with appropriate labels for accessibility.

<template>
  <form>
    <fieldset name="personalInfo">
      <label for="email">Email</label>
      <input type="email" id="email" 
             placeholder="email@site.com">
    </fieldset>

    <fieldset name="cardInfo">
      <label for="cardNum" required>Card Number</label>
      <input type="tel" id="cardNum" 
             placeholder="0000 0000 0000 0000">

      <label for="cardExp" required>Expires</label>
      <input type="tel" id="cardExp" placeholder="MM/YY">  

      <label for="cardCVC" required>CVC</label>
      <input type="tel" id="cardCVC" placeholder="***">
    </fieldset>

    <input type="submit" value="Donate {{amount}}">
  </form>
</template>

There’s nothing unusual in the HTML above, except the following line:

<input type="submit" value="Donate {{amount}}">

The values inside the {{ }} are called Polymer expressions. These provide a hook to bind data that’s specified in the Light DOM (while using the custom element) into the Shadow DOM (custom element’s template). In this case, I’m referring to the value specified in the amount attribute of the <credit-card> tag:

<credit-card amount="$300"></credit-card>

And this will be translated to:

<input type="submit" value="Donate $300">

One last thing about defining attributes on a custom element is that you also need to declare them at the top of the primary element before you can use them. This is done as follows:

<polymer-element name="credit-card" attributes="amount">

While specifying multiple attributes on the same custom element, you can either separate them via a space or comma.

Styling the custom element

The styles can be defined either within the template tag like this:

<template>
  <style>
    /* ... */
  </style>
</template>

Or we could write them in a new CSS file and then import the file using the link tag.

In an attempt to make our setup more modular and scalable to handle future enhancements, we’ll use the second approach by creating a new CSS file, credit-card.css, which will contain all the necessary styles for our custom element.

<template>
  <link rel="stylesheet" href="credit-card.css">
  <form>
    [...]
  </form>
</template>

This article focuses primarily on the functional aspect of the custom element, not the design. Styling is pretty standard as long as you are aware of the following points:

  • By default, all custom elements are set as display: inline. Hence, in this case, we had to explicitly declare our element as display: block.
  • :host refers to the custom element itself and has the lowest specificity. This allows users to override your styles from the outside.

Registering the element

Registering the element allows it be recognized as a custom element by a supporting browser. Typically, an element can be registered directly by calling the Polymer() constructor,

Polymer([ tag-name, ] [prototype]);

Where:

  • tag-name matches the name attribute on <polymer-element>. This is optional unless the <script> tag that calls Polymer is placed outside the <polymer-element> tag.
  • [prototype] contains the public properties and methods used to define the behavior of custom element.

The simplest way to invoke Polymer is to place an inline script inside your <polymer-element> tag:

<polymer-element name="tag-name">
  <template>
    [...]
  </template>
  <script>Polymer();</script>
</polymer-element>

For elements that don’t require custom properties or methods, you can use the noscript attribute:

<polymer-element name="tag-name" noscript>
  [...]
</polymer-element>

For the sake of brevity, we’ll register our custom element by explicitly using the tag name in the following way:

<script>
  Polymer('credit-card', {
    /* More to come */
  });
</script>

Using JavaScript libraries with Custom Elements

In a practical setup, the behavior of a custom element could depend on an external JavaScript library. This section will take you through a basic example of using external JavaScript files in conjunction with your custom element.

Let’s improve the payment experience for our users by:

  • Formatting the credit card number into groups of 4 digits with the total length being no longer than 16 digits.
  • Formatting the expiry date as MM/YY.

We’ll use the excellent jquery.payment library by Stripe that solves these usability issues and in turn needs the jQuery library to work. In nutshell, we’ve got two scripts to be included right before the closing <polymer-element> tag, as we do not really need any JavaScript while loading the custom element.

<polymer-element name="credit-card" attributes="amount">
  <template>
    [...]
  </template>
  <script src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
  <script src='//cdnjs.cloudflare.com/ajax/libs/jquery.payment/1.2.1/jquery.payment.min.js'></script>
  <script>  
    Polymer('credit-card', {});
  </script>
</polymer-element>

The Polymer library provides a bunch of usefull callback methods to inject custom behavior at different stages in the lifecycle of the creation of a custom element:

  • created – An instance of the element is created.
  • ready – An instance of the element was inserted into the DOM.
  • detached – An instance of the element was removed from the DOM.
  • attributeChanged – An attribute of the element was added, removed, or updated.

More info about the lifecycle methods is available in the API developer guide

<script>  
Polymer('credit-card', {
  ready: function() {       
    var cardNum = this.$.cardNum,
        cardExp = this.$.cardExp,
        cardCVC = this.$.cardCVC,
        creditCard = this.$.creditCard,
        $cardNum = jQuery(cardNum),
        $cardExp = jQuery(cardExp),
        $cardCVC = jQuery(cardCVC),
        $creditCard = jQuery(creditCard);

    /* Formatting input fields */
    $cardNum.payment('formatCardNumber');
    $cardExp.payment('formatCardExpiry');
    $cardCVC.payment('formatCardCVC');

    /* Card validation on Form submission */
    $creditCard.submit(function(e){
      var cardExpiryVal = $cardExp.payment('cardExpiryVal'),
        cardType = jQuery.payment.cardType($cardNum.val()),
        isValidNum = jQuery.payment.validateCardNumber($cardNum.val()),
        isValidExp = jQuery.payment.validateCardExpiry(cardExpiryVal),
        isValidCVC = jQuery.payment.validateCardCVC($cardCVC.val(), cardType);

        if (isValidNum && isValidExp && isValidCVC) {
          /* Success */
        }
        else {
          /* Validation failed */
        }
    });
  }
});
</script>

Polymer provides a convenient way to automatically find nodes inside the Shadow DOM using this.$.id. $ of course is a shorthand alias for jQuery. Hence, to avoid any potential conflicts, we have used the identifier jQuery throughout the code. Alternatively, if you want to find nodes using a class name instead of an ID, then you’ve got two options:

Access the Shadow DOM directly using:

this.shadowRoot.querySelector('.classname');

Or add an identifier (#container) to the custom element and then use:

this.$.container.querySelector('.classname');

Using the Custom Element

Include the credit-card.html file in your document and then using the custom element is as simple as writing:

<head>
  <link rel="import" href="credit-card.html">
</head>
<body>
  <credit-card amount="$300"></credit-card>
</body>

Chrome and Opera now fully support web components in their latest versions. IE, Firefox, and Safari, however, are still lagging behind. To enable support for these browsers, you need to include the webcomponents.js polyfill as well:

<head>
  <!-- Load platform support before any code that touches the DOM. -->
  <script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <link rel="import" href="credit-card.html">
</head>

Check out the complete code along-with the demo on Plunker. I’ve also set up a project repository on Github for the credit card custom element.

Conclusion

This isn’t a functional credit card payment form and far from even complete. However, the demo should be good enough to give you a jump start to build your own design components in the form of custom element using the Polymer library.

Comments
brad7928

Surely this is a typo?

if(isValidNum || isValidExp || isValidCVC)
/* Success */
else
/* Validation failed */

Wouldn't you want all the be valid, not just one of them?

if(isValidNum && isValidExp && isValidCVC)
/* Success */
else
/* Validation failed */

Otherwise, great article! Was a good read.

louislazaris

Thanks @brad7928, I've corrected it. One of us should have caught that, good eye.

younesrafie

Really good explaination. Great job (y)
Q: is there a generator for yeoman to combine and build the component to avoid calling external files (css, js)?

pankajparashar

Thanks for the correction @brad7928 . I clearly missed it and should've caught it earlier.

pankajparashar

Yes @younesrafie there is! The generator-polymer is a nice little build tool to scaffold your Polymer app and then concatenates your assets and HTML Imports into a single file, thus avoiding multiple HTTP requests. I plan to write about it soon!

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Front-end, once a week, for free.