Made with Atavist? It’s made with Polymer, too.
How we’re using web components in production
In the beginning, floating over the void, were the elemental media types: image, video, audio. In any CMS worth its salt, you could click a button, upload your file, publish your post, and see the result. And it was good enough.
A year ago, when we at Atavist set out to rethink our CMS, we looked around the web and saw a lot that wasn’t so elemental: interactive charts, data-rich maps, photos that didn’t march lockstep with text but glided gracefully in and out of view. Designers and developers at the world’s best publications were expanding the web’s repertoire. We wanted to help Atavist users keep up.
But it wasn’t so simple, technically speaking. We were asking a lot of blocks. They had to work well in the Atavist editor as they’re added, dragged, realigned, and configured. Blocks would have styles of their own, but needed to adapt to the layouts and visual environments into which they’re published. A block also needed some kind of interface, something that would tell the world which of its aspects were configurable and that would then facilitate that configuration, allowing users to add points to a map or change the font size of a pullquote.
Finally, thinking practically, blocks had to be easy to develop and maintain, both for our benefit and for the eventual benefit of third-party developers who might want to make their own. That suggested they take the form of discrete, concise units rather than scattered splotches of code.
We could have chased down these problems individually. Early on, though, we locked on to an idea that got at all of them, an idea that made its way to production when we launched the new Atavist in March and one that’s been serving us well since: Blocks would be web components, built with Polymer.
Behold, a map. It’s of Dumbo, Brooklyn, home to Atavist, Inc. and a perfectly nice place to pay $15 for a sandwich.
If you fire up your browser’s developer tools and inspect that map, you might be surprised by what you see.
It’s a custom element, with an identity, contents, and a set of behaviors all its own. Such elements are the heart of the web components spec, which has three parts (or four, depending on whom you ask):
- Custom elements: Define entirely new HTML elements.
- Shadow DOM: Isolate elements that lie inside a custom element, so that certain styles can be sheltered from those defined on the parent document.
- HTML imports: Load custom element definitions and other dependencies declaratively in markup.
Alongside which you’ll sometimes see this:
- HTML templates: Declare a bundle of reusable markup, which can be cloned and used inside a custom element.
These are early days for web components. To many web developers in 2015, seeing something called “atavist-simple-map” in a stream of otherwise generic div and span tags is what it must have been like in 2011 to see a 🍔 in the midst of an otherwise normal string of characters. Novel, certainly. But how useful are these custom elements, really?
Quite useful, in fact. The ability to give an element its own HTML tag name is just the most visible piece of the custom element spec: it’s flashy and fun, but it’s arguably more a philosophical stake in the ground (web components let developers extend the web platform!) and implementation detail (a solution to custom element namespacing) than a compelling feature. More important are the so-called lifecycle callbacks, which allow developers to execute functions at key moments of a custom element’s life: when it’s first created, when it’s added and removed from the DOM, and when the value of one of its attributes has changed. These callbacks suggest a way of thinking about custom element behavior: an element boots itself up in the “created” and “attached” callbacks, and it responds to changes in the “attributeChanged” callback. It’s a simple model, one that Polymer picks up and runs with.
In the last two years or so, Polymer has undergone quite a transformation, hatching as an 0.x “developer alpha,” pupating into something more stable and with fewer bells and whistles, and then emerging, at this year’s Google I/O, as a production-ready 1.0 release. The trajectory of this evolution, in which Polymer was busy defining itself, roughly coincided with the rise to prominence of the framework within the web development community, so much so that comparisons to existing libraries, rather than the thing itself, seemed to frame the collective understanding of Polymer’s purpose. Many a blog post, Stack Overflow response, and discussion thread was spawned: on Polymer vs. Angular, Polymer vs. React, Polymer vs. (of all things) Bootstrap.
Some posts in this genre offered some fine practical guidance, and there really are some meaningful overlaps, between Polymer and React in particular, both of which concern themselves with the life and times of components specifically. But Polymer isn’t really a framework, and I don’t think it’d describe itself as React does, as “the V in MVC.” It’s fundamentally a convenience layer that sits atop the web components spec, providing comfortable handholds to those defining and manipulating custom elements.
Defining a custom element in Polymer is a two-step dance. First comes what Polymer calls the declarative piece, which establishes what markup appears inside an element. Here’s our sidebar block, something nice and simple:
That “dom-module” element is a Polymerism, one that facilitates the stamping of the markup appearing in that “template” tag inside any “atavist-sidebar” element that appears on the page. Notice the “atavist-text” element used here — a custom element inside a custom element! (For most uses of Polymer, by the way, you’d see the element’s styles defined in or around the “template” tag here. We use a build process that handles that a little differently. Describing how that works is out of the scope of this post, but it’d make for a worthwhile follow-up. Stay tuned!)
The object passed in to that Polymer function is the custom element’s prototype, defining its name (“atavist-sidebar”), some properties, and some methods. Chief among these are the lifecycle callbacks. The callbacks defined in the web components spec — “created,” “attached,” and “detached” — are available for use, but Polymer adds another, “ready,” that’s particularly useful. It fires after an element’s local DOM — the bundle of elements defined inside it — has been generated from a template and stamped into the element, and after any Polymer elements found within have fired their ready callbacks. That means the guts of the element are primed and in place, ready to be queried and manipulated as needed.
Also important, especially to our use of Polymer, are the element’s declared properties, the values of which can be set as attributes in markup and then manipulated on the fly. These properties determine, in essence, an element’s public API. Here, for instance, are the declared properties of our Twitter block, with which a user can embed a Twitter post, based on its URL, and provide a caption:
Each property can in turn have an “observer,” a method defined on the element prototype that’s executed whenever Polymer notices that the property’s value has changed. The Twitter block, for instance, uses “tweet_urlChanged” to fetch fresh embed markup whenever a user specifies a new URL.
The property and observer model fits perfectly into Atavist’s editorial environment. Early in the development of what would become blocks, we spent some time discussing how we’d develop editing UI for each and every block type we wanted to produce — a prospect that, conceivably, could have been a chore. In working with web components, a clear answer emerged: properties, if we could carry out the simple task of mapping each to a standard dropdown menu, text field, or other input, were the interface.
Here’s that map again:
In the Atavist editor, when working with a map like this, you’ll see a dropdown menu labeled “Map theme,” with options like “Basic,” “Black and White,” “Satellite,” and “Terrain.” The dropdown corresponds to the map block’s “map_style” property. When a user chooses a new map theme from the menu, that value is pushed to the block. To see for yourself how this works, fire up your developer tools and stick this in the console (I’ve given the above map block an ID, for convenience’s sake):
This will return the property’s current value: “satellite.” Now, keeping an eye on the map, try this:
Fun, right? The block’s “map_style” property is calling its observer method, “map_styleChanged,” which does what’s necessary to change the look of the map tiles. You can go ahead and try other values, too: “black_and_white,” maybe, or “terrain” (which isn’t so impressive in mountain-free Brooklyn).
This, in a nutshell, is how blocks work. A custom element is added to the page; a user fiddles with its properties, seeing the results of that fiddling live; and then, when it comes time to a save and quit, the current values of all a block’s properties are reflected up as element attributes, like this:
Actually, that’s a simplified example. The real thing, with all the requisite map-specific properties, including a few blobs of stringified JSON, looks like this:
It’s attributes like these that save to the database all the ways in which a user has configured a particular block. When that user re-opens a project to begin editing again, or when a published article is loaded in a reader’s browser, this element begins life anew, drawing in its local DOM from the matching “template” element, executing its initializing lifecycle callbacks, and reading in each of its declared properties, firing off observer callbacks as it goes.
Try it yourself
If you’ve got an Atavist account, you can experience web components for yourself. Create a new project, drag in a few blocks, and pay attention to the elements being added and manipulated.
(If you don’t have an Atavist account, get one. It’s free.)
Anyone interested in Polymer should head on over to the official docs. There’s a lot there to take in, including many facets of Polymer left unmentioned in this post.
Finally, those eager to get his or her hands dirty actually making custom elements should consider starting with a custom block, a feature we quietly released to developer-tier users a week or so ago. If you’re not a developer-tier user and just want to play around, send us a quick email, mention this post, and we’ll give you what you need to get started.