Features:
Introducing Pym
Responsive iFrames from the NPR Visuals team
This project was documented and released as part of the first OpenNews Code Convening.
At NPR, we build our biggest projects outside of the CMS so we have complete control over the page. However, for smaller-scale projects—data tables, dynamic graphs and the like—it usually makes more sense to embed a given project within the story it relates to, via the CMS.
Working within a CMS has certain challenges, however. NPR’s CMS, called Seamus, is quite flexible, allowing users to insert page-specific override CSS and blocks of HTML and JavaScript. But there’s always the risk that, over time, changes to the overall site templates will conflict with our code—even break things—creating long-term technical debt.
The ideal, then, is to somehow encapsulate our code and embed a minimum amount of markup within the CMS. Iframes are great for this. We can put all the code for, say, a D3 chart on a standalone page, and then just iframe it into the story page. If NPR.org redesigns, the graphic won’t break or otherwise look strange. And JavaScript within the graphic won’t conflict with JavaScript on the story page.
There is, however, one major point of frustration: the dimensions of the iframe. And within a responsively-designed site, iframe dimensions are especially problematic because you can’t plan for a single height and width. The user’s viewport could be anything.
An iframe’s width can be defined quite flexibly: Set the width to 100%. If the height of your content won’t change as you stretch or shrink your browser window, you can set a fixed number for the height of your iframe and call it a day. (Mostly: iOS sometimes behaves strangely with iframes set to 100%, ignoring the width of the parent container. The fix: Specify a fixed iframe width, rather than a percentage.)
However, if the height of your content changes depending on the width of the iframe (e.g., the text wraps, or you have media queries tied to particular browser widths) or events within the iframe, then you potentially have to account for many possible heights. (Alyson Hurt admits that in the past, she has tried so many unattractive workarounds to this, including: specifying an excessively large height that the iframe content will (likely) never exceed; specifying a fixed height and allowing the iframe to scroll; and writing extremely granular CSS media queries to reset the iframe height depending on the page width.)
Enter Pym.js, the NPR Visuals team’s solution for responsive iframes. Named after a certain superhero who can grow or shrink in size, Pym.js fits the iframe to the parent’s width and the child’s height—and adjusts those dimensions as needed.
About Pym.js
Pym.js started as a fork of the NPR Tech Team’s Responsive IFrames project. Responsive IFrames was the product of an NPR internal hack day. The Tech Team later worked it into a few components of the main NPR.org site, but we had trouble getting it to work within the CMS. Christopher Groskopf refactored the library to remove code specific to legacy browsers we no longer support (specifically, IE8 and earlier), improve the user-facing API and fix a variety of platform-specific bugs.
Then Jeremy Bowers and Tyler Fisher incorporated that library into a larger “daily graphics” workflow: a GitHub repository of code-driven graphics; a process to deploy those graphics to S3; and project-specific code snippets to paste into story pages in the CMS.
We’ve used this workflow for the past three months, revising occasionally as new use cases arose, and it’s worked well so far for us and our users. And we’ve heard from peers who have also been struggling with iframes, responsive templates and CMSes. (And also from peers who have devised their own solutions.) The Knight-Mozilla OpenNews Code Convening in Portland in mid-April gave us the opportunity to further develop the library—and, finally—document it.
What We Did in Portland
First, we completely refactored the JavaScript library from a pair of unrelated scripts to a single, cleanly-namespaced script with a class for the parent and a class for the child. While we had the patient up on the operating table, we removed jQuery as a dependency and fixed several bugs related to having more than one child iframe embedded in the parent.
Next, we updated the user-facing API in order to make the process of embedding iframes more explicit. And to that end, we backported over several real-world examples to demonstrate different ways of embedding iframes and calling updates at various points in the process.
We also did several rounds of browser testing and generated several edge cases that required bug fixes. And we also came up with a catchy new name, courtesy of the New York Times’s Erik Hinton.
Finally, we extensively documented it, with examples taken from actual graphics we’ve produced at NPR using our “daily graphics” workflow (a responsive data table, a D3 line graph and a quiz). Revising and documenting these examples helped us validate assumptions made in the refactored library code and double-check that everything still worked. (Thanks to Ryan Pitts for the GitHub Pages template we used for the documentation.)
How It Works
Pym.js comprises a pair of classes; one for the parent page and one for the child page.
At a high level, Pym.js is passing messages between a parent page and a child page that has been iframed in place. The parent will communicate the page’s width to the child, and the child will resize to that width and then communicate its height back to the parent.
Practically speaking, the parent will communicate the initial size to the child when the iframe is initialized, and then also anytime that the parent page is resized. The child will always respond to the parent’s message. Additionally, the child can send a message to the parent in case its dimensions change as a result of user behavior, e.g., clicking on a button.
The messages passed between parent and child contain two elements; the ID of the iframe that the message is intended for and an integer representing width for messages from the parent to the child or height for messages from the child to the parent.
Since Pym.js works across domains, there’s an additional layer of security in place to ignore messages that come from untrusted domains. When the parent module is initialized, you can pass an xdomain key with a regular expression string as the value. Pym.js will evaluate the regular expression and make sure that messages match this domain.
This is what the code looks like for the simplest use case:
On the parent page:
<div id="example"></div>
<script type="text/javascript" src="pym.js"></script>
<script>
var pymParent = new pym.Parent('example', 'child.html', {});
</script>
And, at the bottom of the child page:
<script src="pym.js" type="text/javascript"></script>
<script>
var pymChild = new pym.Child();
</script>
See working examples and learn more about how it works at the Pym.js project page on GitHub. This approach has worked fairly well for us so far, and we’re excited to share it.
People
Organizations
Code
Credits
-
Jeremy Bowers
Jeremy Bowers is a developer on the Interactive News Team at The New York Times and previously worked for NPR, the Washington Post, and the St. Petersburg Times. He spends his weekends watching baseball and obsessively honing his ramen broth. As a college debater, he spoke more than 600 words per minute. (Photo credit: Claire O’Neil, NPR Visuals.)
-
Alyson Hurt
Alyson Hurt is the graphics editor at NPR, serving on the Visuals team (formerly News Apps + Multimedia). Previously at the Washington Post and the Arizona Republic. Graduate of ASU’s Cronkite School and Georgetown’s CCT program.