HTML & CSS
Article

Building a Pull-Quote Custom Element with Polymer

By Pankaj Parashar

Custom elements allow developers to define their own HTML elements with custom tag names that closely resemble their design components. The W3C defines custom elements as the specification that:

Enables authors to define and use new types of DOM elements in a document.

The definition looks quite simple in theory, but in practice the process of creating and using a custom element involves:

  1. A decent amount of JavaScript
  2. Knowledge of the JavaScript API(s) to create the custom element
  3. Handling cross-browser inconsistencies.

If you manage to survive through these challenges, then you still have to worry about those browsers that do not support custom elements. Thankfully, Polymer saves the day by providing an easy-to-use framework to create and use custom elements.

If you need a quick refresher on Polymer and Web components then I would strongly recommend reading my previous article on SitePoint that introduces these concepts in depth.

In this article, we will take a very basic example of creating a custom element with Polymer to render pull quotes. The idea originated from this article by Louis Lazaris where he sparked a debate on the semantic value of using blockquote vs aside to create pull quotes in your markup.

Marking up pull-quotes

It’s fairly common to see blockquote, aside, or even a plain div element being used to mark up pull quotes. It’s time we put an end to this conundrum by creating a new custom element called <pull-quote> that can accurately represent our content. By the end of this article you will be able to create something like this:

Custom pull-quote element

Setting Up the Custom Element

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

$ bower install --save Polymer/polymer

Typically, creating a new custom element involves the following steps:

  1. Create an HTML file with the name of the custom element.
  2. Import the polymer.html file from the bower_components folder using <link>.
  3. Declare your custom element using <polymer-element>.
  4. Add the styles and the markup needed to define your custom element using the <template> tag.
  5. If your custom element involves JavaScript, call the Polymer() constructor to register the element in the DOM.

The basic approach is to first decide how you are going to use the custom element in your markup and then work your way backwards to create the template for the custom element. In this case, we plan to use the following markup every time we intend to use pull quotes in our document:

<pull-quote>Sample text and can even contain other HTML tags.</pull-quote>

We’ll start by creating a file called pull-quote.html in the root directory. Edit the file and import the polymer.html file that can be found in the bower_components folder:

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

We’ll declare our new <pull-quote> element using the name attribute of <polymer-element> in the following way:

<polymer-element name="pull-quote" noscript>
    <!-- More to come -->
</polymer-element>

Couple of points to note:

  1. The name of the custom element must contain a hyphen (U+002D HYPHEN-MINUS) character to allow the HTML parsers to differentiate custom elements from regular HTML elements.
  2. The noscript attribute indicates that this element doesn’t need any JavaScript to function and hence can be immediately registered in the DOM.

Adding the Content

The content that makes up the custom element is defined inside the <template> tag. The <template> tag provides separation of presentation from content. In our case, anything we write inside the <pull-quote> tag would make up the content of the custom element and the styles applied to it would form the presentation.

In nutshell, there are three types of DOM elements that we are dealing with:

  1. Light DOM – This includes what we write inside the custom element. Hence, the user of the custom element supplies the Light DOM.
  2. Shadow DOM – This includes everything that we define within the <template> tag. It is internal to the element and encompasses everything that is required to make the custom element work.
  3. Composed DOM – This is what the browser actually renders. For rendering, the light DOM is distributed into the shadow DOM to produce the composed DOM.

Types of DOM elements in a Custom Element

The <content> tag represents the insertion point where the Light DOM content would be injected into the Shadow DOM. The <content> tag has much more advanced usage in selecting only specific content that you write inside the custom element. However, for the sake of this example we’ll stick to the basic usage of this tag.

<polymer-element name="pull-quote" noscript>
    <template>
        <content></content>
    </template>
</polymer-element>

Styling the Custom Element

For styling our custom element, Polymer provides a bunch of useful selectors. For example:

  1. :host – Targets the custom element itself.
  2. :host(<pseudo-class>) – Targets the various states of the custom element like :host(:hover), :host(:active) etc.
  3. :host(<class-name>) – Targets only those custom elements that have the supplied class name.
  4. :host-context(<selector>) – This pseudo class targets the host element if it or any of its ancestors matches <selector>. This is particularly useful for theming when you apply theme-related classes on <html> or <body> tags.

