About Pym.js

Using iframes in a responsive page can be frustrating. It’s easy enough to make an iframe’s width span 100% of its container, but sizing its height is tricky — especially if the content of the iframe changes height depending on page width (for example, because of text wrapping or media queries) or events within the iframe.

Pym.js embeds and resizes an iframe responsively (width and height) within its parent container. It also bypasses the usual cross-domain issues.

Use case: The NPR Visuals team uses Pym.js to embed small custom bits of code (charts, maps, etc.) inside our CMS without CSS or JavaScript conflicts. See an example of this in action.

Download Pym.js

Requirements/Assumptions

  • pym.js does not require jQuery or any other libraries. Some of the examples use jQuery on the child pages for unrelated features.
  • If you’re pasting the iframe embed code into a CMS or blogging software, make sure that it allows you to embed HTML and JavaScript. Some CMSes (such as WordPress) may strip tags out.
  • The parent and child pages do not need to be on the same domain. If they are not, child URLs should be fully-qualified, e.g., http://blog.apps.npr.org/pym.js/examples/graphic/child.html
  • pym.js isn't built to handle embedding more than a handful of child frames on the parent. Ten or fewer is a good rule of thumb.

Browsers

pym.js has been tested in:

  • Internet Explorer 9, 10 (Windows 7), 11 (Windows 8)
  • Chrome 32 (Mac 10.9)
  • Firefox 26 (Mac 10.9)
  • Safari 7 (Mac 10.9)
  • iOS 7 Safari
  • iOS 7 Chrome
  • Android 4.4 Chrome

Internet Explorer versions earlier than 9 are not supported.

Installing with Bower

If you use Bower to manage your Javascript dependencies then you can install Pym.js by running:

bower install pym.js

Otherwise you can just copy pym.js into your codebase like you would any other javascript library.

Installing with NPM

If you use NPM to manage your Javascript dependencies then you can install Pym.js by running:

npm install pym.js

Otherwise you can just copy pym.js into your codebase like you would any other javascript library.

Usage

Resize your browser window to see the responsiveness in action.

On the Parent Page (Where You’re Putting the iFrame)

  • Name your containing div (or any other block-level element) with a unique ID.
  • Do not apply any padding or border to your container div. This will throw off pym's height calculations. If you need padding or border around your container, wrap it in another div and apply that there.
  • Include pym.js. Only once per page, no matter how many iframes you will have.
  • Create a new pym.Parent(parent_id, child_url); for each responsive iframe. (Pym will generate the actual iframe element.)
  • Optional: To filter incoming message domains, you can pass in a regex. For example: pym.Parent('example', 'child.html', { xdomain: '*\.npr\.org' });
<div id="example"></div>
<script type="text/javascript" src="pym.js"></script>
<script>
    var pymParent = new pym.Parent('example', 'child.html', {});
</script>

Multiple Embeds on the Same Parent Page

You don’t have to do anything special to the child pages, but you do need to keep a few things in mind for the parent page:

  • Each child embed needs a unique div ID (<div id="example">, <div id="bar-graph">, etc.).
  • Include pym.js only once on the page.
HTML Needed on the Parent Page
<div id="example-1"></div>
<div id="example-2"></div>
Javascript Needed on the Parent Page
var pymParent = new pym.Parent('example-1', 'child-1.html', {});
var second = new pym.Parent('example-2', 'child-2.html', {});

On the Child Page (The Content You Want to Embed)

  • Include pym.js.
  • Create a new pym.Child();.
  • Optional: If the contents of your iframe are dynamic you will want to pass in a rendering function, like this: pym.Child({ renderCallback: myFunc }); This function will be called once when the page loads and again any time the window is resized.
  • Optional: You can pass in a number of milliseconds to enable automaticaly updating the height at that rate (in addition to onload and onresize events). For example: pym.Child({ polling: 500 });.
  • Optional: If you need finer control over resize events, you can invoke pymChild.sendHeight() at any time to force the iframe to update its size.

Example: Basic Embed

In our simplest example, we have an HTML table that changes format (via CSS media queries). The height of the iframe adjusts to the height of the content onload and onresize.

Javascript Needed on the Child Page
var pymChild = new pym.Child();

Example: Call a Function When the Page Resizes

This might be useful in cases where sections of your child page need to be redrawn based on the new width. (For example, a graphic generated by D3, which would not stretch or reflow on its own.)

Javascript Needed on the Child Page
function drawGraphic(width) {
    ...
}

var pymChild = new pym.Child({ renderCallback: drawGraphic });

Example: Manual resize events

If you have dynamic content and need finer control over resize events, you can invoke pymChild.sendHeight() in the child window at any time to force the iframe to update its size. For example, say you have a quiz, and the content of the page changes when someone selects an answer, affecting the page’s height:

Javascript Needed on the Child Page
function check_answer(e) {
    // highlight the correct answer
    ...

    // send updated height to parent
    pymChild.sendHeight();
}

$('.question').find('li').on('click', check_answer);

var pymChild = new pym.Child();

Example: Resizing after following links in the iframe

If you have links in your iframe that lead to other pages, you can maintain auto-resizing on the subsequent pages by explicitly setting the child ID in each one.

Javascript Needed on Every Child Page
var pymChild = new pym.Child({ id: 'example-follow-links' });
                        

How It Works

The Pym.js library and a small bit of javascript are injected onto the parent page. This code writes an iframe to the page in a container of your choice. The request for the iframe’s contents includes querystring parameters for the initialWidth and childId of the child page. The initialWidth allows the child to know its size immediately on load, because in iOS, the child frame can not determine its own width accurately. The childId allows multiple children to be embedded on the same page, each with its own communication to the parent.

The child page also includes Pym.js and its own javascript. It initializes cross-iframe communication to the parent, renders any dynamic content and then sends the computed height of the child to the parent via postMessage. Upon receiving this message the parent resizes the containing iframe, thus ensuring the contents of the child are always visible.

The parent page also registers for resize events. Any time one is received, the parent sends the new container width to each child via postMessage. The child re-renders its content and sends back the new height.

Auto-initializing Pym

Certain CMSes prevent custom Javascript from being embedded on the page. In order to allow pym to support those environments, it can be initialized via data- attributes. This limits usage of pym to simple cases, but should still be enough for many users.

(NPR member stations: Use this minor workaround to get this to work in Core Publisher.)

HTML Needed on the Parent Page
<div data-pym-src="child.html"></div>

Sending Custom iFrame Events

Although not its original purpose, pym.js can also be used to send and receive generic event data between the iframe parent and child. A simple, jQuery-like event binding model is used to support this message passing.

In the following example a click on the link in the parent container will navigate the child frame to a new location.

Javascript Needed on the Parent Page
var pymParent = pym.Parent('example', 'child.html', {});

document.getElementById('myLink').addEventListener('click', onLinkClick);

function onLinkClick(e) {
    e.preventDefault();

    pymParent.sendMessage('navigate', e.target.href);
}
Javascript needed on the Child page
var pymChild = new pym.Child();

pymChild.onMessage('navigate', onNavigateMessage);

function onNavigateMessage(url) {
    window.location.href = url;
}

Note that the reverse scenario can be performed as well; children can send messages to the parent with the same sendMessage() function.

Shortcuts for navigating the parent from the child

As scrolling and navigating the parent window is a very common need, there are shortcuts for these actions that do not require you to implement your own events.

Code to scroll and navigate the parent
pymChild.scrollParentTo('about');
pymChild.navigateParentTo('https://github.com/nprapps/pym.js');

Pym.js in the wild

License

MIT