Pro tip – If you simply use selectors (like, p, #id, or .class) without the :host then they would automatically become descendent selectors. For example, to target a paragraph tag inside the <pull-quote> we can simply use, p { ... } and then it will automatically be expanded as pull-quote p { ... }.

We can either choose to style our custom element using the <style> tag:

<template>
    <style>
        :host {
            display: inline-block;
            font-style: italic;
            text-align: justify;
            width: 325px;
            line-height: 30px;
        }
    </style>
    <content></content>
</template>

Or we can write our CSS in a new file like pull-quote.css and then import that file into our pull-quote.html file using <link>:

<link rel="stylesheet" href="pull-quote.css">

A couple of points to note:

  1. By default, all custom elements are set as display: inline. Hence, we have to explicitly declare display: inline-block.
  2. The :host selector has the lowest specificity. Hence, users can override your styles from outside the custom element.

At the time of writing, using pseudo elements (::before and ::after) on the :host selector like :host::before {...} does not work as expected in Blink browsers (Chrome and Opera). This appears to be a known bug that has been fixed and will be shipped soon with Chrome 38. But this shouldn’t stop us from using them altogether.

To surround our pull-quotes with actual quotes, we’ve got three options.

Adding Quotes with :host Pseudo-Elements

The first option is to use :host::before and :host::after pseudo-elements from within the custom element to add double-quotes:

:host::before {
  content: '\201C';
}
:host::after {
  content: '\201D';
}

As described above, this would work only in non-Blink browsers. If you could afford to wait till the other browsers fix the bug then stick with this method.

Adding Quotes with Regular Pseudo-Elements

You can also use pull-quote::before and pull-quote::after pseudo-elements to add double-quotes from outside the custom element.

pull-quote::before {
  content: '\201C';
}
pull-quote::after {
  content: '\201D';
}

This works in all browsers but may not be the most ideal solution keeping in mind that the styles for the same custom element are now fragmented in two different files.

Adding Quotes with Extra HTML

The last option is to use HTML to add double quotes in the template itself:

<template>
    <span>&quot;</span>
    <content></content>
    <span>&quot;</span>
</template>

This works in all browsers too! Additionally, you could also style the quotes with CSS that can then sit inside the <template> tag.

Using Our Finished pull-quote Element

Make sure you add the platform.js polyfill in the <head> of your document. This polyfill will ensure that your custom elements continue to work as expected even in browsers that do not support them.

<script src="bower_components/platform/platform.js"></script>

To use the custom elements, we do exactly what I described in my previous article:

  1. Download the Custom Element package via Bower.
  2. Import the corresponding .html file in your document.
  3. Use the custom element markup anywhere in your document.

    <html>
        <head>
            <script src="bower_components/platform/platform.js"></script>
            <link rel="import" href="pull-quote.html">
        </head>
        <body>
            <pull-quote>Sample text inside pull quote</pull-quote>
    
            <pull-quote>
                <p>Can also contain other tags</p>
            </pull-quote>
        </body>
    </html>

Check out the complete code along-with the demo on Plunker. I’ve also setup a project repository on Github for the pull-quote custom element. And if you want more, CustomElements.io, a project by Zeno Rocha has a useful collection of custom elements created using Polymer, X-Tag, etc.

The Polymer library provides a vast amount of powerful features and APIs to interact with custom elements and other web components. We’ve barely touched the topic and still have a lot of features to explore. However, I am hopeful that this introduction would be simple enough for everyone to understand and serve as a springboard to build many more new custom elements.

  • Kelderic

    Here’s my problem with this. Instead of all that work, I could instead just write in my css file:

    pull-quote {///styles}

    Then I could use my pull-quote anywhere. I can even get IE8 support just be creating the element via one line of Javascript.

    • LouisLazaris

      Admittedly, that’s not an easy question to answer. The fact is, in HTML, you can pretty much do whatever the heck you want. Of course, accessibility pundits will say that would be bad for semantics and accessibility. But the fact is, many of the pre-defined HTML elements themselves are bad for accessibility. So I don’t think that’s much of an argument.

      All that being said, I think the spec for custom elements kind of answers your question. It says:

      Though it was long possible to create DOM elements with any tag names in HTML, these elements weren’t very functional. By giving Web developers the means to both inform the parser on how to properly construct an element and to react to lifecycle changes of an element, the specification eliminates the need for DOM-as-a-render-view scaffolding that has to exist today in most web frameworks or libraries.

      So the main benefit to doing it the new standard way is for creating “functional” elements. The example in the article above, of course, is not very “functional”, so it might not be the best choice. But when you incorporate scripts into the elements, that end up being packaged as portable modules, then you can see the benefits a little more.

      I’m sure many of these benefits will become more clear as this stuff gets used more, but for now that’s about the best answer I can provide.

      • Kelderic

        Those are very good points. While it’s super easy to make a renamed block container, making more complex elements are where this method is going to shine. However, I normally just more complex code into PHP includes, which lets me create reusable templates already.

      • CrashNBurn71

        So you say this particular example is a poor one, and the article itself provides zero reasons why anyone would *want* to do “all this work” beyond, “well you can”, slightly nicer mark-up (less scaffolding) and supposed re-usability?

        How is a handful of CSS classes and a HTML/php template not “re-usable” ?

        This sounds more like trying to stuff more XML-like ideals down HTML’s throat. Yet seems far less robust than XSLT and far more complicated.

        • Pankaj Parashar

          I don’t think Louis’s comment implied that this is a ‘poor example’. He was simply replying to Kelderic’s question by stating that this example is not best suited to represent what all things custom elements can do. This was however, the most basic example that you could use to start building your own custom element.

          That being said, if HTML provides native capabilities to create custom DOM elements, there is no need to involve additional technology stack like XSLT, XML in your workflow. This makes things much easier. Agreed that the setup process is slightly convoluted but that is destined to change in the coming years once the support becomes better.

          After all, web is evolving and things are rapidly changing, so you’ll always have old technology being phased out in favor of new ones.

        • LouisLazaris

          I’m not completely sold on Web Components myself yet. But not every article on every subject needs to be immediately applicable.

          For example, don’t many programming tutorials start with a “hello world” example? That’s as useless as it gets when it comes to examples, but it’s a starting point. Nothing wrong with simple examples to teach the concepts, but then move on to other things once you have an understanding.

    • LouisLazaris

      Here’s some more info on this:

      If you just create your own element and do the single-line of JS thing, as pointed out in this article, the element will inherit from the HTMLUnknownElement Interface instead of the HTMLElement interface. This, apparently, will have some limitations.

      I think this topic would be a good one as an article, so I might have someone write it at some point.

      • http://pankajparashar.com/ Pankaj Parashar

        That’s correct however, just FYI it is still possible to override this behaviour by mutating the prototype of a newly created custom element from HTMLUnknownElement to HTMLElement and then eventually overcome all the limitations.

        This however will need a lot of JavaScript!

    • http://pankajparashar.com/ Pankaj Parashar

      Creating a custom element like you described above can work for simple design components like `pull-quote`. But imagine building a complex design component like a twitter profile card, that contains bunch of div(s), span(s), anchor(s) and img(s) to create a single card.

      1) You could either chose to repeat the markup everytime you intend to create a profile card, OR
      2) Attempt to create the custom tag for profile card with the way you described above, which will definitely need much more than just a single line of JavaScript!

      or better
      3) Define a custom element once with all the necessary markup alongwith the styles and scripts needed to make it work and then use it anywhere and everywhere you need a profile card.

      That being said, your’re argument is compelling when we think of examples like `pull-quote`. However, the intent of the article is to provide a simplest possible example of how to create a custom element. But, there is no limit to the amount of complex design components that you can create with the simplest possible markup using the powerful features of custom elements.

  • http://thietkenoithat.com.vn/ hue pham

    Mình đã bỏ nghề code mất rồi, lâu rồi không code tự nhiên nhìn đoạn mã code của anh mà lại muốn quay trở về làm design website quá.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

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